summaryrefslogtreecommitdiffstats
path: root/samples/training
diff options
context:
space:
mode:
authorChris Banes <chrisbanes@google.com>2013-02-07 14:02:46 +0000
committerChris Banes <chrisbanes@google.com>2013-02-13 13:19:09 +0000
commit3b7f23f8818d496c4afea597e0b5158a481131ed (patch)
treefd2a34ffe3b901a0bf489c9e42fca1799e0b65b8 /samples/training
parent4d6574e878e2af2f19b46b28b28748ba2786e05a (diff)
downloadandroid_development-3b7f23f8818d496c4afea597e0b5158a481131ed.tar.gz
android_development-3b7f23f8818d496c4afea597e0b5158a481131ed.tar.bz2
android_development-3b7f23f8818d496c4afea597e0b5158a481131ed.zip
bitmapfun: Add support to use inBitmap option
This adds support for devices running Honeycomb or newer to use the inBitmap BitmapFactory option when decoding images. The way it accomplishes this is by adding a second level 'memory cache' using SoftReferences. When an item is removed from the LruCache it is added to this SoftReference Set. The next time an image is decoded, the Set is searched to see if it contains a bitmap that can be re-used. Change-Id: I8f980160ddc7116731b25e6f3f110c38f207f286 Signed-off-by: Chris Banes <chrisbanes@google.com>
Diffstat (limited to 'samples/training')
-rw-r--r--samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java82
-rw-r--r--samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java3
-rw-r--r--samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java49
-rw-r--r--samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java7
4 files changed, 132 insertions, 9 deletions
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
index f567e76a3..d65d65568 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
@@ -20,8 +20,8 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
@@ -33,11 +33,16 @@ import android.util.Log;
import com.example.android.bitmapfun.BuildConfig;
import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.ref.SoftReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
/**
* This class holds our bitmap caches (memory and disk).
@@ -68,6 +73,8 @@ public class ImageCache {
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
+ private HashSet<SoftReference<Bitmap>> mReusableBitmaps;
+
/**
* Creating a new ImageCache object using the specified parameters.
*
@@ -126,6 +133,12 @@ public class ImageCache {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
}
+
+ // If we're running on Honeycomb or newer, then
+ if (Utils.hasHoneycomb()) {
+ mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
+ }
+
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
/**
@@ -138,6 +151,14 @@ public class ImageCache {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+ } else {
+ // The removed entry is a standard BitmapDrawable
+
+ if (Utils.hasHoneycomb()) {
+ // We're running on Honeycomb or later, so add the bitmap
+ // to a SoftRefrence set for possible use with inBitmap later
+ mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
+ }
}
}
@@ -277,6 +298,8 @@ public class ImageCache {
*/
public Bitmap getBitmapFromDiskCache(String data) {
final String key = hashKeyForDisk(data);
+ Bitmap bitmap = null;
+
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
@@ -293,8 +316,12 @@ public class ImageCache {
}
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
- final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
- return bitmap;
+ FileDescriptor fd = ((FileInputStream) inputStream).getFD();
+
+ // Decode bitmap, but we don't want to sample so give
+ // MAX_VALUE as the target dimensions
+ bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
+ fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (final IOException e) {
@@ -307,11 +334,44 @@ public class ImageCache {
} catch (IOException e) {}
}
}
- return null;
+ return bitmap;
}
}
/**
+ * @param options - BitmapFactory.Options with out* options populated
+ * @return Bitmap that case be used for inBitmap
+ */
+ protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+ Bitmap bitmap = null;
+
+ if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+ final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
+ Bitmap item;
+
+ while (iterator.hasNext()) {
+ item = iterator.next().get();
+
+ if (null != item && item.isMutable()) {
+ // Check to see it the item can be used for inBitmap
+ if (canUseForInBitmap(item, options)) {
+ bitmap = item;
+
+ // Remove from reusable set so it can't be used again
+ iterator.remove();
+ break;
+ }
+ } else {
+ // Remove from the set if the reference has been cleared.
+ iterator.remove();
+ }
+ }
+ }
+
+ return bitmap;
+ }
+
+ /**
* Clears both the memory and disk cache associated with this ImageCache object. Note that
* this includes disk access so this should not be executed on the main/UI thread.
*/
@@ -426,6 +486,20 @@ public class ImageCache {
}
/**
+ * @param candidate - Bitmap to check
+ * @param targetOptions - Options that have the out* value populated
+ * @return true if <code>candidate</code> can be used for inBitmap re-use with
+ * <code>targetOptions</code>
+ */
+ private static boolean canUseForInBitmap(
+ Bitmap candidate, BitmapFactory.Options targetOptions) {
+ int width = targetOptions.outWidth / targetOptions.inSampleSize;
+ int height = targetOptions.outHeight / targetOptions.inSampleSize;
+
+ return candidate.getWidth() == width && candidate.getHeight() == height;
+ }
+
+ /**
* Get a usable cache directory (external if available, internal otherwise).
*
* @param context The context to use
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
index 708484530..4c92d7417 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
@@ -241,7 +241,8 @@ public class ImageFetcher extends ImageResizer {
Bitmap bitmap = null;
if (fileDescriptor != null) {
- bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, mImageHeight);
+ bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
+ mImageHeight, getImageCache());
}
if (fileInputStream != null) {
try {
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
index 6a129c318..2a9d152d1 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
@@ -16,10 +16,12 @@
package com.example.android.bitmapfun.util;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.Build;
import android.util.Log;
import com.example.android.bitmapfun.BuildConfig;
@@ -90,7 +92,8 @@ public class ImageResizer extends ImageWorker {
if (BuildConfig.DEBUG) {
Log.d(TAG, "processBitmap - " + resId);
}
- return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, mImageHeight);
+ return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
+ mImageHeight, getImageCache());
}
@Override
@@ -105,11 +108,12 @@ public class ImageResizer extends ImageWorker {
* @param resId The resource id of the image data
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
- int reqWidth, int reqHeight) {
+ int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -119,6 +123,11 @@ public class ImageResizer extends ImageWorker {
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
@@ -130,11 +139,12 @@ public class ImageResizer extends ImageWorker {
* @param filename The full path of the file to decode
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromFile(String filename,
- int reqWidth, int reqHeight) {
+ int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -144,6 +154,11 @@ public class ImageResizer extends ImageWorker {
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filename, options);
@@ -155,11 +170,12 @@ public class ImageResizer extends ImageWorker {
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
- FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
+ FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -171,9 +187,34 @@ public class ImageResizer extends ImageWorker {
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
+ // inBitmap only works with mutable bitmaps so force the decoder to
+ // return mutable bitmaps.
+ options.inMutable = true;
+
+ if (cache != null) {
+ // Try and find a bitmap to use for inBitmap
+ Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+ if (inBitmap != null) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Found bitmap to use for inBitmap");
+ }
+ options.inBitmap = inBitmap;
+ }
+ }
+ }
+
/**
* Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
index 84a0f5981..87b2cb232 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
@@ -165,6 +165,13 @@ public abstract class ImageWorker {
protected abstract Bitmap processBitmap(Object data);
/**
+ * @return The {@link ImageCache} object currently being used by this ImageWorker.
+ */
+ protected ImageCache getImageCache() {
+ return mImageCache;
+ }
+
+ /**
* Cancels any pending work attached to the provided ImageView.
* @param imageView
*/