Android图片管理组件(双缓存+异步加载)

来源:互联网 发布:seo站群计划书 编辑:程序博客网 时间:2024/04/30 05:37

Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等。但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片。android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升。

我这里的解决方案是HandlerThread(异步加载)+LruCache(内存缓存)+DiskLruCache(硬盘缓存)

作为程序员,我也不多说,直接和大家共享我的代码,用代码交流更方便些。

源码demo地址:
https://github.com/yueyueniao2012/multiimagechooser

 

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();}}


 

0 0
原创粉丝点击