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; // 10MB private 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 tag imageView.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 tag imageView.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) { // 删除已有ImageView Iterator<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相册图片,则直接读取,不用经过DiskCache if (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; } } else bitmap = 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 ARGB if (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() { @Override public 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; // 非同一ImageView if (!(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)