Skip to content
This repository was archived by the owner on Oct 15, 2018. It is now read-only.

Commit b373472

Browse files
author
Chris Banes
committed
Change default RecyclePolicy to PRE_HONEYCOMB, and enable inBitmap use when recycling is not enabled.
1 parent b75c98c commit b373472

File tree

6 files changed

+242
-48
lines changed

6 files changed

+242
-48
lines changed

library/src/uk/co/senab/bitmapcache/BitmapLruCache.java

Lines changed: 127 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import android.util.Log;
3030

3131
import java.io.File;
32+
import java.io.FileInputStream;
33+
import java.io.FileNotFoundException;
3234
import java.io.IOException;
3335
import java.io.InputStream;
3436
import java.io.OutputStream;
@@ -45,7 +47,7 @@
4547
*
4648
* <p> Instances of this class should ideally be kept globally with the application, for example in
4749
* the {@link android.app.Application Application} object. You should also use the bundled {@link
48-
* CacheableImageView} wherever possible, as the memory cache has a close relationship with it.
50+
* CacheableImageView} wherever possible, as the memory cache has a closeStream relationship with it.
4951
* </p>
5052
*
5153
* <p> Clients can call {@link #get(String)} to retrieve a cached value from the given Url. This
@@ -84,6 +86,15 @@ public static enum RecyclePolicy {
8486
*/
8587
ALWAYS;
8688

89+
boolean canInBitmap() {
90+
switch (this) {
91+
case PRE_HONEYCOMB_ONLY:
92+
case DISABLED:
93+
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
94+
}
95+
return false;
96+
}
97+
8798
boolean canRecycle() {
8899
switch (this) {
89100
case DISABLED:
@@ -257,24 +268,20 @@ public CacheableBitmapDrawable getFromDiskCache(final String url,
257268

258269
try {
259270
final String key = transformUrlForDiskCacheKey(url);
260-
DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
261-
if (null != snapshot) {
262-
// Try and decode bitmap
263-
Bitmap bitmap = BitmapFactory
264-
.decodeStream(snapshot.getInputStream(0), null, decodeOpts);
265-
266-
if (null != bitmap) {
267-
result = new CacheableBitmapDrawable(url, mResources, bitmap,
268-
mRecyclePolicy);
269-
if (null != mMemoryCache) {
270-
mMemoryCache.put(result);
271-
}
272-
} else {
273-
// If we get here, the file in the cache can't be
274-
// decoded. Remove it and schedule a flush.
275-
mDiskCache.remove(key);
276-
scheduleDiskCacheFlush();
271+
// Try and decode bitmap
272+
Bitmap bitmap = decodeBitmap(new SnapshotInputStreamProvider(key), decodeOpts);
273+
274+
if (null != bitmap) {
275+
result = new CacheableBitmapDrawable(url, mResources, bitmap,
276+
mRecyclePolicy);
277+
if (null != mMemoryCache) {
278+
mMemoryCache.put(result);
277279
}
280+
} else {
281+
// If we get here, the file in the cache can't be
282+
// decoded. Remove it and schedule a flush.
283+
mDiskCache.remove(key);
284+
scheduleDiskCacheFlush();
278285
}
279286
} catch (IOException e) {
280287
e.printStackTrace();
@@ -301,7 +308,7 @@ public CacheableBitmapDrawable getFromMemoryCache(final String url) {
301308
result = mMemoryCache.get(url);
302309

303310
// If we get a value, but it has a invalid bitmap, remove it
304-
if (null != result && !result.hasValidBitmap()) {
311+
if (null != result && !result.isBitmapValid()) {
305312
mMemoryCache.remove(url);
306313
result = null;
307314
}
@@ -379,14 +386,7 @@ public CacheableBitmapDrawable put(final String url, final Bitmap bitmap,
379386
} catch (IOException e) {
380387
Log.e(Constants.LOG_TAG, "Error while writing to disk cache", e);
381388
} finally {
382-
if (null != os) {
383-
try {
384-
os.close();
385-
} catch (IOException e) {
386-
Log.e(Constants.LOG_TAG, "Failed to close output stream", e);
387-
}
388-
}
389-
389+
IoUtils.closeStream(os);
390390
lock.unlock();
391391
scheduleDiskCacheFlush();
392392
}
@@ -448,7 +448,7 @@ public CacheableBitmapDrawable put(final String url, final InputStream inputStre
448448

449449
if (null != tmpFile) {
450450
// Try and decode File
451-
Bitmap bitmap = BitmapFactory.decodeFile(tmpFile.getAbsolutePath(), decodeOpts);
451+
Bitmap bitmap = decodeBitmap(new FileInputStreamProvider(tmpFile), decodeOpts);
452452

453453
if (null != bitmap) {
454454
d = new CacheableBitmapDrawable(url, mResources, bitmap, mRecyclePolicy);
@@ -525,9 +525,9 @@ synchronized void setDiskCache(DiskLruCache diskCache) {
525525
}
526526
}
527527

528-
void setMemoryCache(BitmapMemoryLruCache memoryCache, RecyclePolicy recyclePolicy) {
528+
void setMemoryCache(BitmapMemoryLruCache memoryCache) {
529529
mMemoryCache = memoryCache;
530-
mRecyclePolicy = recyclePolicy;
530+
mRecyclePolicy = memoryCache.getRecyclePolicy();
531531
}
532532

533533
private ReentrantLock getLockForDiskCacheEdit(String url) {
@@ -553,6 +553,58 @@ private void scheduleDiskCacheFlush() {
553553
TimeUnit.SECONDS);
554554
}
555555

556+
private Bitmap decodeBitmap(InputStreamProvider ip, BitmapFactory.Options opts) {
557+
Bitmap bm = null;
558+
InputStream is = null;
559+
560+
try {
561+
if (mRecyclePolicy.canInBitmap()) {
562+
// Create an options instance if we haven't been provided with one
563+
if (opts == null) {
564+
opts = new BitmapFactory.Options();
565+
}
566+
567+
if (opts.inSampleSize <= 1) {
568+
opts.inSampleSize = 1;
569+
addInBitmapOptions(ip, opts);
570+
}
571+
}
572+
573+
// Get InputStream for actual decode
574+
is = ip.getInputStream();
575+
// Decode stream
576+
bm = BitmapFactory.decodeStream(is, null, opts);
577+
} catch (Exception e) {
578+
Log.e(Constants.LOG_TAG, "Unable to decode stream", e);
579+
} finally {
580+
IoUtils.closeStream(is);
581+
}
582+
return bm;
583+
}
584+
585+
private void addInBitmapOptions(InputStreamProvider ip, BitmapFactory.Options opts) {
586+
// Create InputStream for decoding the bounds
587+
final InputStream is = ip.getInputStream();
588+
// Decode the bounds so we know what size Bitmap to look for
589+
opts.inJustDecodeBounds = true;
590+
BitmapFactory.decodeStream(is, null, opts);
591+
IoUtils.closeStream(is);
592+
593+
// Turn off just decoding bounds
594+
opts.inJustDecodeBounds = false;
595+
// Make sure the decoded file is mutable
596+
opts.inMutable = true;
597+
598+
// Try and find Bitmap to use for inBitmap
599+
Bitmap reusableBm = mMemoryCache.getBitmapFromRemoved(opts.outWidth, opts.outHeight);
600+
if (reusableBm != null) {
601+
if (Constants.DEBUG) {
602+
Log.i(Constants.LOG_TAG, "Using inBitmap");
603+
}
604+
SDK11.addInBitmapOption(opts, reusableBm);
605+
}
606+
}
607+
556608
/**
557609
* Builder class for {link {@link BitmapLruCache}. An example call:
558610
*
@@ -578,7 +630,7 @@ public final static class Builder {
578630

579631
static final int DEFAULT_MEM_CACHE_MAX_SIZE_MB = 3;
580632

581-
static final RecyclePolicy DEFAULT_RECYCLE_POLICY = RecyclePolicy.ALWAYS;
633+
static final RecyclePolicy DEFAULT_RECYCLE_POLICY = RecyclePolicy.PRE_HONEYCOMB_ONLY;
582634

583635
// Only used for Javadoc
584636
static final float DEFAULT_MEMORY_CACHE_HEAP_PERCENTAGE = DEFAULT_MEMORY_CACHE_HEAP_RATIO
@@ -635,7 +687,7 @@ public BitmapLruCache build() {
635687
if (Constants.DEBUG) {
636688
Log.d("BitmapLruCache.Builder", "Creating Memory Cache");
637689
}
638-
cache.setMemoryCache(new BitmapMemoryLruCache(mMemoryCacheMaxSize), mRecyclePolicy);
690+
cache.setMemoryCache(new BitmapMemoryLruCache(mMemoryCacheMaxSize, mRecyclePolicy));
639691
}
640692

641693
if (isValidOptionsForDiskCache()) {
@@ -799,4 +851,47 @@ public void run() {
799851
}
800852
}
801853
}
854+
855+
interface InputStreamProvider {
856+
InputStream getInputStream();
857+
}
858+
859+
static class FileInputStreamProvider implements InputStreamProvider {
860+
final File mFile;
861+
862+
FileInputStreamProvider(File file) {
863+
mFile = file;
864+
}
865+
866+
@Override
867+
public InputStream getInputStream() {
868+
try {
869+
return new FileInputStream(mFile);
870+
} catch (FileNotFoundException e) {
871+
Log.e(Constants.LOG_TAG, "Could not decode file: " + mFile.getAbsolutePath(), e);
872+
}
873+
return null;
874+
}
875+
}
876+
877+
final class SnapshotInputStreamProvider implements InputStreamProvider {
878+
final String mKey;
879+
880+
SnapshotInputStreamProvider(String key) {
881+
mKey = key;
882+
}
883+
884+
@Override
885+
public InputStream getInputStream() {
886+
try {
887+
DiskLruCache.Snapshot snapshot = mDiskCache.get(mKey);
888+
if (snapshot != null) {
889+
return snapshot.getInputStream(0);
890+
}
891+
} catch (IOException e) {
892+
Log.e(Constants.LOG_TAG, "Could open disk cache for url: " + mKey, e);
893+
}
894+
return null;
895+
}
896+
}
802897
}

library/src/uk/co/senab/bitmapcache/BitmapMemoryLruCache.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,28 @@
1515
******************************************************************************/
1616
package uk.co.senab.bitmapcache;
1717

18+
import android.graphics.Bitmap;
1819
import android.support.v4.util.LruCache;
1920

21+
import java.lang.ref.SoftReference;
22+
import java.util.Collections;
23+
import java.util.HashSet;
24+
import java.util.Iterator;
2025
import java.util.Map.Entry;
2126
import java.util.Set;
2227

2328
final class BitmapMemoryLruCache extends LruCache<String, CacheableBitmapDrawable> {
2429

25-
BitmapMemoryLruCache(int maxSize) {
30+
private final Set<SoftReference<CacheableBitmapDrawable>> mRemovedEntries;
31+
private final BitmapLruCache.RecyclePolicy mRecyclePolicy;
32+
33+
BitmapMemoryLruCache(int maxSize, BitmapLruCache.RecyclePolicy policy) {
2634
super(maxSize);
35+
36+
mRecyclePolicy = policy;
37+
mRemovedEntries = policy.canInBitmap()
38+
? Collections.synchronizedSet(new HashSet<SoftReference<CacheableBitmapDrawable>>())
39+
: null;
2740
}
2841

2942
CacheableBitmapDrawable put(CacheableBitmapDrawable value) {
@@ -35,6 +48,10 @@ CacheableBitmapDrawable put(CacheableBitmapDrawable value) {
3548
return null;
3649
}
3750

51+
BitmapLruCache.RecyclePolicy getRecyclePolicy() {
52+
return mRecyclePolicy;
53+
}
54+
3855
@Override
3956
protected int sizeOf(String key, CacheableBitmapDrawable value) {
4057
return value.getMemorySize();
@@ -45,6 +62,41 @@ protected void entryRemoved(boolean evicted, String key, CacheableBitmapDrawable
4562
CacheableBitmapDrawable newValue) {
4663
// Notify the wrapper that it's no longer being cached
4764
oldValue.setCached(false);
65+
66+
if (mRemovedEntries != null && oldValue.isBitmapValid() && oldValue.isBitmapMutable()) {
67+
synchronized (mRemovedEntries) {
68+
mRemovedEntries.add(new SoftReference<CacheableBitmapDrawable>(oldValue));
69+
}
70+
}
71+
}
72+
73+
Bitmap getBitmapFromRemoved(final int width, final int height) {
74+
if (mRemovedEntries == null) {
75+
return null;
76+
}
77+
78+
Bitmap result = null;
79+
80+
synchronized (mRemovedEntries) {
81+
final Iterator<SoftReference<CacheableBitmapDrawable>> it = mRemovedEntries.iterator();
82+
83+
while (it.hasNext()) {
84+
CacheableBitmapDrawable value = it.next().get();
85+
86+
if (value != null && value.isBitmapValid() && value.isBitmapMutable()) {
87+
if (value.getIntrinsicWidth() == width
88+
&& value.getIntrinsicHeight() == height) {
89+
it.remove();
90+
result = value.getBitmap();
91+
break;
92+
}
93+
} else {
94+
it.remove();
95+
}
96+
}
97+
}
98+
99+
return result;
48100
}
49101

50102
void trimMemory() {

library/src/uk/co/senab/bitmapcache/CacheableBitmapDrawable.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,16 @@ public String getUrl() {
9999
*
100100
* @return true - if the bitmap has not been recycled.
101101
*/
102-
public synchronized boolean hasValidBitmap() {
102+
public synchronized boolean isBitmapValid() {
103103
Bitmap bitmap = getBitmap();
104104
return null != bitmap && !bitmap.isRecycled();
105105
}
106106

107+
public synchronized boolean isBitmapMutable() {
108+
Bitmap bitmap = getBitmap();
109+
return null != bitmap && bitmap.isMutable();
110+
}
111+
107112
/**
108113
* @return true - if the bitmap is currently being displayed by a {@link CacheableImageView}.
109114
*/
@@ -194,7 +199,7 @@ private synchronized void checkState(final boolean ignoreBeenDisplayed) {
194199
cancelCheckStateCallback();
195200

196201
// We're not being referenced or used anywhere
197-
if (mCacheCount <= 0 && mDisplayingCount <= 0 && hasValidBitmap()) {
202+
if (mCacheCount <= 0 && mDisplayingCount <= 0 && isBitmapValid()) {
198203

199204
/**
200205
* If we have been displayed or we don't care whether we have

library/src/uk/co/senab/bitmapcache/IoUtils.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@
2626

2727
class IoUtils {
2828

29+
static void closeStream(InputStream is) {
30+
if (is != null) {
31+
try {
32+
is.close();
33+
} catch (IOException e) {
34+
Log.i(Constants.LOG_TAG, "Failed to close InputStream", e);
35+
}
36+
}
37+
}
38+
39+
static void closeStream(OutputStream os) {
40+
if (os != null) {
41+
try {
42+
os.close();
43+
} catch (IOException e) {
44+
Log.i(Constants.LOG_TAG, "Failed to close OutputStream", e);
45+
}
46+
}
47+
}
48+
2949
static long copy(File in, OutputStream out) throws IOException {
3050
return copy(new FileInputStream(in), out);
3151
}
@@ -49,16 +69,8 @@ private static long copy(InputStream input, OutputStream output) throws IOExcept
4969
output.flush();
5070
return count;
5171
} finally {
52-
try {
53-
input.close();
54-
} catch (IOException e) {
55-
Log.i(Constants.LOG_TAG, "Failed to close InputStream", e);
56-
}
57-
try {
58-
output.close();
59-
} catch (IOException e) {
60-
Log.i(Constants.LOG_TAG, "Failed to close OutputStream", e);
61-
}
72+
IoUtils.closeStream(input);
73+
IoUtils.closeStream(output);
6274
}
6375
}
6476

0 commit comments

Comments
 (0)