本文共 17483 字,大约阅读时间需要 58 分钟。
转自:
如果你还在因为大量不同size的图片缓存产生的OOM而烦恼,如果你还在因为用软引用(SoftReference)快速回收的蛋疼用户体验而不知所措,那么我建议无论你是高手还是菜鸟,真的很有必要看一下这篇文章,希望能从中给你一些启发,给你的产品用户带去一些好的体验。
思维的火花
既然我们要提供用户的体验,既然我们摒弃了软应用,那么我这里才用的是使用LRU的缓存机制来达到我们的目的。在android 3.1以上我们可以使用LruCache类,但如果在低一些的版本我们则只要把源代码copy出来放进工程就ok了。但是,仅仅把LruCache的代码copy出来只是完成了我们实现这里图片缓存方案的准备工作。
精心的构建
1.LruCache - package XXXl;
-
- import java.util.LinkedHashMap;
- import java.util.Map;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public class LruCache<K, V> {
- private LogUtils mLog = LogUtils.getLog(LruCache.class);
- private final LinkedHashMap<K, V> map;
-
-
- private int size;
- private int maxSize;
-
- private int putCount;
- private int createCount;
- private int evictionCount;
- private int hitCount;
- private int missCount;
-
-
-
-
-
-
- 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);
- }
-
-
-
-
-
-
-
- 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++;
- }
-
-
-
-
-
-
-
-
- V createdValue = create(key);
- if (createdValue == null) {
- return null;
- }
-
- synchronized (this) {
- createCount++;
- mapValue = map.put(key, createdValue);
-
- if (mapValue != null) {
-
- map.put(key, mapValue);
- } else {
- size += safeSizeOf(key, createdValue);
- }
- }
-
- if (mapValue != null) {
- entryRemoved(false, key, createdValue, mapValue);
- return mapValue;
- } else {
- trimToSize(maxSize);
- return createdValue;
- }
- }
-
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
- 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);
- }
- }
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
-
-
- protected int sizeOf(K key, V value) {
- return 1;
- }
-
-
-
-
- public final void evictAll() {
- trimToSize(-1);
- }
-
-
-
-
-
-
- public synchronized final int size() {
- return size;
- }
-
-
-
-
-
-
- public synchronized final int maxSize() {
- return maxSize;
- }
-
-
-
-
- public synchronized final int hitCount() {
- return hitCount;
- }
-
-
-
-
-
- public synchronized final int missCount() {
- return missCount;
- }
-
-
-
-
- public synchronized final int createCount() {
- return createCount;
- }
-
-
-
-
- public synchronized final int putCount() {
- return putCount;
- }
-
-
-
-
- public synchronized final int evictionCount() {
- return evictionCount;
- }
-
-
-
-
-
- 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);
- }
- }
2.自定义ImageView - 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();
-
-
- mCacheSize = 1024 * 1024 * memClass / 8;
- mLruCache = new LruCache<String, Bitmap>(mCacheSize) {
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
-
-
- 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) {
-
- }
- } 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:")) {
- bm = download(mParams);
- } else {
-
- }
- 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);
- }
- }
- }
- };
-
-
-
-
-
- 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;
- } else {
- bytesSkipped = 1;
- }
- }
- 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();
- }
- }
-
-
-
-
-
- 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;
- }
- }
- }
这一步是实现LRU缓存方案的最关键一步,里面需要对几个地方做详细和认真的解释。 在初始化LruCache的时候我们有用到: - protected void entryRemoved(boolean evicted, String key, Bitmap oldValue,
- Bitmap newValue) {
- if (evicted && oldValue !=null && !oldValue.isRecycled()) {
- oldValue.recycle();
- oldValue = null;
- }
- }
注意if里面的条件一共由3个(evicted && oldValue !=null && !oldValue.isRecycled())组成一个都不能少,至于原因希望你们去思考。 另外调用addBitmapToCache方法我是在后台调用的,没有在主线程里面操作,原因是里面调用了Runtime.getRuntime().gc(),基本上每次GC的执行都要花去20~50ms如果是在列表里面的话,对ui应该有一定的影响。在此强调一下Runtime.getRuntime().gc()在每次加载图片之后最好调用他。这是一个小兄弟测试的结果,在android调用GC有助于虚拟机减少内存碎片和加速内存碎片的重整理。 下载图片建立连接我用了httpclient的连接池方式,如果你觉的麻烦你可以使用URLconnection,这里暂时不给出httpclient连接池框架的部分,如果你随时关注我的话,你可以从我后面的博客中看到关于它的话题。 3.如何使用 可能你的项目中有多个地方要用到图片,那么只要在你的xml中需要用到imageview的这样去定义(以listview的row举例): - <XXX.view.CacheImageView
- android:id="@+id/icon"
- android:layout_width="40dip"
- android:layout_height="40dip"
- android:layout_marginLeft="10dip" />
然后再你的adapter代码中只需要简单的两句: - holder.icon = (CacheImageView)convertView.findViewById(R.id.icon);
- holder.icon.setImageUrl(url, resId);
完美的总结
该方案是尽量减少图片被回收的时间,但是并不是不被回收,所以需要一直展示给用户的情况不适合本方案。 对于某些国产机内存特小的那种,即使使用软引用都很容易挂的那种,建议不要再设置为内存的8分之一大小,而是获取到手机的UA(model),去硬编码一个大小吧。 本方案在这里只展示了基于内存的缓存方式,基于disk的部分代码,朋友们可以去实现,这里不再赘述。 可能本方案还有很多不足,欢迎大家提意见,我好不断完善 转载地址:http://tkefb.baihongyu.com/