Android实现图片缓存与异步加载
程序员文章站
2024-02-24 14:30:58
imagemanager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现o...
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(); } }
这里就是给出了异步加载、内存缓存和硬盘缓存的解决方案,希望对大家的学习有所帮助。