Android实现图片缓存与异步加载
ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom。
Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等。但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片。android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升。
我这里的解决方案是HandlerThread(异步加载)+LruCache(内存缓存)+DiskLruCache(硬盘缓存)。
作为程序员,我也不多说,直接和大家共享我的代码,用代码交流更方便些。
packagecom.example.util;
importjava.io.File;
importjava.util.Iterator;
importjava.util.LinkedList;
importjava.util.Queue;
importjava.util.Stack;
importorg.apache.http.HttpEntity;
importorg.apache.http.HttpResponse;
importorg.apache.http.client.methods.HttpGet;
importorg.apache.http.util.EntityUtils;
importandroid.app.ActivityManager;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.graphics.drawable.BitmapDrawable;
importandroid.graphics.drawable.ColorDrawable;
importandroid.graphics.drawable.Drawable;
importandroid.graphics.drawable.TransitionDrawable;
importandroid.media.ThumbnailUtils;
importandroid.os.Handler;
importandroid.os.HandlerThread;
importandroid.os.Looper;
importandroid.os.Message;
importandroid.support.v4.util.LruCache;
importandroid.widget.ImageView;
importcom.example.MyApplication;
/**
*图片加载类
*
*@author月月鸟
*/
publicclassImageManager2{
privatestaticImageManager2imageManager;
publicLruCache<String,Bitmap>mMemoryCache;
privatestaticfinalintDISK_CACHE_SIZE=1024*1024*20;//10MB
privatestaticfinalStringDISK_CACHE_SUBDIR="thumbnails";
publicDiskLruCachemDiskCache;
privatestaticMyApplicationmyapp;
/**图片加载队列,后进先出*/
privateStack<ImageRef>mImageQueue=newStack<ImageRef>();
/**图片请求队列,先进先出,用于存放已发送的请求。*/
privateQueue<ImageRef>mRequestQueue=newLinkedList<ImageRef>();
/**图片加载线程消息处理器*/
privateHandlermImageLoaderHandler;
/**图片加载线程是否就绪*/
privatebooleanmImageLoaderIdle=true;
/**请求图片*/
privatestaticfinalintMSG_REQUEST=1;
/**图片加载完成*/
privatestaticfinalintMSG_REPLY=2;
/**中止图片加载线程*/
privatestaticfinalintMSG_STOP=3;
/**如果图片是从网络加载,则应用渐显动画,如果从缓存读出则不应用动画*/
privatebooleanisFromNet=true;
/**
*获取单例,只能在UI线程中使用。
*
*@paramcontext
*@return
*/
publicstaticImageManager2from(Contextcontext){
//如果不在ui线程中,则抛出异常
if(Looper.myLooper()!=Looper.getMainLooper()){
thrownewRuntimeException("CannotinstantiateoutsideUIthread.");
}
if(myapp==null){
myapp=(MyApplication)context.getApplicationContext();
}
if(imageManager==null){
imageManager=newImageManager2(myapp);
}
returnimageManager;
}
/**
*私有构造函数,保证单例模式
*
*@paramcontext
*/
privateImageManager2(Contextcontext){
intmemClass=((ActivityManager)context
.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
memClass=memClass>32?32:memClass;
//使用可用内存的1/8作为图片缓存
finalintcacheSize=1024*1024*memClass/8;
mMemoryCache=newLruCache<String,Bitmap>(cacheSize){
protectedintsizeOf(Stringkey,Bitmapbitmap){
returnbitmap.getRowBytes()*bitmap.getHeight();
}
};
FilecacheDir=DiskLruCache
.getDiskCacheDir(context,DISK_CACHE_SUBDIR);
mDiskCache=DiskLruCache.openCache(context,cacheDir,DISK_CACHE_SIZE);
}
/**
*存放图片信息
*/
classImageRef{
/**图片对应ImageView控件*/
ImageViewimageView;
/**图片URL地址*/
Stringurl;
/**图片缓存路径*/
StringfilePath;
/**默认图资源ID*/
intresId;
intwidth=0;
intheight=0;
/**
*构造函数
*
*@paramimageView
*@paramurl
*@paramresId
*@paramfilePath
*/
ImageRef(ImageViewimageView,Stringurl,StringfilePath,intresId){
this.imageView=imageView;
this.url=url;
this.filePath=filePath;
this.resId=resId;
}
ImageRef(ImageViewimageView,Stringurl,StringfilePath,intresId,
intwidth,intheight){
this.imageView=imageView;
this.url=url;
this.filePath=filePath;
this.resId=resId;
this.width=width;
this.height=height;
}
}
/**
*显示图片
*
*@paramimageView
*@paramurl
*@paramresId
*/
publicvoiddisplayImage(ImageViewimageView,Stringurl,intresId){
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;
}
//添加urltag
imageView.setTag(url);
//读取map缓存
Bitmapbitmap=mMemoryCache.get(url);
if(bitmap!=null){
setImageBitmap(imageView,bitmap,false);
return;
}
//生成文件名
StringfilePath=urlToFilePath(url);
if(filePath==null){
return;
}
queueImage(newImageRef(imageView,url,filePath,resId));
}
/**
*显示图片固定大小图片的缩略图,一般用于显示列表的图片,可以大大减小内存使用
*
*@paramimageView加载图片的控件
*@paramurl加载地址
*@paramresId默认图片
*@paramwidth指定宽度
*@paramheight指定高度
*/
publicvoiddisplayImage(ImageViewimageView,Stringurl,intresId,
intwidth,intheight){
if(imageView==null){
return;
}
if(resId>=0){
if(imageView.getBackground()==null){
imageView.setBackgroundResource(resId);
}
imageView.setImageDrawable(null);
}
if(url==null||url.equals("")){
return;
}
//添加urltag
imageView.setTag(url);
//读取map缓存
Bitmapbitmap=mMemoryCache.get(url+width+height);
if(bitmap!=null){
setImageBitmap(imageView,bitmap,false);
return;
}
//生成文件名
StringfilePath=urlToFilePath(url);
if(filePath==null){
return;
}
queueImage(newImageRef(imageView,url,filePath,resId,width,height));
}
/**
*入队,后进先出
*
*@paramimageRef
*/
publicvoidqueueImage(ImageRefimageRef){
//删除已有ImageView
Iterator<ImageRef>iterator=mImageQueue.iterator();
while(iterator.hasNext()){
if(iterator.next().imageView==imageRef.imageView){
iterator.remove();
}
}
//添加请求
mImageQueue.push(imageRef);
sendRequest();
}
/**
*发送请求
*/
privatevoidsendRequest(){
//开启图片加载线程
if(mImageLoaderHandler==null){
HandlerThreadimageLoader=newHandlerThread("image_loader");
imageLoader.start();
mImageLoaderHandler=newImageLoaderHandler(
imageLoader.getLooper());
}
//发送请求
if(mImageLoaderIdle&&mImageQueue.size()>0){
ImageRefimageRef=mImageQueue.pop();
Messagemessage=mImageLoaderHandler.obtainMessage(MSG_REQUEST,
imageRef);
mImageLoaderHandler.sendMessage(message);
mImageLoaderIdle=false;
mRequestQueue.add(imageRef);
}
}
/**
*图片加载线程
*/
classImageLoaderHandlerextendsHandler{
publicImageLoaderHandler(Looperlooper){
super(looper);
}
publicvoidhandleMessage(Messagemsg){
if(msg==null)
return;
switch(msg.what){
caseMSG_REQUEST://收到请求
Bitmapbitmap=null;
BitmaptBitmap=null;
if(msg.obj!=null&&msg.objinstanceofImageRef){
ImageRefimageRef=(ImageRef)msg.obj;
Stringurl=imageRef.url;
if(url==null)
return;
//如果本地url即读取sd相册图片,则直接读取,不用经过DiskCache
if(url.toLowerCase().contains("dcim")){
tBitmap=null;
BitmapFactory.Optionsopt=newBitmapFactory.Options();
opt.inSampleSize=1;
opt.inJustDecodeBounds=true;
BitmapFactory.decodeFile(url,opt);
intbitmapSize=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.Optionsopt=newBitmapFactory.Options();
opt.inSampleSize=1;
opt.inJustDecodeBounds=true;
BitmapFactory.decodeByteArray(data,0,
data.length,opt);
intbitmapSize=opt.outHeight*opt.outWidth
*4;//pixels*3ifit'sRGBandpixels*4
//ifit'sARGB
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(OutOfMemoryErrore){
}
}
}
if(mImageManagerHandler!=null){
Messagemessage=mImageManagerHandler.obtainMessage(
MSG_REPLY,bitmap);
mImageManagerHandler.sendMessage(message);
}
break;
caseMSG_STOP://收到终止指令
Looper.myLooper().quit();
break;
}
}
}
/**UI线程消息处理器*/
privateHandlermImageManagerHandler=newHandler(){
@Override
publicvoidhandleMessage(Messagemsg){
if(msg!=null){
switch(msg.what){
caseMSG_REPLY://收到应答
do{
ImageRefimageRef=mRequestQueue.remove();
if(imageRef==null)
break;
if(imageRef.imageView==null
||imageRef.imageView.getTag()==null
||imageRef.url==null)
break;
if(!(msg.objinstanceofBitmap)||msg.obj==null){
break;
}
Bitmapbitmap=(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();
}
}
};
/**
*添加图片显示渐现动画
*
*/
privatevoidsetImageBitmap(ImageViewimageView,Bitmapbitmap,
booleanisTran){
if(isTran){
finalTransitionDrawabletd=newTransitionDrawable(
newDrawable[]{
newColorDrawable(android.R.color.transparent),
newBitmapDrawable(bitmap)});
td.setCrossFadeEnabled(true);
imageView.setImageDrawable(td);
td.startTransition(300);
}else{
imageView.setImageBitmap(bitmap);
}
}
/**
*从网络获取图片字节数组
*
*@paramurl
*@return
*/
privatebyte[]loadByteArrayFromNetwork(Stringurl){
try{
HttpGetmethod=newHttpGet(url);
HttpResponseresponse=myapp.getHttpClient().execute(method);
HttpEntityentity=response.getEntity();
returnEntityUtils.toByteArray(entity);
}catch(Exceptione){
returnnull;
}
}
/**
*根据url生成缓存文件完整路径名
*
*@paramurl
*@return
*/
publicStringurlToFilePath(Stringurl){
//扩展名位置
intindex=url.lastIndexOf('.');
if(index==-1){
returnnull;
}
StringBuilderfilePath=newStringBuilder();
//图片存取路径
filePath.append(myapp.getCacheDir().toString()).append('/');
//图片文件名
filePath.append(MD5.Md5(url)).append(url.substring(index));
returnfilePath.toString();
}
/**
*Activity#onStop后,ListView不会有残余请求。
*/
publicvoidstop(){
//清空请求队列
mImageQueue.clear();
}
}
这里就是给出了异步加载、内存缓存和硬盘缓存的解决方案,希望对大家的学习有所帮助。