Merge "Update bitmapfun sample to call recycle()" into jb-mr1-dev
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ImageView
|
||||
<com.example.android.bitmapfun.ui.RecyclingImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.example.android.bitmapfun.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
@@ -270,7 +270,7 @@ public class ImageGridFragment extends Fragment implements AdapterView.OnItemCli
|
||||
// Now handle the main ImageView thumbnails
|
||||
ImageView imageView;
|
||||
if (convertView == null) { // if it's not recycled, instantiate and initialize
|
||||
imageView = new ImageView(mContext);
|
||||
imageView = new RecyclingImageView(mContext);
|
||||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
imageView.setLayoutParams(mImageViewLayoutParams);
|
||||
} else { // Otherwise re-use the converted view
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.bitmapfun.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.example.android.bitmapfun.util.RecyclingBitmapDrawable;
|
||||
|
||||
/**
|
||||
* Sub-class of ImageView which automatically notifies the drawable when it is
|
||||
* being displayed.
|
||||
*/
|
||||
public class RecyclingImageView extends ImageView {
|
||||
|
||||
public RecyclingImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RecyclingImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.widget.ImageView#onDetachedFromWindow()
|
||||
*/
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
// This has been detached from Window, so clear the drawable
|
||||
setImageDrawable(null);
|
||||
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
|
||||
*/
|
||||
@Override
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
// Keep hold of previous Drawable
|
||||
final Drawable previousDrawable = getDrawable();
|
||||
|
||||
// Call super to set new Drawable
|
||||
super.setImageDrawable(drawable);
|
||||
|
||||
// Notify new Drawable that it is being displayed
|
||||
notifyDrawable(drawable, true);
|
||||
|
||||
// Notify old Drawable so it is no longer being displayed
|
||||
notifyDrawable(previousDrawable, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the drawable that it's displayed state has changed.
|
||||
*
|
||||
* @param drawable
|
||||
* @param isDisplayed
|
||||
*/
|
||||
private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
|
||||
if (drawable instanceof RecyclingBitmapDrawable) {
|
||||
// The drawable is a CountingBitmapDrawable, so notify it
|
||||
((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
|
||||
} else if (drawable instanceof LayerDrawable) {
|
||||
// The drawable is a LayerDrawable, so recurse on each layer
|
||||
LayerDrawable layerDrawable = (LayerDrawable) drawable;
|
||||
for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
|
||||
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,10 +17,10 @@
|
||||
package com.example.android.bitmapfun.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
@@ -63,7 +63,7 @@ public class ImageCache {
|
||||
private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
|
||||
|
||||
private DiskLruCache mDiskLruCache;
|
||||
private LruCache<String, Bitmap> mMemoryCache;
|
||||
private LruCache<String, BitmapDrawable> mMemoryCache;
|
||||
private ImageCacheParams mCacheParams;
|
||||
private final Object mDiskCacheLock = new Object();
|
||||
private boolean mDiskCacheStarting = true;
|
||||
@@ -126,14 +126,28 @@ public class ImageCache {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
|
||||
}
|
||||
mMemoryCache = new LruCache<String, Bitmap>(mCacheParams.memCacheSize) {
|
||||
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
|
||||
|
||||
/**
|
||||
* Notify the removed entry that is no longer being cached
|
||||
*/
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, String key,
|
||||
BitmapDrawable oldValue, BitmapDrawable newValue) {
|
||||
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
|
||||
// The removed entry is a recycling drawable, so notify it
|
||||
// that it has been removed from the memory cache
|
||||
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure item size in kilobytes rather than units which is more practical
|
||||
* for a bitmap cache
|
||||
*/
|
||||
@Override
|
||||
protected int sizeOf(String key, Bitmap bitmap) {
|
||||
final int bitmapSize = getBitmapSize(bitmap) / 1024;
|
||||
protected int sizeOf(String key, BitmapDrawable value) {
|
||||
final int bitmapSize = getBitmapSize(value) / 1024;
|
||||
return bitmapSize == 0 ? 1 : bitmapSize;
|
||||
}
|
||||
};
|
||||
@@ -184,16 +198,21 @@ public class ImageCache {
|
||||
/**
|
||||
* Adds a bitmap to both memory and disk cache.
|
||||
* @param data Unique identifier for the bitmap to store
|
||||
* @param bitmap The bitmap to store
|
||||
* @param value The bitmap drawable to store
|
||||
*/
|
||||
public void addBitmapToCache(String data, Bitmap bitmap) {
|
||||
if (data == null || bitmap == null) {
|
||||
public void addBitmapToCache(String data, BitmapDrawable value) {
|
||||
if (data == null || value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to memory cache
|
||||
if (mMemoryCache != null && mMemoryCache.get(data) == null) {
|
||||
mMemoryCache.put(data, bitmap);
|
||||
if (mMemoryCache != null) {
|
||||
if (RecyclingBitmapDrawable.class.isInstance(value)) {
|
||||
// The removed entry is a recycling drawable, so notify it
|
||||
// that it has been added into the memory cache
|
||||
((RecyclingBitmapDrawable) value).setIsCached(true);
|
||||
}
|
||||
mMemoryCache.put(data, value);
|
||||
}
|
||||
|
||||
synchronized (mDiskCacheLock) {
|
||||
@@ -207,7 +226,7 @@ public class ImageCache {
|
||||
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
|
||||
if (editor != null) {
|
||||
out = editor.newOutputStream(DISK_CACHE_INDEX);
|
||||
bitmap.compress(
|
||||
value.getBitmap().compress(
|
||||
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
|
||||
editor.commit();
|
||||
out.close();
|
||||
@@ -234,19 +253,20 @@ public class ImageCache {
|
||||
* Get from memory cache.
|
||||
*
|
||||
* @param data Unique identifier for which item to get
|
||||
* @return The bitmap if found in cache, null otherwise
|
||||
* @return The bitmap drawable if found in cache, null otherwise
|
||||
*/
|
||||
public Bitmap getBitmapFromMemCache(String data) {
|
||||
public BitmapDrawable getBitmapFromMemCache(String data) {
|
||||
BitmapDrawable memValue = null;
|
||||
|
||||
if (mMemoryCache != null) {
|
||||
final Bitmap memBitmap = mMemoryCache.get(data);
|
||||
if (memBitmap != null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
memValue = mMemoryCache.get(data);
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG && memValue != null) {
|
||||
Log.d(TAG, "Memory cache hit");
|
||||
}
|
||||
return memBitmap;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return memValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,12 +473,14 @@ public class ImageCache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size in bytes of a bitmap.
|
||||
* @param bitmap
|
||||
* Get the size in bytes of a bitmap in a BitmapDrawable.
|
||||
* @param value
|
||||
* @return size in bytes
|
||||
*/
|
||||
@TargetApi(12)
|
||||
public static int getBitmapSize(Bitmap bitmap) {
|
||||
public static int getBitmapSize(BitmapDrawable value) {
|
||||
Bitmap bitmap = value.getBitmap();
|
||||
|
||||
if (Utils.hasHoneycombMR1()) {
|
||||
return bitmap.getByteCount();
|
||||
}
|
||||
|
||||
@@ -76,15 +76,15 @@ public abstract class ImageWorker {
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap bitmap = null;
|
||||
BitmapDrawable value = null;
|
||||
|
||||
if (mImageCache != null) {
|
||||
bitmap = mImageCache.getBitmapFromMemCache(String.valueOf(data));
|
||||
value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
|
||||
}
|
||||
|
||||
if (bitmap != null) {
|
||||
if (value != null) {
|
||||
// Bitmap found in memory cache
|
||||
imageView.setImageBitmap(bitmap);
|
||||
imageView.setImageDrawable(value);
|
||||
} else if (cancelPotentialWork(data, imageView)) {
|
||||
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
|
||||
final AsyncDrawable asyncDrawable =
|
||||
@@ -222,7 +222,7 @@ public abstract class ImageWorker {
|
||||
/**
|
||||
* The actual AsyncTask that will asynchronously process the image.
|
||||
*/
|
||||
private class BitmapWorkerTask extends AsyncTask<Object, Void, Bitmap> {
|
||||
private class BitmapWorkerTask extends AsyncTask<Object, Void, BitmapDrawable> {
|
||||
private Object data;
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
|
||||
@@ -234,7 +234,7 @@ public abstract class ImageWorker {
|
||||
* Background processing.
|
||||
*/
|
||||
@Override
|
||||
protected Bitmap doInBackground(Object... params) {
|
||||
protected BitmapDrawable doInBackground(Object... params) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "doInBackground - starting work");
|
||||
}
|
||||
@@ -242,6 +242,7 @@ public abstract class ImageWorker {
|
||||
data = params[0];
|
||||
final String dataString = String.valueOf(data);
|
||||
Bitmap bitmap = null;
|
||||
BitmapDrawable drawable = null;
|
||||
|
||||
// Wait here if work is paused and the task is not cancelled
|
||||
synchronized (mPauseWorkLock) {
|
||||
@@ -274,39 +275,50 @@ public abstract class ImageWorker {
|
||||
// bitmap to the cache for future use. Note we don't check if the task was cancelled
|
||||
// here, if it was, and the thread is still running, we may as well add the processed
|
||||
// bitmap to our cache as it might be used again in the future
|
||||
if (bitmap != null && mImageCache != null) {
|
||||
mImageCache.addBitmapToCache(dataString, bitmap);
|
||||
if (bitmap != null) {
|
||||
if (Utils.hasHoneycomb()) {
|
||||
// Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
|
||||
drawable = new BitmapDrawable(mResources, bitmap);
|
||||
} else {
|
||||
// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
|
||||
// which will recycle automagically
|
||||
drawable = new RecyclingBitmapDrawable(mResources, bitmap);
|
||||
}
|
||||
|
||||
if (mImageCache != null) {
|
||||
mImageCache.addBitmapToCache(dataString, drawable);
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "doInBackground - finished work");
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
return drawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the image is processed, associates it to the imageView
|
||||
*/
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
protected void onPostExecute(BitmapDrawable value) {
|
||||
// if cancel was called on this task or the "exit early" flag is set then we're done
|
||||
if (isCancelled() || mExitTasksEarly) {
|
||||
bitmap = null;
|
||||
value = null;
|
||||
}
|
||||
|
||||
final ImageView imageView = getAttachedImageView();
|
||||
if (bitmap != null && imageView != null) {
|
||||
if (value != null && imageView != null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "onPostExecute - setting bitmap");
|
||||
}
|
||||
setImageBitmap(imageView, bitmap);
|
||||
setImageDrawable(imageView, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(Bitmap bitmap) {
|
||||
super.onCancelled(bitmap);
|
||||
protected void onCancelled(BitmapDrawable value) {
|
||||
super.onCancelled(value);
|
||||
synchronized (mPauseWorkLock) {
|
||||
mPauseWorkLock.notifyAll();
|
||||
}
|
||||
@@ -349,18 +361,19 @@ public abstract class ImageWorker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the processing is complete and the final bitmap should be set on the ImageView.
|
||||
* Called when the processing is complete and the final drawable should be
|
||||
* set on the ImageView.
|
||||
*
|
||||
* @param imageView
|
||||
* @param bitmap
|
||||
* @param drawable
|
||||
*/
|
||||
private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
|
||||
private void setImageDrawable(ImageView imageView, Drawable drawable) {
|
||||
if (mFadeInBitmap) {
|
||||
// Transition drawable with a transparent drwabale and the final bitmap
|
||||
// Transition drawable with a transparent drawable and the final drawable
|
||||
final TransitionDrawable td =
|
||||
new TransitionDrawable(new Drawable[] {
|
||||
new ColorDrawable(android.R.color.transparent),
|
||||
new BitmapDrawable(mResources, bitmap)
|
||||
drawable
|
||||
});
|
||||
// Set background to loading bitmap
|
||||
imageView.setBackgroundDrawable(
|
||||
@@ -369,7 +382,7 @@ public abstract class ImageWorker {
|
||||
imageView.setImageDrawable(td);
|
||||
td.startTransition(FADE_IN_TIME);
|
||||
} else {
|
||||
imageView.setImageBitmap(bitmap);
|
||||
imageView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.bitmapfun.util;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.bitmapfun.BuildConfig;
|
||||
|
||||
/**
|
||||
* A BitmapDrawable that keeps track of whether it is being displayed or cached.
|
||||
* When the drawable is no longer being displayed or cached,
|
||||
* {@link Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
|
||||
*/
|
||||
public class RecyclingBitmapDrawable extends BitmapDrawable {
|
||||
|
||||
static final String LOG_TAG = "CountingBitmapDrawable";
|
||||
|
||||
private int mCacheRefCount = 0;
|
||||
private int mDisplayRefCount = 0;
|
||||
|
||||
private boolean mHasBeenDisplayed;
|
||||
|
||||
public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
|
||||
super(res, bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the drawable that the displayed state has changed. Internally a
|
||||
* count is kept so that the drawable knows when it is no longer being
|
||||
* displayed.
|
||||
*
|
||||
* @param isDisplayed - Whether the drawable is being displayed or not
|
||||
*/
|
||||
public void setIsDisplayed(boolean isDisplayed) {
|
||||
synchronized (this) {
|
||||
if (isDisplayed) {
|
||||
mDisplayRefCount++;
|
||||
mHasBeenDisplayed = true;
|
||||
} else {
|
||||
mDisplayRefCount--;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if recycle() can be called
|
||||
checkState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the drawable that the cache state has changed. Internally a count
|
||||
* is kept so that the drawable knows when it is no longer being cached.
|
||||
*
|
||||
* @param isCached - Whether the drawable is being cached or not
|
||||
*/
|
||||
public void setIsCached(boolean isCached) {
|
||||
synchronized (this) {
|
||||
if (isCached) {
|
||||
mCacheRefCount++;
|
||||
} else {
|
||||
mCacheRefCount--;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if recycle() can be called
|
||||
checkState();
|
||||
}
|
||||
|
||||
private synchronized void checkState() {
|
||||
// If the drawable cache and display ref counts = 0, and this drawable
|
||||
// has been displayed, then recycle
|
||||
if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
|
||||
&& hasValidBitmap()) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOG_TAG, "No longer being used or cached so recycling. "
|
||||
+ toString());
|
||||
}
|
||||
|
||||
getBitmap().recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean hasValidBitmap() {
|
||||
Bitmap bitmap = getBitmap();
|
||||
return bitmap != null && !bitmap.isRecycled();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user