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(); } }
这里就是给出了异步加载、内存缓存和硬盘缓存的解决方案,希望对大家的学习有所帮助。