am c5d5c3db: Merge "bitmapfun: Add support to use inBitmap option" into jb-mr1-dev

* commit 'c5d5c3db62354a51b90934511a1f8dd1c87e987f':
  bitmapfun: Add support to use inBitmap option
This commit is contained in:
Chris Banes
2013-02-21 14:14:35 -08:00
committed by Android Git Automerger
4 changed files with 132 additions and 9 deletions

View File

@@ -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,10 +334,43 @@ 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.
@@ -425,6 +485,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).
*

View File

@@ -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 {

View File

@@ -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

View File

@@ -164,6 +164,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