Welcome 微信登录

首页 / 移动开发 / Android / Android实现图片缓存与异步加载

ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom。
Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等。但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片。android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升。
我这里的解决方案是HandlerThread(异步加载)+LruCache(内存缓存)+DiskLruCache(硬盘缓存)。
作为程序员,我也不多说,直接和大家共享我的代码,用代码交流更方便些。
package com.example.util; import java.io.File;import java.util.Iterator;import java.util.LinkedList;import java.util.Queue;import java.util.Stack; import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.util.EntityUtils; import android.app.ActivityManager;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.ColorDrawable;import android.graphics.drawable.Drawable;import android.graphics.drawable.TransitionDrawable;import android.media.ThumbnailUtils;import android.os.Handler;import android.os.HandlerThread;import android.os.Looper;import android.os.Message;import android.support.v4.util.LruCache;import android.widget.ImageView; import com.example.MyApplication; /** * 图片加载类 ** @author 月月鸟 */public class ImageManager2 { private static ImageManager2 imageManager;public LruCache<String, Bitmap> mMemoryCache;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 20; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";public DiskLruCache mDiskCache;private static MyApplication myapp; /** 图片加载队列,后进先出 */private Stack<ImageRef> mImageQueue = new Stack<ImageRef>(); /** 图片请求队列,先进先出,用于存放已发送的请求。 */private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>(); /** 图片加载线程消息处理器 */private Handler mImageLoaderHandler; /** 图片加载线程是否就绪 */private boolean mImageLoaderIdle = true; /** 请求图片 */private static final int MSG_REQUEST = 1;/** 图片加载完成 */private static final int MSG_REPLY = 2;/** 中止图片加载线程 */private static final int MSG_STOP = 3;/** 如果图片是从网络加载,则应用渐显动画,如果从缓存读出则不应用动画 */private boolean isFromNet = true; /** * 获取单例,只能在UI线程中使用。 ** @param context * @return */public static ImageManager2 from(Context context) { // 如果不在ui线程中,则抛出异常if (Looper.myLooper() != Looper.getMainLooper()) {throw new RuntimeException("Cannot instantiate outside UI thread.");} if (myapp == null) {myapp = (MyApplication) context.getApplicationContext();} if (imageManager == null) {imageManager = new ImageManager2(myapp);} return imageManager;} /** * 私有构造函数,保证单例模式 ** @param context */private ImageManager2(Context context) {int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();memClass = memClass > 32 ? 32 : memClass;// 使用可用内存的1/8作为图片缓存final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { protected int sizeOf(String key, Bitmap bitmap) {return bitmap.getRowBytes() * bitmap.getHeight();} }; File cacheDir = DiskLruCache.getDiskCacheDir(context, DISK_CACHE_SUBDIR);mDiskCache = DiskLruCache.openCache(context, cacheDir, DISK_CACHE_SIZE); } /** * 存放图片信息 */class ImageRef { /** 图片对应ImageView控件 */ImageView imageView;/** 图片URL地址 */String url;/** 图片缓存路径 */String filePath;/** 默认图资源ID */int resId;int width = 0;int height = 0; /** * 构造函数 ** @param imageView * @param url * @param resId * @param filePath */ImageRef(ImageView imageView, String url, String filePath, int resId) {this.imageView = imageView;this.url = url;this.filePath = filePath;this.resId = resId;} ImageRef(ImageView imageView, String url, String filePath, int resId,int width, int height) {this.imageView = imageView;this.url = url;this.filePath = filePath;this.resId = resId;this.width = width;this.height = height;} } /** * 显示图片 ** @param imageView * @param url * @param resId */public void displayImage(ImageView imageView, String url, int resId) {if (imageView == null) {return;}if (imageView.getTag() != null&& imageView.getTag().toString().equals(url)) {return;}if (resId >= 0) {if (imageView.getBackground() == null) {imageView.setBackgroundResource(resId);}imageView.setImageDrawable(null); }if (url == null || url.equals("")) {return;} // 添加url tagimageView.setTag(url); // 读取map缓存Bitmap bitmap = mMemoryCache.get(url);if (bitmap != null) {setImageBitmap(imageView, bitmap, false);return;} // 生成文件名String filePath = urlToFilePath(url);if (filePath == null) {return;} queueImage(new ImageRef(imageView, url, filePath, resId));} /** * 显示图片固定大小图片的缩略图,一般用于显示列表的图片,可以大大减小内存使用 ** @param imageView 加载图片的控件 * @param url 加载地址 * @param resId 默认图片 * @param width 指定宽度 * @param height 指定高度 */public void displayImage(ImageView imageView, String url, int resId,int width, int height) {if (imageView == null) {return;}if (resId >= 0) { if (imageView.getBackground() == null) {imageView.setBackgroundResource(resId);}imageView.setImageDrawable(null); }if (url == null || url.equals("")) {return;} // 添加url tagimageView.setTag(url);// 读取map缓存Bitmap bitmap = mMemoryCache.get(url + width + height);if (bitmap != null) {setImageBitmap(imageView, bitmap, false);return;} // 生成文件名String filePath = urlToFilePath(url);if (filePath == null) {return;} queueImage(new ImageRef(imageView, url, filePath, resId, width, height));} /** * 入队,后进先出 ** @param imageRef */public void queueImage(ImageRef imageRef) { // 删除已有ImageViewIterator<ImageRef> iterator = mImageQueue.iterator();while (iterator.hasNext()) {if (iterator.next().imageView == imageRef.imageView) {iterator.remove();}} // 添加请求mImageQueue.push(imageRef);sendRequest();} /** * 发送请求 */private void sendRequest() { // 开启图片加载线程if (mImageLoaderHandler == null) {HandlerThread imageLoader = new HandlerThread("image_loader");imageLoader.start();mImageLoaderHandler = new ImageLoaderHandler(imageLoader.getLooper());} // 发送请求if (mImageLoaderIdle && mImageQueue.size() > 0) {ImageRef imageRef = mImageQueue.pop();Message message = mImageLoaderHandler.obtainMessage(MSG_REQUEST,imageRef);mImageLoaderHandler.sendMessage(message);mImageLoaderIdle = false;mRequestQueue.add(imageRef);}} /** * 图片加载线程 */class ImageLoaderHandler extends Handler { public ImageLoaderHandler(Looper looper) {super(looper);} public void handleMessage(Message msg) {if (msg == null)return; switch (msg.what) { case MSG_REQUEST: // 收到请求Bitmap bitmap = null;Bitmap tBitmap = null;if (msg.obj != null && msg.obj instanceof ImageRef) { ImageRef imageRef = (ImageRef) msg.obj;String url = imageRef.url;if (url == null)return;// 如果本地url即读取sd相册图片,则直接读取,不用经过DiskCacheif (url.toLowerCase().contains("dcim")) { tBitmap = null;BitmapFactory.Options opt = new BitmapFactory.Options();opt.inSampleSize = 1;opt.inJustDecodeBounds = true;BitmapFactory.decodeFile(url, opt);int bitmapSize = opt.outHeight * opt.outWidth * 4;opt.inSampleSize = bitmapSize / (1000 * 2000);opt.inJustDecodeBounds = false;tBitmap = BitmapFactory.decodeFile(url, opt);if (imageRef.width != 0 && imageRef.height != 0) {bitmap = ThumbnailUtils.extractThumbnail(tBitmap,imageRef.width, imageRef.height,ThumbnailUtils.OPTIONS_RECYCLE_INPUT);isFromNet = true;} else {bitmap = tBitmap;tBitmap = null;} } elsebitmap = mDiskCache.get(url); if (bitmap != null) {// ToolUtil.log("从disk缓存读取");// 写入map缓存if (imageRef.width != 0 && imageRef.height != 0) {if (mMemoryCache.get(url + imageRef.width+ imageRef.height) == null)mMemoryCache.put(url + imageRef.width+ imageRef.height, bitmap);} else {if (mMemoryCache.get(url) == null)mMemoryCache.put(url, bitmap);} } else {try {byte[] data = loadByteArrayFromNetwork(url); if (data != null) { BitmapFactory.Options opt = new BitmapFactory.Options();opt.inSampleSize = 1; opt.inJustDecodeBounds = true;BitmapFactory.decodeByteArray(data, 0,data.length, opt);int bitmapSize = opt.outHeight * opt.outWidth* 4;// pixels*3 if it"s RGB and pixels*4// if it"s ARGBif (bitmapSize > 1000 * 1200)opt.inSampleSize = 2;opt.inJustDecodeBounds = false;tBitmap = BitmapFactory.decodeByteArray(data,0, data.length, opt);if (imageRef.width != 0 && imageRef.height != 0) {bitmap = ThumbnailUtils.extractThumbnail(tBitmap,imageRef.width,imageRef.height,ThumbnailUtils.OPTIONS_RECYCLE_INPUT);} else {bitmap = tBitmap;tBitmap = null;} if (bitmap != null && url != null) {// 写入SD卡if (imageRef.width != 0&& imageRef.height != 0) {mDiskCache.put(url + imageRef.width+ imageRef.height, bitmap);mMemoryCache.put(url + imageRef.width+ imageRef.height, bitmap);} else {mDiskCache.put(url, bitmap);mMemoryCache.put(url, bitmap);}isFromNet = true;}}} catch (OutOfMemoryError e) {} } } if (mImageManagerHandler != null) {Message message = mImageManagerHandler.obtainMessage(MSG_REPLY, bitmap);mImageManagerHandler.sendMessage(message);}break; case MSG_STOP: // 收到终止指令Looper.myLooper().quit();break; }}} /** UI线程消息处理器 */private Handler mImageManagerHandler = new Handler() { @Overridepublic void handleMessage(Message msg) {if (msg != null) {switch (msg.what) { case MSG_REPLY: // 收到应答 do {ImageRef imageRef = mRequestQueue.remove(); if (imageRef == null)break; if (imageRef.imageView == null|| imageRef.imageView.getTag() == null|| imageRef.url == null)break; if (!(msg.obj instanceof Bitmap) || msg.obj == null) {break;}Bitmap bitmap = (Bitmap) msg.obj; // 非同一ImageViewif (!(imageRef.url).equals((String) imageRef.imageView.getTag())) {break;} setImageBitmap(imageRef.imageView, bitmap, isFromNet);isFromNet = false; } while (false); break;}}// 设置闲置标志mImageLoaderIdle = true; // 若服务未关闭,则发送下一个请求。if (mImageLoaderHandler != null) {sendRequest();}}}; /** * 添加图片显示渐现动画 **/private void setImageBitmap(ImageView imageView, Bitmap bitmap,boolean isTran) {if (isTran) {final TransitionDrawable td = new TransitionDrawable(new Drawable[] {new ColorDrawable(android.R.color.transparent),new BitmapDrawable(bitmap) });td.setCrossFadeEnabled(true);imageView.setImageDrawable(td);td.startTransition(300);} else {imageView.setImageBitmap(bitmap);}} /** * 从网络获取图片字节数组 ** @param url * @return */private byte[] loadByteArrayFromNetwork(String url) { try { HttpGet method = new HttpGet(url);HttpResponse response = myapp.getHttpClient().execute(method);HttpEntity entity = response.getEntity();return EntityUtils.toByteArray(entity); } catch (Exception e) {return null;} } /** * 根据url生成缓存文件完整路径名 ** @param url * @return */public String urlToFilePath(String url) { // 扩展名位置int index = url.lastIndexOf(".");if (index == -1) {return null;} StringBuilder filePath = new StringBuilder(); // 图片存取路径filePath.append(myapp.getCacheDir().toString()).append("/"); // 图片文件名 filePath.append(MD5.Md5(url)).append(url.substring(index)); return filePath.toString();} /** * Activity#onStop后,ListView不会有残余请求。 */public void stop() { // 清空请求队列mImageQueue.clear(); } }
这里就是给出了异步加载、内存缓存和硬盘缓存的解决方案,希望对大家的学习有所帮助。