1:网络的底层环境 采用apache 的httpClient 链接池框架
2:图片缓存采用基于LRU 的算法
3:网络接口采用监听者模式
4 包含图片的OOM 处理(及时回收处理技术的应用)
import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.concurrent.RejectedExecutionException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import xiaogang.enif.utils.HttpManager; import xiaogang.enif.utils.IOUtils; import xiaogang.enif.utils.LogUtils; import xiaogang.enif.utils.LruCache; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.os.AsyncTask; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.ImageView; public class CacheView extends ImageView { private static final int DEFAULT_RES_ID = 0; private int mDefaultImage = DEFAULT_RES_ID; private static LruCache<String, Bitmap> mLruCache; private static HashMap<Integer, SoftReference<Bitmap>> mResImage; private Context mContext; private LogUtils mLog = LogUtils.getLog(CacheView.class); public CacheView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public CacheView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public CacheView(Context context) { super(context); init(context); } private void init(Context context) { mContext = context; if (mLruCache == null) { final int cacheSize = getCacheSize(context); mLruCache = new LruCache<String, Bitmap>(cacheSize) { @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>>(); } } @Override protected void onDraw(Canvas canvas) { BitmapDrawable drawable = (BitmapDrawable)getDrawable(); if (drawable == null) { setDefaultImage(); } else { if (drawable.getBitmap() == null || drawable.getBitmap().isRecycled()) { setDefaultImage(); } } try { super.onDraw(canvas); } catch(RuntimeException ex) { } } public void setImageUrl(String url, int resId) { setTag(url); Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null || bitmap.isRecycled()) { mDefaultImage = resId; setDefaultImage(); try { new DownloadTask().execute(url); } catch (RejectedExecutionException e) { // do nothing, just keep not crash } } else { setImageBitmap(bitmap); } } private void setDefaultImage() { if (mDefaultImage != DEFAULT_RES_ID) { setImageBitmap(getDefaultBitmap(mContext)); } } private Bitmap getDefaultBitmap(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]; final Bitmap bm = download(mParams); addBitmapToCache(mParams, bm); return bm; } @Override public void onPostExecute(Bitmap bitmap) { String tag = (String)getTag(); 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; } addBitmapToCache(url, bmp); } catch (IOException e) { return bmp; } finally { IOUtils.closeStream(in); } return bmp; } private final Bitmap getDecodeBitmap(InputStream in, String url) { Options options = new Options(); options.inPurgeable = true; options.inInputShareable = true; return BitmapFactory.decodeStream(new FlushedInputStream(in), null, options); } private final void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { mLruCache.put(url, bitmap); Runtime.getRuntime().gc(); } } private final Bitmap getBitmapFromCache(String url) { return mLruCache.get(url); } private int getCacheSize(Context context) { // According to the phone memory, set a proper cache size for LRU cache // dynamically. final ActivityManager am = (ActivityManager)context .getSystemService(Context.ACTIVITY_SERVICE); final int memClass = am.getMemoryClass(); int cacheSize; if (memClass <= 24) { cacheSize = (memClass << 20) / 24; } else if (memClass <= 36) { cacheSize = (memClass << 20) / 18; } else if (memClass <= 48) { cacheSize = (memClass << 20) / 12; } else { cacheSize = (memClass << 20) >> 3; } mLog.debug("cacheSize == "+cacheSize); System.out.println("cacheSize == "+cacheSize); return cacheSize; } public static void recycle() { if (mLruCache != null && !mLruCache.isEmpty()) { 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; } } } 说明: 1)entryRemoved 在做Bitmap recyle 的时候的三个条件缺一不可 2)onDraw 里面判断图片是否被回收,如果回收 需要设置默认的图片 3)add bitmap 到cache 的时候 Runtime.getRuntime().gc 的调用