Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge tag 'v2.1'

[maven-release-plugin]  copy for tag v2.1
  • Loading branch information...
commit bb40c8b5a0e8a53ef863358a1592d9b9f694fa9a 2 parents 898e3f1 + 04bb386
@chrisbanes authored
Showing with 552 additions and 315 deletions.
  1. +2 −2 library/AndroidManifest.xml
  2. BIN  library/libs/disklrucache-1.2.1.jar
  3. BIN  library/libs/disklrucache-1.3.1.jar
  4. +2 −2 library/pom.xml
  5. +1 −1  library/project.properties
  6. +186 −100 library/src/uk/co/senab/bitmapcache/BitmapLruCache.java
  7. +19 −12 library/src/uk/co/senab/bitmapcache/BitmapMemoryLruCache.java
  8. +193 −0 library/src/uk/co/senab/bitmapcache/CacheableBitmapDrawable.java
  9. +8 −120 library/src/uk/co/senab/bitmapcache/CacheableBitmapWrapper.java
  10. +27 −40 library/src/uk/co/senab/bitmapcache/CacheableImageView.java
  11. +5 −3 library/src/uk/co/senab/bitmapcache/Constants.java
  12. +1 −1  library/src/uk/co/senab/bitmapcache/Md5.java
  13. +24 −0 library/src/uk/co/senab/bitmapcache/WeakReferenceRunnable.java
  14. +2 −2 pom.xml
  15. +2 −2 sample/AndroidManifest.xml
  16. +1 −1  sample/pom.xml
  17. +1 −1  sample/project.properties
  18. +3 −2 sample/src/uk/co/senab/bitmapcache/samples/GridViewActivity.java
  19. +57 −23 sample/src/uk/co/senab/bitmapcache/samples/NetworkedCacheableImageView.java
  20. +1 −1  sample/src/uk/co/senab/bitmapcache/samples/PugPagerAdapter.java
  21. +14 −0 sample/src/uk/co/senab/bitmapcache/samples/SDK11.java
  22. +3 −2 sample/src/uk/co/senab/bitmapcache/samples/ViewPagerActivity.java
View
4 library/AndroidManifest.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.senab.bitmapcache"
- android:versionCode="2000"
- android:versionName="2.0" >
+ android:versionCode="2100"
+ android:versionName="2.1" >
<uses-sdk android:minSdkVersion="4" />
View
BIN  library/libs/disklrucache-1.2.1.jar
Binary file not shown
View
BIN  library/libs/disklrucache-1.3.1.jar
Binary file not shown
View
4 library/pom.xml
@@ -10,7 +10,7 @@
<parent>
<groupId>com.github.chrisbanes.bitmapcache</groupId>
<artifactId>parent</artifactId>
- <version>2.0</version>
+ <version>2.1</version>
</parent>
<dependencies>
@@ -22,7 +22,7 @@
<groupId>com.jakewharton</groupId>
<artifactId>disklrucache</artifactId>
<type>jar</type>
- <version>1.2.1</version>
+ <version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
View
2  library/project.properties
@@ -11,5 +11,5 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-17
+target=android-16
android.library=true
View
286 library/src/uk/co/senab/bitmapcache/BitmapLruCache.java
@@ -26,6 +26,8 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.os.Looper;
import android.os.Process;
import android.util.Log;
@@ -67,6 +69,16 @@
static final int DISK_CACHE_FLUSH_DELAY_SECS = 5;
/**
+ * @throws IllegalStateException if the calling thread is the main/UI
+ * thread.
+ */
+ private static void checkNotOnMainThread() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("This method should not be called from the main/UI thread.");
+ }
+ }
+
+ /**
* The disk cache only accepts a reduced range of characters for the key
* values. This method transforms the {@code url} into something accepted
* from {@link DiskLruCache}. Currently we simply return a MD5 hash of the
@@ -79,80 +91,138 @@ private static String transformUrlForDiskCacheKey(String url) {
return Md5.encode(url);
}
- private final DiskLruCache mDiskCache;
- private final BitmapMemoryLruCache mMemoryCache;
+ private DiskLruCache mDiskCache;
+ private BitmapMemoryLruCache mMemoryCache;
// Variables which are only used when the Disk Cache is enabled
- private final HashMap<String, ReentrantLock> mDiskCacheEditLocks;
- private final ScheduledThreadPoolExecutor mDiskCacheFlusherExecutor;
- private final DiskCacheFlushRunnable mDiskCacheFlusherRunnable;
+ private HashMap<String, ReentrantLock> mDiskCacheEditLocks;
+ private ScheduledThreadPoolExecutor mDiskCacheFlusherExecutor;
+ private DiskCacheFlushRunnable mDiskCacheFlusherRunnable;
// Transient
private ScheduledFuture<?> mDiskCacheFuture;
- protected BitmapLruCache(BitmapMemoryLruCache memoryCache, DiskLruCache diskCache) {
+ protected BitmapLruCache(BitmapMemoryLruCache memoryCache) {
mMemoryCache = memoryCache;
- mDiskCache = diskCache;
+ }
- if (null != diskCache) {
- mDiskCacheEditLocks = new HashMap<String, ReentrantLock>();
- mDiskCacheFlusherExecutor = new ScheduledThreadPoolExecutor(1);
- mDiskCacheFlusherRunnable = new DiskCacheFlushRunnable(diskCache);
- } else {
- mDiskCacheEditLocks = null;
- mDiskCacheFlusherExecutor = null;
- mDiskCacheFlusherRunnable = null;
+ /**
+ * Returns whether any of the enabled caches contain the specified URL.
+ * <p/>
+ * If you have the disk cache enabled, you should not call this method from
+ * main/UI thread.
+ *
+ * @param url the URL to search for.
+ * @return {@code true} if any of the caches contain the specified URL,
+ * {@code false} otherwise.
+ */
+ public boolean contains(String url) {
+ return containsInMemoryCache(url) || containsInDiskCache(url);
+ }
+
+ /**
+ * Returns whether the Disk Cache contains the specified URL. You should not
+ * call this method from main/UI thread.
+ *
+ * @param url the URL to search for.
+ * @return {@code true} if the Disk Cache is enabled and contains the
+ * specified URL, {@code false} otherwise.
+ */
+ public boolean containsInDiskCache(String url) {
+ if (null != mDiskCache) {
+ checkNotOnMainThread();
+
+ try {
+ return null != mDiskCache.get(url);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
+
+ return false;
+ }
+
+ /**
+ * Returns whether the Memory Cache contains the specified URL. This method
+ * is safe to be called from the main thread.
+ *
+ * @param url the URL to search for.
+ * @return {@code true} if the Memory Cache is enabled and contains the
+ * specified URL, {@code false} otherwise.
+ */
+ public boolean containsInMemoryCache(String url) {
+ return null != mMemoryCache && null != mMemoryCache.get(url);
+ }
+
+ /**
+ * Returns the value for {@code url}. This will check all caches currently
+ * enabled.
+ * <p/>
+ * If you have the disk cache enabled, you should not call this method from
+ * main/UI thread.
+ *
+ * @param url - String representing the URL of the image
+ */
+ public CacheableBitmapDrawable get(String url) {
+ return get(url, null);
}
/**
* Returns the value for {@code url}. This will check all caches currently
- * enabled, meaning that this probably isn't safe to be called on the main
- * thread.
+ * enabled.
+ * <p/>
+ * If you have the disk cache enabled, you should not call this method from
+ * main/UI thread.
*
* @param url - String representing the URL of the image
+ * @param decodeOpts - Options used for decoding the contents from the disk
+ * cache only.
*/
- public CacheableBitmapWrapper get(String url) {
- CacheableBitmapWrapper result = null;
+ public CacheableBitmapDrawable get(String url, BitmapFactory.Options decodeOpts) {
+ CacheableBitmapDrawable result = null;
// First try Memory Cache
result = getFromMemoryCache(url);
if (null == result) {
// Memory Cache failed, so try Disk Cache
- result = getFromDiskCache(url);
+ result = getFromDiskCache(url, decodeOpts);
}
return result;
}
/**
- * Returns the value for {@code url} in the disk cache only. As this will
- * read from the file system, this method is not safe to be called from the
- * main thread. If enabled, the result of this method will be cached in the
- * memory cache.
+ * Returns the value for {@code url} in the disk cache only. You should not
+ * call this method from main/UI thread.
+ * <p/>
+ * If enabled, the result of this method will be cached in the memory cache.
* <p />
* Unless you have a specific requirement to only query the disk cache, you
* should call {@link #get(String)} instead.
*
* @param url - String representing the URL of the image
+ * @param decodeOpts - Options used for decoding the contents from the disk
+ * cache.
* @return Value for {@code url} from disk cache, or {@code null} if the
* disk cache is not enabled.
*/
- public CacheableBitmapWrapper getFromDiskCache(final String url) {
- CacheableBitmapWrapper result = null;
+ public CacheableBitmapDrawable getFromDiskCache(final String url, final BitmapFactory.Options decodeOpts) {
+ CacheableBitmapDrawable result = null;
if (null != mDiskCache) {
+ checkNotOnMainThread();
+
try {
final String key = transformUrlForDiskCacheKey(url);
DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
if (null != snapshot) {
// Try and decode bitmap
- Bitmap bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
+ Bitmap bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0), null, decodeOpts);
if (null != bitmap) {
- result = new CacheableBitmapWrapper(url, bitmap);
- putIntoMemoryCache(result);
+ result = new CacheableBitmapDrawable(url, bitmap);
+ mMemoryCache.put(result);
} else {
// If we get here, the file in the cache can't be
// decoded. Remove it and schedule a flush.
@@ -175,15 +245,12 @@ public CacheableBitmapWrapper getFromDiskCache(final String url) {
* You should check the result of this method before starting a threaded
* call.
*
- * Unless you have a specific requirement to only query the disk cache, you
- * should call {@link #get(String)} instead.
- *
* @param url - String representing the URL of the image
* @return Value for {@code url} from memory cache, or {@code null} if the
* disk cache is not enabled.
*/
- public CacheableBitmapWrapper getFromMemoryCache(final String url) {
- CacheableBitmapWrapper result = null;
+ public CacheableBitmapDrawable getFromMemoryCache(final String url) {
+ CacheableBitmapDrawable result = null;
if (null != mMemoryCache) {
synchronized (mMemoryCache) {
@@ -202,40 +269,25 @@ public CacheableBitmapWrapper getFromMemoryCache(final String url) {
/**
* Caches {@code bitmap} for {@code url} into all enabled caches. If the
- * disk cache is enabled, the bitmap will be compressed losslessly. *
+ * disk cache is enabled, the bitmap will be compressed losslessly.
* <p/>
- * As this method may write to the file system, this method is not safe to
- * be called from the main thread.
+ * If you have the disk cache enabled, you should not call this method from
+ * main/UI thread.
*
- * @param url - String representing the URL of the image
- * @param bitmap - Bitmap which has been decoded from {@code url}
- * @return CacheableBitmapWrapper which can be used to display the bitmap.
+ * @param url - String representing the URL of the image.
+ * @param bitmap - Bitmap which has been decoded from {@code url}.
+ * @return CacheableBitmapDrawable which can be used to display the bitmap.
*/
- public CacheableBitmapWrapper put(final String url, final Bitmap bitmap) {
- return put(url, bitmap, true);
- }
-
- /**
- * Advanced version of {@link #put(String, Bitmap)} which allows selective
- * caching to the disk cache (if the disk cache is enabled).
- * <p/>
- * As this method may write to the file system, this method is not safe to
- * be called from the main thread.
- *
- * @param url - String representing the URL of the image
- * @param bitmap - Bitmap which has been decoded from {@code url}
- * @param cacheToDiskIfEnabled - Cache to disk, if the disk cache is
- * enabled.
- * @return CacheableBitmapWrapper which can be used to display the bitmap.
- */
- public CacheableBitmapWrapper put(final String url, final Bitmap bitmap, final boolean cacheToDiskIfEnabled) {
- CacheableBitmapWrapper wrapper = new CacheableBitmapWrapper(url, bitmap);
+ public CacheableBitmapDrawable put(final String url, final Bitmap bitmap) {
+ CacheableBitmapDrawable d = new CacheableBitmapDrawable(url, bitmap);
if (null != mMemoryCache) {
- putIntoMemoryCache(wrapper);
+ mMemoryCache.put(d);
}
- if (null != mDiskCache && cacheToDiskIfEnabled) {
+ if (null != mDiskCache) {
+ checkNotOnMainThread();
+
final ReentrantLock lock = getLockForDiskCacheEdit(url);
lock.lock();
try {
@@ -250,7 +302,7 @@ public CacheableBitmapWrapper put(final String url, final Bitmap bitmap, final b
}
}
- return wrapper;
+ return d;
}
/**
@@ -262,37 +314,50 @@ public CacheableBitmapWrapper put(final String url, final Bitmap bitmap, final b
* The contents of the InputStream will be copied to a temporary file, then
* the file will be decoded into a Bitmap. Providing the decode worked:
* <ul>
- * <li>If the memory cache is enabled, the Bitmap will be cached to memory</li>
- * <li>If the disk cache is enabled, the contents of the file will be cached
- * to disk.</li>
+ * <li>If the memory cache is enabled, the decoded Bitmap will be cached to
+ * memory.</li>
+ * <li>If the disk cache is enabled, the contents of the original stream
+ * will be cached to disk.</li>
* </ul>
* <p/>
- * As this method may write to the file system, this method is not safe to
- * be called from the main thread.
+ * You should not call this method from the main/UI thread.
*
* @param url - String representing the URL of the image
* @param inputStream - InputStream opened from {@code url}
- * @return CacheableBitmapWrapper which can be used to display the bitmap.
+ * @return CacheableBitmapDrawable which can be used to display the bitmap.
*/
- public CacheableBitmapWrapper put(final String url, final InputStream inputStream) {
- return put(url, inputStream, true);
+ public CacheableBitmapDrawable put(final String url, final InputStream inputStream) {
+ return put(url, inputStream, null);
}
/**
- * Advanced version of {@link #put(String, InputStream)} which allows
- * selective caching to the disk cache (if the disk cache is enabled).
+ * Caches resulting bitmap from {@code inputStream} for {@code url} into all
+ * enabled caches. This version of the method should be preferred as it
+ * allows the original image contents to be cached, rather than a
+ * re-compressed version.
+ * <p />
+ * The contents of the InputStream will be copied to a temporary file, then
+ * the file will be decoded into a Bitmap, using the optional
+ * <code>decodeOpts</code>. Providing the decode worked:
+ * <ul>
+ * <li>If the memory cache is enabled, the decoded Bitmap will be cached to
+ * memory.</li>
+ * <li>If the disk cache is enabled, the contents of the original stream
+ * will be cached to disk.</li>
+ * </ul>
* <p/>
- * As this method may write to the file system, this method is not safe to
- * be called from the main thread.
+ * You should not call this method from the main/UI thread.
*
* @param url - String representing the URL of the image
* @param inputStream - InputStream opened from {@code url}
- * @param cacheToDiskIfEnabled - Cache to disk, if the disk cache is
- * enabled.
- * @return CacheableBitmapWrapper which can be used to display the bitmap.
+ * @param decodeOpts - Options used for decoding. This does not affect what
+ * is cached in the disk cache (if enabled).
+ * @return CacheableBitmapDrawable which can be used to display the bitmap.
*/
- public CacheableBitmapWrapper put(final String url, final InputStream inputStream,
- final boolean cacheToDiskIfEnabled) {
+ public CacheableBitmapDrawable put(final String url, final InputStream inputStream,
+ final BitmapFactory.Options decodeOpts) {
+ checkNotOnMainThread();
+
// First we need to save the stream contents to a temporary file, so it
// can be read multiple times
File tmpFile = null;
@@ -312,21 +377,21 @@ public CacheableBitmapWrapper put(final String url, final InputStream inputStrea
e.printStackTrace();
}
- CacheableBitmapWrapper wrapper = null;
+ CacheableBitmapDrawable d = null;
if (null != tmpFile) {
// Try and decode File
- Bitmap bitmap = BitmapFactory.decodeFile(tmpFile.getAbsolutePath());
+ Bitmap bitmap = BitmapFactory.decodeFile(tmpFile.getAbsolutePath(), decodeOpts);
if (null != bitmap) {
- wrapper = new CacheableBitmapWrapper(url, bitmap);
+ d = new CacheableBitmapDrawable(url, bitmap);
if (null != mMemoryCache) {
- wrapper.setCached(true);
- mMemoryCache.put(wrapper.getUrl(), wrapper);
+ d.setCached(true);
+ mMemoryCache.put(d.getUrl(), d);
}
- if (null != mDiskCache && cacheToDiskIfEnabled) {
+ if (null != mDiskCache) {
final ReentrantLock lock = getLockForDiskCacheEdit(url);
lock.lock();
try {
@@ -346,11 +411,14 @@ public CacheableBitmapWrapper put(final String url, final InputStream inputStrea
tmpFile.delete();
}
- return wrapper;
+ return d;
}
/**
* Removes the entry for {@code url} from all enabled caches, if it exists.
+ * <p/>
+ * If you have the disk cache enabled, you should not call this method from
+ * main/UI thread.
*/
public void remove(String url) {
if (null != mMemoryCache) {
@@ -358,6 +426,8 @@ public void remove(String url) {
}
if (null != mDiskCache) {
+ checkNotOnMainThread();
+
try {
mDiskCache.remove(transformUrlForDiskCacheKey(url));
scheduleDiskCacheFlush();
@@ -379,6 +449,16 @@ public void trimMemory() {
}
}
+ synchronized void setDiskCache(DiskLruCache diskCache) {
+ mDiskCache = diskCache;
+
+ if (null != diskCache) {
+ mDiskCacheEditLocks = new HashMap<String, ReentrantLock>();
+ mDiskCacheFlusherExecutor = new ScheduledThreadPoolExecutor(1);
+ mDiskCacheFlusherRunnable = new DiskCacheFlushRunnable(diskCache);
+ }
+ }
+
private ReentrantLock getLockForDiskCacheEdit(String url) {
synchronized (mDiskCacheEditLocks) {
ReentrantLock lock = mDiskCacheEditLocks.get(url);
@@ -390,11 +470,6 @@ private ReentrantLock getLockForDiskCacheEdit(String url) {
}
}
- private void putIntoMemoryCache(CacheableBitmapWrapper wrapper) {
- wrapper.setCached(true);
- mMemoryCache.put(wrapper.getUrl(), wrapper);
- }
-
private void scheduleDiskCacheFlush() {
// If we already have a flush scheduled, cancel it
if (null != mDiskCacheFuture) {
@@ -459,7 +534,6 @@ public Builder() {
*/
public BitmapLruCache build() {
BitmapMemoryLruCache memoryCache = null;
- DiskLruCache diskCache = null;
if (isValidOptionsForMemoryCache()) {
if (Constants.DEBUG) {
@@ -468,18 +542,30 @@ public BitmapLruCache build() {
memoryCache = new BitmapMemoryLruCache(mMemoryCacheMaxSize);
}
+ final BitmapLruCache cache = new BitmapLruCache(memoryCache);
+
if (isValidOptionsForDiskCache()) {
- try {
- if (Constants.DEBUG) {
- Log.d("BitmapLruCache.Builder", "Creating Disk Cache");
+ new AsyncTask<Void, Void, DiskLruCache>() {
+
+ @Override
+ protected DiskLruCache doInBackground(Void... params) {
+ try {
+ return DiskLruCache.open(mDiskCacheLocation, 0, 1, mDiskCacheMaxSize);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
}
- diskCache = DiskLruCache.open(mDiskCacheLocation, 0, 1, mDiskCacheMaxSize);
- } catch (IOException e) {
- e.printStackTrace();
- }
+
+ @Override
+ protected void onPostExecute(DiskLruCache result) {
+ cache.setDiskCache(result);
+ }
+
+ }.execute();
}
- return new BitmapLruCache(memoryCache, diskCache);
+ return cache;
}
/**
View
31 library/src/uk/co/senab/bitmapcache/BitmapMemoryLruCache.java
@@ -16,35 +16,42 @@
package uk.co.senab.bitmapcache;
import java.util.Map.Entry;
+import java.util.Set;
-import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
-final class BitmapMemoryLruCache extends LruCache<String, CacheableBitmapWrapper> {
+final class BitmapMemoryLruCache extends LruCache<String, CacheableBitmapDrawable> {
BitmapMemoryLruCache(int maxSize) {
super(maxSize);
}
- @Override
- protected int sizeOf(String key, CacheableBitmapWrapper value) {
- if (value.hasValidBitmap()) {
- Bitmap bitmap = value.getBitmap();
- return bitmap.getRowBytes() * bitmap.getHeight();
+ CacheableBitmapDrawable put(CacheableBitmapDrawable value) {
+ if (null != value) {
+ value.setCached(true);
+ return put(value.getUrl(), value);
}
- return 0;
+
+ return null;
+ }
+
+ @Override
+ protected int sizeOf(String key, CacheableBitmapDrawable value) {
+ return value.getMemorySize();
}
@Override
- protected void entryRemoved(boolean evicted, String key, CacheableBitmapWrapper oldValue,
- CacheableBitmapWrapper newValue) {
+ protected void entryRemoved(boolean evicted, String key, CacheableBitmapDrawable oldValue,
+ CacheableBitmapDrawable newValue) {
// Notify the wrapper that it's no longer being cached
oldValue.setCached(false);
}
void trimMemory() {
- for (Entry<String, CacheableBitmapWrapper> entry : snapshot().entrySet()) {
- CacheableBitmapWrapper value = entry.getValue();
+ final Set<Entry<String, CacheableBitmapDrawable>> values = snapshot().entrySet();
+
+ for (Entry<String, CacheableBitmapDrawable> entry : values) {
+ CacheableBitmapDrawable value = entry.getValue();
if (null == value || !value.isBeingDisplayed()) {
remove(entry.getKey());
}
View
193 library/src/uk/co/senab/bitmapcache/CacheableBitmapDrawable.java
@@ -0,0 +1,193 @@
+package uk.co.senab.bitmapcache;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+public class CacheableBitmapDrawable extends BitmapDrawable {
+
+ static final String LOG_TAG = "CacheableBitmapDrawable";
+
+ private final String mUrl;
+
+ // Number of Views currently displaying bitmap
+ private int mDisplayingCount;
+
+ // Has it been displayed yet
+ private boolean mHasBeenDisplayed;
+
+ // Number of caches currently referencing the wrapper
+ private int mCacheCount;
+
+ // Handler which may be used later
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ public CacheableBitmapDrawable(Bitmap bitmap) {
+ this(null, bitmap);
+ }
+
+ @SuppressWarnings("deprecation")
+ public CacheableBitmapDrawable(String url, Bitmap bitmap) {
+ super(bitmap);
+
+ mUrl = url;
+ mDisplayingCount = 0;
+ mCacheCount = 0;
+ }
+
+ /**
+ * @return Amount of heap size currently being used by {@code Bitmap}
+ */
+ int getMemorySize() {
+ int size = 0;
+
+ final Bitmap bitmap = getBitmap();
+ if (null != bitmap && !bitmap.isRecycled()) {
+ size = bitmap.getRowBytes() * bitmap.getHeight();
+ }
+
+ return size;
+ }
+
+ /**
+ * @return the URL associated with the BitmapDrawable
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Returns true when this wrapper has a bitmap and the bitmap has not been
+ * recycled.
+ *
+ * @return true - if the bitmap has not been recycled.
+ */
+ public synchronized boolean hasValidBitmap() {
+ Bitmap bitmap = getBitmap();
+ if (null != bitmap) {
+ return !bitmap.isRecycled();
+ }
+ return false;
+ }
+
+ /**
+ * @return true - if the bitmap is currently being displayed by a
+ * {@link CacheableImageView}.
+ */
+ public synchronized boolean isBeingDisplayed() {
+ return mDisplayingCount > 0;
+ }
+
+ /**
+ * @return true - if the wrapper is currently referenced by a cache.
+ */
+ public synchronized boolean isReferencedByCache() {
+ return mCacheCount > 0;
+ }
+
+ /**
+ * Used to signal to the Drawable whether it is being used or not.
+ *
+ * @param beingUsed - true if being used, false if not.
+ */
+ public synchronized void setBeingUsed(boolean beingUsed) {
+ if (beingUsed) {
+ mDisplayingCount++;
+ mHasBeenDisplayed = true;
+ } else {
+ mDisplayingCount--;
+ }
+ checkState();
+ }
+
+ /**
+ * Used to signal to the wrapper whether it is being referenced by a cache
+ * or not.
+ *
+ * @param added - true if the wrapper has been added to a cache, false if
+ * removed.
+ */
+ synchronized void setCached(boolean added) {
+ if (added) {
+ mCacheCount++;
+ } else {
+ mCacheCount--;
+ }
+ checkState();
+ }
+
+ /**
+ * Calls {@link #checkState(boolean)} with default parameter of
+ * <code>false</code>.
+ */
+ private void checkState() {
+ checkState(false);
+ }
+
+ /**
+ * Checks whether the wrapper is currently referenced by a cache, and is
+ * being displayed. If neither of those conditions are met then the bitmap
+ * is ready to be recycled. Whether this happens now, or is delayed depends
+ * on whether the Drawable has been displayed or not.
+ * <ul>
+ * <li>If it has been displayed, it is recycled straight away.</li>
+ * <li>If it has not been displayed, and <code>ignoreBeenDisplayed</code> is
+ * <code>false</code>, a call to <code>checkState(true)</code> is queued to
+ * be called after a delay.</li>
+ * <li>If it has not been displayed, and <code>ignoreBeenDisplayed</code> is
+ * <code>true</code>, it is recycled straight away.</li>
+ * </ul>
+ *
+ * @see Constants#UNUSED_DRAWABLE_RECYCLE_DELAY_MS
+ *
+ * @param ignoreBeenDisplayed - Whether to ignore the 'has been displayed'
+ * flag when deciding whether to recycle() now.
+ */
+ private synchronized void checkState(final boolean ignoreBeenDisplayed) {
+ if (Constants.DEBUG) {
+ Log.d(LOG_TAG, String.format("checkState(). Been Displayed: %b, Displaying: %d, Caching: %d, URL: %s",
+ mHasBeenDisplayed, mDisplayingCount, mCacheCount, mUrl));
+ }
+
+ // We only want to recycle if it has been displayed.
+ if (mCacheCount <= 0 && mDisplayingCount <= 0 && hasValidBitmap()) {
+
+ /**
+ * If we have been displayed or we don't care whether we have been
+ * or not, then recycle() now. Otherwise, we retry in 1 second.
+ */
+ if (mHasBeenDisplayed || ignoreBeenDisplayed) {
+ if (Constants.DEBUG) {
+ Log.d(LOG_TAG, "Recycling bitmap with url: " + mUrl);
+ }
+ getBitmap().recycle();
+ } else {
+ if (Constants.DEBUG) {
+ Log.d(LOG_TAG, "Unused Bitmap which hasn't been displayed, delaying recycle(): " + mUrl);
+ }
+ sHandler.postDelayed(new CheckStateRunnable(this), Constants.UNUSED_DRAWABLE_RECYCLE_DELAY_MS);
+ }
+ }
+ }
+
+ /**
+ * Runnable which run a {@link CacheableBitmapDrawable#checkState(boolean)
+ * checkState(false)} call.
+ *
+ * @author chrisbanes
+ */
+ private static final class CheckStateRunnable extends WeakReferenceRunnable<CacheableBitmapDrawable> {
+
+ public CheckStateRunnable(CacheableBitmapDrawable object) {
+ super(object);
+ }
+
+ @Override
+ public void run(CacheableBitmapDrawable object) {
+ object.checkState(true);
+ }
+ }
+
+}
View
128 library/src/uk/co/senab/bitmapcache/CacheableBitmapWrapper.java
@@ -1,130 +1,18 @@
-/*******************************************************************************
- * Copyright 2011, 2012 Chris Banes.
- *
- * 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 uk.co.senab.bitmapcache;
import android.graphics.Bitmap;
-import android.util.Log;
-public class CacheableBitmapWrapper {
+/**
+ * @deprecated - You should use {@link CacheableBitmapDrawable} instead.
+ */
+public class CacheableBitmapWrapper extends CacheableBitmapDrawable {
- private final String mUrl;
- private final Bitmap mBitmap;
-
- // Number of ImageViews currently showing bitmap
- private int mImageViewsCount;
-
- // Number of caches currently referencing the wrapper
- private int mCacheCount;
-
- CacheableBitmapWrapper(Bitmap bitmap) {
- this(null, bitmap);
- }
-
- CacheableBitmapWrapper(String url, Bitmap bitmap) {
- if (null == bitmap) {
- throw new IllegalArgumentException("Bitmap can not be null");
- }
-
- mBitmap = bitmap;
- mUrl = url;
- mImageViewsCount = 0;
- mCacheCount = 0;
- }
-
- /**
- * @return true - if the wrapper is currently referenced by a cache.
- */
- public boolean isReferencedByCache() {
- return mCacheCount > 0;
- }
-
- /**
- * @return true - if the bitmap is currently being displayed by a
- * {@link CacheableImageView}.
- */
- public boolean isBeingDisplayed() {
- return mImageViewsCount > 0;
- }
-
- /**
- * Returns the currently reference Bitmap
- *
- * @return Bitmap - referenced Bitmaps
- */
- public Bitmap getBitmap() {
- return mBitmap;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * Returns true when this wrapper has a bitmap and the bitmap has not been
- * recycled.
- *
- * @return true - if the bitmap has not been recycled.
- */
- public boolean hasValidBitmap() {
- return !mBitmap.isRecycled();
- }
-
- /**
- * Used to signal to the wrapper whether it is being referenced by a cache
- * or not.
- *
- * @param added - true if the wrapper has been added to a cache, false if
- * removed.
- */
- void setCached(boolean added) {
- if (added) {
- mCacheCount++;
- } else {
- mCacheCount--;
- }
- checkState();
- }
-
- /**
- * Used to signal to the wrapper whether it is being used or not. Being used
- * could be that it is being displayed by an ImageView.
- *
- * @param beingUsed - true if being used, false if not.
- */
- public void setBeingUsed(boolean beingUsed) {
- if (beingUsed) {
- mImageViewsCount++;
- } else {
- mImageViewsCount--;
- }
- checkState();
+ public CacheableBitmapWrapper(Bitmap bitmap) {
+ super(bitmap);
}
- /**
- * Checks whether the wrapper is currently referenced, and is being
- * displayed. If neither of those conditions are met then the bitmap is
- * recycled and freed.
- */
- private void checkState() {
- if (mCacheCount <= 0 && mImageViewsCount <= 0 && hasValidBitmap()) {
- if (Constants.DEBUG) {
- Log.d(Constants.LOG_TAG, "Recycling bitmap with url: " + mUrl);
- }
- mBitmap.recycle();
- }
+ public CacheableBitmapWrapper(String url, Bitmap bitmap) {
+ super(url, bitmap);
}
}
View
67 library/src/uk/co/senab/bitmapcache/CacheableImageView.java
@@ -16,15 +16,24 @@
package uk.co.senab.bitmapcache;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;
public class CacheableImageView extends ImageView {
- private CacheableBitmapWrapper mDisplayedBitmapWrapper;
+ private static void onDrawableSet(Drawable drawable) {
+ if (drawable instanceof CacheableBitmapDrawable) {
+ ((CacheableBitmapDrawable) drawable).setBeingUsed(true);
+ }
+ }
+
+ private static void onDrawableUnset(final Drawable drawable) {
+ if (drawable instanceof CacheableBitmapDrawable) {
+ ((CacheableBitmapDrawable) drawable).setBeingUsed(false);
+ }
+ }
public CacheableImageView(Context context) {
super(context);
@@ -34,42 +43,31 @@ public CacheableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
- /**
- * Sets the current {@code CacheableBitmapWrapper}, and displays it Bitmap.
- *
- * @param wrapper - Wrapper to display.s
- */
- public void setImageCachedBitmap(final CacheableBitmapWrapper wrapper) {
- if (null != wrapper) {
- wrapper.setBeingUsed(true);
- setImageDrawable(new BitmapDrawable(getResources(), wrapper.getBitmap()));
- } else {
- setImageDrawable(null);
- }
-
- // Finally, set our new BitmapWrapper
- mDisplayedBitmapWrapper = wrapper;
- }
-
- @Override
- public void setImageBitmap(Bitmap bm) {
- setImageCachedBitmap(new CacheableBitmapWrapper(bm));
- }
-
@Override
public void setImageDrawable(Drawable drawable) {
+ final Drawable previousDrawable = getDrawable();
+
+ // Set new Drawable
super.setImageDrawable(drawable);
- resetCachedDrawable();
+
+ if (drawable != previousDrawable) {
+ onDrawableSet(drawable);
+ onDrawableUnset(previousDrawable);
+ }
}
@Override
public void setImageResource(int resId) {
+ final Drawable previousDrawable = getDrawable();
super.setImageResource(resId);
- resetCachedDrawable();
+ onDrawableUnset(previousDrawable);
}
- public CacheableBitmapWrapper getCachedBitmapWrapper() {
- return mDisplayedBitmapWrapper;
+ @Override
+ public void setImageURI(Uri uri) {
+ final Drawable previousDrawable = getDrawable();
+ super.setImageURI(uri);
+ onDrawableUnset(previousDrawable);
}
@Override
@@ -80,15 +78,4 @@ protected void onDetachedFromWindow() {
setImageDrawable(null);
}
- /**
- * Called when the current cached bitmap has been removed. This method will
- * remove the displayed flag and remove this objects reference to it.
- */
- private void resetCachedDrawable() {
- if (null != mDisplayedBitmapWrapper) {
- mDisplayedBitmapWrapper.setBeingUsed(false);
- mDisplayedBitmapWrapper = null;
- }
- }
-
}
View
8 library/src/uk/co/senab/bitmapcache/Constants.java
@@ -15,10 +15,12 @@
*******************************************************************************/
package uk.co.senab.bitmapcache;
-public class Constants {
+class Constants {
- public static boolean DEBUG = false;
+ static boolean DEBUG = false;
- public static String LOG_TAG = "BitmapCache";
+ static String LOG_TAG = "BitmapCache";
+
+ static final int UNUSED_DRAWABLE_RECYCLE_DELAY_MS = 1000;
}
View
2  library/src/uk/co/senab/bitmapcache/Md5.java
@@ -3,7 +3,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-public class Md5 {
+class Md5 {
private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
View
24 library/src/uk/co/senab/bitmapcache/WeakReferenceRunnable.java
@@ -0,0 +1,24 @@
+package uk.co.senab.bitmapcache;
+
+import java.lang.ref.WeakReference;
+
+abstract class WeakReferenceRunnable<T> implements Runnable {
+
+ private final WeakReference<T> mObjectRef;
+
+ public WeakReferenceRunnable(T object) {
+ mObjectRef = new WeakReference<T>(object);
+ }
+
+ @Override
+ public final void run() {
+ T object = mObjectRef.get();
+
+ if (null != object) {
+ run(object);
+ }
+ }
+
+ public abstract void run(T object);
+
+}
View
4 pom.xml
@@ -5,7 +5,7 @@
<groupId>com.github.chrisbanes.bitmapcache</groupId>
<artifactId>parent</artifactId>
<packaging>pom</packaging>
- <version>2.0</version>
+ <version>2.1</version>
<name>Android-BitmapCache Project</name>
<description>A custom Bitmap cache implementation for Android</description>
<url>https://github.com/chrisbanes/Android-BitmapCache</url>
@@ -28,7 +28,7 @@
<url>https://github.com/chrisbanes/Android-BitmapCache</url>
<connection>scm:git:git://github.com/chrisbanes/Android-BitmapCache.git</connection>
<developerConnection>scm:git:git@github.com:chrisbanes/Android-BitmapCache.git</developerConnection>
- <tag>v2.0</tag>
+ <tag>v2.1</tag>
</scm>
<developers>
View
4 sample/AndroidManifest.xml
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.senab.bitmapcache.samples"
- android:versionCode="2000"
- android:versionName="2.0" >
+ android:versionCode="2100"
+ android:versionName="2.1" >
<uses-sdk
android:minSdkVersion="4"
View
2  sample/pom.xml
@@ -10,7 +10,7 @@
<parent>
<groupId>com.github.chrisbanes.bitmapcache</groupId>
<artifactId>parent</artifactId>
- <version>2.0</version>
+ <version>2.1</version>
</parent>
<dependencies>
View
2  sample/project.properties
@@ -11,5 +11,5 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-8
+target=android-16
android.library.reference.1=../library
View
5 sample/src/uk/co/senab/bitmapcache/samples/GridViewActivity.java
@@ -23,6 +23,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.HashSet;
import org.json.JSONArray;
import org.json.JSONException;
@@ -65,13 +66,13 @@
JSONObject document = new JSONObject(response);
JSONArray pugsJsonArray = document.getJSONArray("pugs");
- ArrayList<String> pugUrls = new ArrayList<String>(pugsJsonArray.length());
+ HashSet<String> pugUrls = new HashSet<String>(pugsJsonArray.length());
for (int i = 0, z = pugsJsonArray.length(); i < z; i++) {
pugUrls.add(pugsJsonArray.getString(i));
}
- return pugUrls;
+ return new ArrayList<String>(pugUrls);
} catch (MalformedURLException e) {
e.printStackTrace();
View
80 sample/src/uk/co/senab/bitmapcache/samples/NetworkedCacheableImageView.java
@@ -18,16 +18,22 @@
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
import java.net.URL;
import uk.co.senab.bitmapcache.BitmapLruCache;
-import uk.co.senab.bitmapcache.CacheableBitmapWrapper;
+import uk.co.senab.bitmapcache.CacheableBitmapDrawable;
import uk.co.senab.bitmapcache.CacheableImageView;
import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
+import android.os.Build;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.ImageView;
/**
* Simple extension of CacheableImageView which allows downloading of Images of
@@ -45,42 +51,61 @@
* a wrapper. This implementation is NOT 'best practice' or production ready
* code.
*/
- private class ImageUrlAsyncTask extends AsyncTask<String, Void, CacheableBitmapWrapper> {
+ private static class ImageUrlAsyncTask extends AsyncTask<String, Void, CacheableBitmapDrawable> {
+
+ private final BitmapLruCache mCache;
+ private final WeakReference<ImageView> mImageViewRef;
+ private final BitmapFactory.Options mDecodeOpts;
+
+ ImageUrlAsyncTask(ImageView imageView, BitmapLruCache cache, BitmapFactory.Options decodeOpts) {
+ mCache = cache;
+ mImageViewRef = new WeakReference<ImageView>(imageView);
+ mDecodeOpts = decodeOpts;
+ }
@Override
- protected CacheableBitmapWrapper doInBackground(String... params) {
+ protected CacheableBitmapDrawable doInBackground(String... params) {
try {
- String url = params[0];
+ // Return early if the ImageView has disappeared.
+ if (null == mImageViewRef.get()) {
+ return null;
+ }
+
+ final String url = params[0];
// Now we're not on the main thread we can check all caches
- CacheableBitmapWrapper result = mCache.get(url);
+ CacheableBitmapDrawable result = mCache.get(url, mDecodeOpts);
if (null == result) {
+ Log.d("ImageUrlAsyncTask", "Downloading: " + url);
+
// The bitmap isn't cached so download from the web
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
InputStream is = new BufferedInputStream(conn.getInputStream());
// Add to cache
- result = mCache.put(url, is);
+ result = mCache.put(url, is, mDecodeOpts);
+ } else {
+ Log.d("ImageUrlAsyncTask", "Got from Cache: " + url);
}
return result;
- } catch (MalformedURLException e) {
- e.printStackTrace();
} catch (IOException e) {
- e.printStackTrace();
+ Log.e("ImageUrlAsyncTask", e.toString());
}
return null;
}
@Override
- protected void onPostExecute(CacheableBitmapWrapper result) {
+ protected void onPostExecute(CacheableBitmapDrawable result) {
super.onPostExecute(result);
- // Display the image
- setImageCachedBitmap(result);
+ ImageView iv = mImageViewRef.get();
+ if (null != iv) {
+ iv.setImageDrawable(result);
+ }
}
}
@@ -92,10 +117,6 @@ public NetworkedCacheableImageView(Context context, AttributeSet attrs) {
mCache = SampleApplication.getApplication(context).getBitmapCache();
}
- public boolean loadImage(String url) {
- return loadImage(url, true);
- }
-
/**
* Loads the Bitmap.
*
@@ -112,18 +133,31 @@ public boolean loadImage(String url, final boolean fullSize) {
// Check to see if the memory cache already has the bitmap. We can
// safely do
// this on the main thread.
- CacheableBitmapWrapper wrapper = mCache.getFromMemoryCache(url);
+ BitmapDrawable wrapper = mCache.getFromMemoryCache(url);
- if (null != wrapper && wrapper.hasValidBitmap()) {
+ if (null != wrapper) {
// The cache has it, so just display it
- setImageCachedBitmap(wrapper);
+ setImageDrawable(wrapper);
return true;
} else {
// Memory Cache doesn't have the URL, do threaded request...
- setImageCachedBitmap(null);
+ setImageDrawable(null);
+
+ BitmapFactory.Options decodeOpts = null;
+
+ if (!fullSize) {
+ decodeOpts = new BitmapFactory.Options();
+ decodeOpts.inDensity = DisplayMetrics.DENSITY_XHIGH;
+ }
+
+ mCurrentTask = new ImageUrlAsyncTask(this, mCache, decodeOpts);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ SDK11.executeOnThreadPool(mCurrentTask, url);
+ } else {
+ mCurrentTask.execute(url);
+ }
- mCurrentTask = new ImageUrlAsyncTask();
- mCurrentTask.execute(url);
return false;
}
}
View
2  sample/src/uk/co/senab/bitmapcache/samples/PugPagerAdapter.java
@@ -44,7 +44,7 @@ public View instantiateItem(ViewGroup container, int position) {
NetworkedCacheableImageView imageView = new NetworkedCacheableImageView(mContext, null);
String pugUrl = mPugUrls.get(position);
- imageView.loadImage(pugUrl);
+ imageView.loadImage(pugUrl, true);
imageView.setScaleType(ScaleType.FIT_CENTER);
container.addView(imageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
View
14 sample/src/uk/co/senab/bitmapcache/samples/SDK11.java
@@ -0,0 +1,14 @@
+package uk.co.senab.bitmapcache.samples;
+
+import android.annotation.TargetApi;
+import android.os.AsyncTask;
+import android.os.Build;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class SDK11 {
+
+ public static <P> void executeOnThreadPool(AsyncTask<P, ?, ?> task, P... params) {
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
+ }
+
+}
View
5 sample/src/uk/co/senab/bitmapcache/samples/ViewPagerActivity.java
@@ -23,6 +23,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.HashSet;
import org.json.JSONArray;
import org.json.JSONException;
@@ -65,13 +66,13 @@
JSONObject document = new JSONObject(response);
JSONArray pugsJsonArray = document.getJSONArray("pugs");
- ArrayList<String> pugUrls = new ArrayList<String>(pugsJsonArray.length());
+ HashSet<String> pugUrls = new HashSet<String>(pugsJsonArray.length());
for (int i = 0, z = pugsJsonArray.length(); i < z; i++) {
pugUrls.add(pugsJsonArray.getString(i));
}
- return pugUrls;
+ return new ArrayList<String>(pugUrls);
} catch (MalformedURLException e) {
e.printStackTrace();
Please sign in to comment.
Something went wrong with that request. Please try again.