Merge "Bitmapfun Sample: Add additional sample images." into jb-mr1-dev
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 f567e76..d65d655 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.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 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 @@
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 @@
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 @@
// 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 Bitmap getBitmapFromDiskCache(String data) {
final String key = hashKeyForDisk(data);
+ Bitmap bitmap = null;
+
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
@@ -293,8 +316,12 @@
}
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 @@
} 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 @@
}
/**
+ * @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 7084845..4c92d74 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 @@
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 6a129c3..2a9d152 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 @@
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 @@
* @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 @@
// 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 @@
* @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 @@
// 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 @@
* @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 @@
// 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 84a0f59..87b2cb2 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 @@
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
*/