既然我们要提供用户的体验,既然我们摒弃了软应用,那么我这里才用的是使用LRU的缓存机制来达到我们的目的。在android 3.1以上我们可以使用LruCache类,但如果在低一些的版本我们则只要把源代码copy出来放进工程就ok了。但是,仅仅把LruCache的代码copy出来只是完成了我们实现这里图片缓存方案的准备工作。
package XXXl; import java.util.LinkedHashMap; import java.util.Map; /** * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection. * * <p>If your cached values hold resources that need to be explicitly released, * override {@link #entryRemoved}. * * <p>If a cache miss should be computed on demand for the corresponding keys, * override {@link #create}. This simplifies the calling code, allowing it to * assume a value will always be returned, even when there's a cache miss. * * <p>By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * is limited to 4MiB of bitmaps: * <pre> {@code * int cacheSize = 4 * 1024 * 1024; // 4MiB * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { * protected int sizeOf(String key, Bitmap value) { * return value.getByteCount(); * } * }}</pre> * * <p>This class is thread-safe. Perform multiple cache operations atomically by * synchronizing on the cache: <pre> {@code * synchronized (cache) { * if (cache.get(key) == null) { * cache.put(key, value); * } * }}</pre> * * <p>This class does not allow null to be used as a key or value. A return * value of null from {@link #get}, {@link #put} or {@link #remove} is * unambiguous: the key was not in the cache. */ /** * Static library version of {@code android.util.LruCache}. Used to write apps * that run on API levels prior to 12. When running on API level 12 or above, * this implementation is still used; it does not try to switch to the * framework's implementation. See the framework SDK documentation for a class * overview. */ public class LruCache<K, V> { private LogUtils mLog = LogUtils.getLog(LruCache.class); private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount; /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */ public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } /** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. If a value was returned, it is moved to the * head of the queue. This returns null if a value is not cached and cannot * be created. */ public final V get(K key) { if (key == null) { throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } } /** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. */ public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; synchronized (this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } mLog.debug("maxSize :" + maxSize); mLog.debug("total size :" + size); trimToSize(maxSize); return previous; } /** * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */ public void trimToSize(int maxSize) { while (true) { K key; V value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } /** * Removes the entry for {@code key} if it exists. * * @return the previous value mapped by {@code key}. */ public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; } /** * Called for entries that have been evicted or removed. This method is * invoked when a value is evicted to make space, removed by a call to * {@link #remove}, or replaced by a call to {@link #put}. The default * implementation does nothing. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * <p>If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */ protected V create(K key) { return null; } private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } mLog.debug("size :" + result); return result; } /** * Returns the size of the entry for {@code key} and {@code value} in * user-defined units. The default implementation returns 1 so that size * is the number of entries and max size is the maximum number of entries. * * <p>An entry's size must not change while it is in the cache. */ protected int sizeOf(K key, V value) { return 1; } /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. */ public synchronized final int size() { return size; } /** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. */ public synchronized final int maxSize() { return maxSize; } /** * Returns the number of times {@link #get} returned a value. */ public synchronized final int hitCount() { return hitCount; } /** * Returns the number of times {@link #get} returned null or required a new * value to be created. */ public synchronized final int missCount() { return missCount; } /** * Returns the number of times {@link #create(Object)} returned a value. */ public synchronized final int createCount() { return createCount; } /** * Returns the number of times {@link #put} was called. */ public synchronized final int putCount() { return putCount; } /** * Returns the number of values that have been evicted. */ public synchronized final int evictionCount() { return evictionCount; } /** * Returns a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. */ public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(map); } @Override public synchronized final String toString() { int accesses = hitCount + missCount; int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount, hitPercent); } }
package XXX.view; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.RejectedExecutionException; import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.ImageView; import ch.boye.httpclientandroidlib.HttpEntity; import ch.boye.httpclientandroidlib.HttpResponse; import ch.boye.httpclientandroidlib.HttpStatus; import ch.boye.httpclientandroidlib.client.methods.HttpGet; public class CacheImageView extends ImageView { private static int mCacheSize; private int mDefaultImage = 0; private static Map<ImageView, String> mImageViews; private static LruCache<String, Bitmap> mLruCache; private static HashMap<Integer, SoftReference<Bitmap>> mResImage; private Context mContext; public CacheImageView (Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public CacheImageView (Context context, AttributeSet attrs) { super(context, attrs); init(context); } public CacheImageView (Context context) { super(context); init(context); } private void init(Context context) { if (mImageViews == null) { mImageViews = new WeakHashMap<ImageView, String>(); } if (mLruCache == null) { final int memClass = ((ActivityManager)context .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache. mCacheSize = 1024 * 1024 * memClass / 8; mLruCache = new LruCache<String, Bitmap>(mCacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than // number of items. return bitmap.getRowBytes() * bitmap.getHeight(); } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (evicted && oldValue !=null && !oldValue.isRecycled()) { oldValue.recycle(); oldValue = null; } } }; } if (mResImage == null) { mResImage = new HashMap<Integer, SoftReference<Bitmap>>(); } mContext = context; } @Override protected void onDraw(Canvas canvas) { BitmapDrawable drawable = (BitmapDrawable)getDrawable(); if (drawable == null ){ setImageBitmap(getLoadingBitmap(mContext)); } else { if( drawable.getBitmap() == null || drawable.getBitmap().isRecycled()) { setImageBitmap(getLoadingBitmap(mContext)); } } super.onDraw(canvas); } public void setImageUrl(String url, int resId) { mDefaultImage = resId; mImageViews.put(this, url); Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null || bitmap.isRecycled()) { setImageBitmap(getLoadingBitmap(mContext)); try { new DownloadTask().execute(url); } catch (RejectedExecutionException e) { // do nothing, just keep not crash } } else { setImageBitmap(bitmap); } } private Bitmap getLoadingBitmap(Context context) { SoftReference<Bitmap> loading = mResImage.get(mDefaultImage); if (loading == null || loading.get() == null || loading.get().isRecycled()) { loading = new SoftReference<Bitmap>(BitmapFactory.decodeResource( context.getResources(), mDefaultImage)); mResImage.put(mDefaultImage, loading); } return loading.get(); } private class DownloadTask extends AsyncTask<String, Void, Bitmap> { private String mParams; @Override public Bitmap doInBackground(String... params) { mParams = params[0]; Bitmap bm = null; if (mParams.startsWith("http:") || mParams.startsWith("https:")) {// 网络列表icon bm = download(mParams); } else { // other types of icons } addBitmapToCache(mParams, bm); return bm; } @Override public void onPostExecute(Bitmap bitmap) { String tag = mImageViews.get(RemoteImageView.this); if (!TextUtils.isEmpty(tag) && tag.equals(mParams)) { if (bitmap != null) { setImageBitmap(bitmap); } } } }; /* * An InputStream that skips the exact number of bytes provided, unless it * reaches EOF. */ static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int b = read(); if (b < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } } private Bitmap download(String url) { InputStream in = null; HttpEntity entity = null; Bitmap bmp = null; try { final HttpGet get = new HttpGet(url); final HttpResponse response = HttpManager.execute(mContext, get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { entity = response.getEntity(); in = entity.getContent(); try { bmp = getDecodeBitmap(in, url); } catch (OutOfMemoryError err) { Runtime.getRuntime().gc(); bmp = getDecodeBitmap(in, url); } } else { get.abort(); return bmp; } } catch (Exception e) { return bmp; } finally { IOUtils.closeStream(in); } return bmp; } private Bitmap getDecodeBitmap(InputStream in, String url) { Options options = new Options(); options.inPurgeable = true; options.inInputShareable = true; return BitmapFactory.decodeStream(new FlushedInputStream(in), null, options); } public void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { mLruCache.put(url, bitmap); Runtime.getRuntime().gc(); } } /** * @param url The URL of the image that will be retrieved from the cache. * @return The cached bitmap or null if it was not found. */ public static Bitmap getBitmapFromCache(String url) { return mLruCache.get(url); } public static void recycle() { if (mImageViews != null && !mImageViews.isEmpty()) { mImageViews.clear(); mImageViews = null; } if (mLruCache != null) { mLruCache.evictAll(); mLruCache = null; } if (mResImage != null) { for (SoftReference<Bitmap> reference : mResImage.values()) { Bitmap bitmap = reference.get(); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); bitmap = null; } } mResImage = null; } } }
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (evicted && oldValue !=null && !oldValue.isRecycled()) { oldValue.recycle(); oldValue = null; } }
<XXX.view.CacheImageView android:id="@+id/icon" android:layout_width="40dip" android:layout_height="40dip" android:layout_marginLeft="10dip" />
holder.icon = (CacheImageView)convertView.findViewById(R.id.icon); holder.icon.setImageUrl(url, resId);
注意结合另一外一篇文章来看: android上的一个网络接口和图片缓存框架enif