Merge "bitmapfun: Add support to use inBitmap option" into jb-mr1-dev
This commit is contained in:
@@ -20,8 +20,8 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Bitmap.CompressFormat;
|
import android.graphics.Bitmap.CompressFormat;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.StatFs;
|
import android.os.StatFs;
|
||||||
@@ -33,11 +33,16 @@ import android.util.Log;
|
|||||||
import com.example.android.bitmapfun.BuildConfig;
|
import com.example.android.bitmapfun.BuildConfig;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds our bitmap caches (memory and disk).
|
* This class holds our bitmap caches (memory and disk).
|
||||||
@@ -68,6 +73,8 @@ public class ImageCache {
|
|||||||
private final Object mDiskCacheLock = new Object();
|
private final Object mDiskCacheLock = new Object();
|
||||||
private boolean mDiskCacheStarting = true;
|
private boolean mDiskCacheStarting = true;
|
||||||
|
|
||||||
|
private HashSet<SoftReference<Bitmap>> mReusableBitmaps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creating a new ImageCache object using the specified parameters.
|
* Creating a new ImageCache object using the specified parameters.
|
||||||
*
|
*
|
||||||
@@ -126,6 +133,12 @@ public class ImageCache {
|
|||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
|
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) {
|
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +151,14 @@ public class ImageCache {
|
|||||||
// The removed entry is a recycling drawable, so notify it
|
// The removed entry is a recycling drawable, so notify it
|
||||||
// that it has been removed from the memory cache
|
// that it has been removed from the memory cache
|
||||||
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
|
((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) {
|
public Bitmap getBitmapFromDiskCache(String data) {
|
||||||
final String key = hashKeyForDisk(data);
|
final String key = hashKeyForDisk(data);
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
|
||||||
synchronized (mDiskCacheLock) {
|
synchronized (mDiskCacheLock) {
|
||||||
while (mDiskCacheStarting) {
|
while (mDiskCacheStarting) {
|
||||||
try {
|
try {
|
||||||
@@ -293,8 +316,12 @@ public class ImageCache {
|
|||||||
}
|
}
|
||||||
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
|
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
|
||||||
return bitmap;
|
|
||||||
|
// 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) {
|
} catch (final IOException e) {
|
||||||
@@ -307,10 +334,43 @@ public class ImageCache {
|
|||||||
} catch (IOException e) {}
|
} 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
|
* 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.
|
* 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).
|
* Get a usable cache directory (external if available, internal otherwise).
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -241,7 +241,8 @@ public class ImageFetcher extends ImageResizer {
|
|||||||
|
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
if (fileDescriptor != null) {
|
if (fileDescriptor != null) {
|
||||||
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, mImageHeight);
|
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
|
||||||
|
mImageHeight, getImageCache());
|
||||||
}
|
}
|
||||||
if (fileInputStream != null) {
|
if (fileInputStream != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
package com.example.android.bitmapfun.util;
|
package com.example.android.bitmapfun.util;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.example.android.bitmapfun.BuildConfig;
|
import com.example.android.bitmapfun.BuildConfig;
|
||||||
@@ -90,7 +92,8 @@ public class ImageResizer extends ImageWorker {
|
|||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.d(TAG, "processBitmap - " + resId);
|
Log.d(TAG, "processBitmap - " + resId);
|
||||||
}
|
}
|
||||||
return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, mImageHeight);
|
return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
|
||||||
|
mImageHeight, getImageCache());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,11 +108,12 @@ public class ImageResizer extends ImageWorker {
|
|||||||
* @param resId The resource id of the image data
|
* @param resId The resource id of the image data
|
||||||
* @param reqWidth The requested width of the resulting bitmap
|
* @param reqWidth The requested width of the resulting bitmap
|
||||||
* @param reqHeight The requested height 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
|
* @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
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
|
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
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
@@ -119,6 +123,11 @@ public class ImageResizer extends ImageWorker {
|
|||||||
// Calculate inSampleSize
|
// Calculate inSampleSize
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
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
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
return BitmapFactory.decodeResource(res, resId, options);
|
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 filename The full path of the file to decode
|
||||||
* @param reqWidth The requested width of the resulting bitmap
|
* @param reqWidth The requested width of the resulting bitmap
|
||||||
* @param reqHeight The requested height 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
|
* @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
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromFile(String filename,
|
public static Bitmap decodeSampledBitmapFromFile(String filename,
|
||||||
int reqWidth, int reqHeight) {
|
int reqWidth, int reqHeight, ImageCache cache) {
|
||||||
|
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
@@ -144,6 +154,11 @@ public class ImageResizer extends ImageWorker {
|
|||||||
// Calculate inSampleSize
|
// Calculate inSampleSize
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
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
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
return BitmapFactory.decodeFile(filename, options);
|
return BitmapFactory.decodeFile(filename, options);
|
||||||
@@ -155,11 +170,12 @@ public class ImageResizer extends ImageWorker {
|
|||||||
* @param fileDescriptor The file descriptor to read from
|
* @param fileDescriptor The file descriptor to read from
|
||||||
* @param reqWidth The requested width of the resulting bitmap
|
* @param reqWidth The requested width of the resulting bitmap
|
||||||
* @param reqHeight The requested height 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
|
* @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
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromDescriptor(
|
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
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
@@ -171,9 +187,34 @@ public class ImageResizer extends ImageWorker {
|
|||||||
|
|
||||||
// Decode bitmap with inSampleSize set
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
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);
|
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
|
* Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
|
||||||
* bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
|
* bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
|
||||||
|
|||||||
@@ -164,6 +164,13 @@ public abstract class ImageWorker {
|
|||||||
*/
|
*/
|
||||||
protected abstract Bitmap processBitmap(Object data);
|
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.
|
* Cancels any pending work attached to the provided ImageView.
|
||||||
* @param imageView
|
* @param imageView
|
||||||
|
|||||||
Reference in New Issue
Block a user