[置顶] android中图片的三级cache策略(内存、文件、网络)之二:内存缓存策略
前言
记得很久之前我写了一篇banner的文章,好多朋友找我要代码,并要我开放banner中使用的图片管理工厂-ImageManager。如果想很好地理解下面的故事,请参看我半年前写的两篇博文:android中图片的三级cache策略(内存、文件、网络) 一和android中左右滑屏的实现(广告位banner组件)。当时没有发上来是由于如下几点原因:首先代码较多,其次当时写的时候也参考了网络上存在的三级cache策略(大同小异),并且采用了Android项目中开源的LruCache页面淘汰算法(近期最少使用算法),还有一点就是这是实际项目使用的代码,不便直接开放,但是现在我决定把它稍作修改后开放给大家。这里我想说说那个banner,平心而论,banner的代码很多,如果采用ViewPager之类的则可以减少不少代码,但是我更看重banner的实现思想以及它的封装和事件传递,在自定义控件的封装和架构上,我到现在还觉得banner是及其成功的,尤其是banner和ImageManager结合以后,整个功能浑然天成,超高内聚,使用起来及其方便,最少只需要两行代码,你不需要导入xml,也不需要处理Json拉取策略,因为相关业务层都被封装在了banner内部,对外只保留很少的几个接口,只要实现它就能和banner内部进行交互。下面我将要介绍三级cache策略之二:内存缓存策略。
内存缓存策略
当有一个图片要去从网络下载的时候,我们并不会直接去从网络下载,因为在这个时代,用户的流量是宝贵的,耗流量的应用是不会得到用户的青睐的。那我们该怎么办呢?这样,我们会先从内存缓存中去查找是否有该图片,如果没有就去文件缓存中查找是否有该图片,如果还没有,我们就从网络下载图片。本博文的侧重点是如何做内存缓存,内存缓存的查找策略是:先从强引用缓存中查找,如果没有再从软引用缓存中查找,如果在软引用缓存中找到了,就把它移入强引用缓存;如果强引用缓存满了,就会根据Lru算法把某些图片移入软引用缓存,如果软引用缓存也满了,最早的软引用就会被删除。这里,我有必要说明下几个概念:强引用、软引用、弱引用、Lru。
强引用:就是直接引用一个对象,一般的对象引用均是强引用
软引用:引用一个对象,当内存不足并且除了我们的引用之外没有其他地方引用此对象的情况 下,该对象会被gc回收
弱引用:引用一个对象,当除了我们的引用之外没有其他地方引用此对象的情况下,只要gc被调用,它就会被回收(请注意它和软引用的区别)
Lru:Least Recently Used 近期最少使用算法,是一种页面置换算法,其思想是在缓存的页面数目固定的情况下,那些最近使用次数最少的页面将被移出,对于我们的内存缓存来说,强引用缓存大小固定为4M,如果当缓存的图片大于4M的时候,有些图片就会被从强引用缓存中删除,哪些图片会被删除呢,就是那些近期使用次数最少的图片。
代码
[java] view plain copy- publicclassImageMemoryCache{
- /**
- *从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
- *强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
- */
- privatestaticfinalStringTAG="ImageMemoryCache";
- privatestaticLruCache<String,Bitmap>mLruCache;//强引用缓存
- privatestaticLinkedHashMap<String,SoftReference<Bitmap>>mSoftCache;//软引用缓存
- privatestaticfinalintLRU_CACHE_SIZE=4*1024*1024;//强引用缓存容量:4MB
- privatestaticfinalintSOFT_CACHE_NUM=20;//软引用缓存个数
- //在这里分别初始化强引用缓存和弱引用缓存
- publicImageMemoryCache(){
- mLruCache=newLruCache<String,Bitmap>(LRU_CACHE_SIZE){
- @Override
- //sizeOf返回为单个hashmapvalue的大小
- protectedintsizeOf(Stringkey,Bitmapvalue){
- if(value!=null)
- returnvalue.getRowBytes()*value.getHeight();
- else
- return0;
- }
- @Override
- protectedvoidentryRemoved(booleanevicted,Stringkey,
- BitmapoldValue,BitmapnewValue){
- if(oldValue!=null){
- //强引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
- Logger.d(TAG,"LruCacheisfull,movetoSoftRefernceCache");
- mSoftCache.put(key,newSoftReference<Bitmap>(oldValue));
- }
- }
- };
- mSoftCache=newLinkedHashMap<String,SoftReference<Bitmap>>(
- SOFT_CACHE_NUM,0.75f,true){
- privatestaticfinallongserialVersionUID=1L;
- /**
- *当软引用数量大于20的时候,最旧的软引用将会被从链式哈希表中移出
- */
- @Override
- protectedbooleanremoveEldestEntry(
- Entry<String,SoftReference<Bitmap>>eldest){
- if(size()>SOFT_CACHE_NUM){
- Logger.d(TAG,"shouldremovetheeldestfromSoftReference");
- returntrue;
- }
- returnfalse;
- }
- };
- }
- /**
- *从缓存中获取图片
- */
- publicBitmapgetBitmapFromMemory(Stringurl){
- Bitmapbitmap;
- //先从强引用缓存中获取
- synchronized(mLruCache){
- bitmap=mLruCache.get(url);
- if(bitmap!=null){
- //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
- mLruCache.remove(url);
- mLruCache.put(url,bitmap);
- Logger.d(TAG,"getbmpfromLruCache,url="+url);
- returnbitmap;
- }
- }
- //如果强引用缓存中找不到,到软引用缓存中找,找到后就把它从软引用中移到强引用缓存中
- synchronized(mSoftCache){
- SoftReference<Bitmap>bitmapReference=mSoftCache.get(url);
- if(bitmapReference!=null){
- bitmap=bitmapReference.get();
- if(bitmap!=null){
- //将图片移回LruCache
- mLruCache.put(url,bitmap);
- mSoftCache.remove(url);
- Logger.d(TAG,"getbmpfromSoftReferenceCache,url="+url);
- returnbitmap;
- }else{
- mSoftCache.remove(url);
- }
- }
- }
- returnnull;
- }
- /**
- *添加图片到缓存
- */
- publicvoidaddBitmapToMemory(Stringurl,Bitmapbitmap){
- if(bitmap!=null){
- synchronized(mLruCache){
- mLruCache.put(url,bitmap);
- }
- }
- }
- publicvoidclearCache(){
- mSoftCache.clear();
- }
- }
另外,给出LruCache供大家参考:
[java] view plain copy
- /*
- *Copyright(C)2011TheAndroidOpenSourceProject
- *
- *LicensedundertheApacheLicense,Version2.0(the"License");
- *youmaynotusethisfileexceptincompliancewiththeLicense.
- *YoumayobtainacopyoftheLicenseat
- *
- *http://www.apache.org/licenses/LICENSE-2.0
- *
- *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
- *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- *SeetheLicenseforthespecificlanguagegoverningpermissionsand
- *limitationsundertheLicense.
- */
- /**
- *Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
- *当cache已满的时候加入新的item时,在队列尾部的item会被回收。
- *
- *如果你cache的某个值需要明确释放,重写entryRemoved()
- *
- *如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。
- *
- *默认cache大小是测量的item的数量,重写sizeof计算不同item的大小。
- *
- *<pre>{@code
- *intcacheSize=4*1024*1024;//4MiB
- *LruCache<String,Bitmap>bitmapCache=newLruCache<String,Bitmap>(cacheSize){
- *protectedintsizeOf(Stringkey,Bitmapvalue){
- *returnvalue.getByteCount();
- *}
- *}}</pre>
- *
- *<p>Thisclassisthread-safe.Performmultiplecacheoperationsatomicallyby
- *synchronizingonthecache:<pre>{@code
- *synchronized(cache){
- *if(cache.get(key)==null){
- *cache.put(key,value);
- *}
- *}}</pre>
- *
- *不允许key或者value为null
- *当get(),put(),remove()返回值为null时,key相应的项不在cache中
- */
- publicclassLruCache<K,V>{
- privatefinalLinkedHashMap<K,V>map;
- /**Sizeofthiscacheinunits.Notnecessarilythenumberofelements.*/
- privateintsize;//已经存储的大小
- privateintmaxSize;//规定的最大存储空间
- privateintputCount;//put的次数
- privateintcreateCount;//create的次数
- privateintevictionCount;//回收的次数
- privateinthitCount;//命中的次数
- privateintmissCount;//丢失的次数
- /**
- *@parammaxSizeforcachesthatdonotoverride{@link#sizeOf},thisis
- *themaximumnumberofentriesinthecache.Forallothercaches,
- *thisisthemaximumsumofthesizesoftheentriesinthiscache.
- */
- publicLruCache(intmaxSize){
- if(maxSize<=0){
- thrownewIllegalArgumentException("maxSize<=0");
- }
- this.maxSize=maxSize;
- this.map=newLinkedHashMap<K,V>(0,0.75f,true);
- }
- /**
- *通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
- *如果item的value没有被cache或者不能被创建,则返回null。
- */
- publicfinalVget(Kkey){
- if(key==null){
- thrownewNullPointerException("key==null");
- }
- VmapValue;
- synchronized(this){
- mapValue=map.get(key);
- if(mapValue!=null){
- hitCount++;
- returnmapValue;
- }
- missCount++;
- }
- /*
- *Attempttocreateavalue.Thismaytakealongtime,andthemap
- *maybedifferentwhencreate()returns.Ifaconflictingvaluewas
- *addedtothemapwhilecreate()wasworking,weleavethatvaluein
- *themapandreleasethecreatedvalue.
- */
- VcreatedValue=create(key);
- if(createdValue==null){
- returnnull;
- }
- synchronized(this){
- createCount++;
- mapValue=map.put(key,createdValue);
- if(mapValue!=null){
- //Therewasaconflictsoundothatlastput
- map.put(key,mapValue);
- }else{
- size+=safeSizeOf(key,createdValue);
- }
- }
- if(mapValue!=null){
- entryRemoved(false,key,createdValue,mapValue);
- returnmapValue;
- }else{
- trimToSize(maxSize);
- returncreatedValue;
- }
- }
- /**
- *Caches{@codevalue}for{@codekey}.Thevalueismovedtotheheadof
- *thequeue.
- *
- *@returnthepreviousvaluemappedby{@codekey}.
- */
- publicfinalVput(Kkey,Vvalue){
- if(key==null||value==null){
- thrownewNullPointerException("key==null||value==null");
- }
- Vprevious;
- synchronized(this){
- putCount++;
- size+=safeSizeOf(key,value);
- previous=map.put(key,value);
- if(previous!=null){
- size-=safeSizeOf(key,previous);
- }
- }
- if(previous!=null){
- entryRemoved(false,key,previous,value);
- }
- trimToSize(maxSize);
- returnprevious;
- }
- /**
- *@parammaxSizethemaximumsizeofthecachebeforereturning.Maybe-1
- *toevicteven0-sizedelements.
- */
- privatevoidtrimToSize(intmaxSize){
- while(true){
- Kkey;
- Vvalue;
- synchronized(this){
- if(size<0||(map.isEmpty()&&size!=0)){
- thrownewIllegalStateException(getClass().getName()
- +".sizeOf()isreportinginconsistentresults!");
- }
- if(size<=maxSize){
- break;
- }
- /*
- *Map.Entry<K,V>toEvict=map.eldest();
- */
- //modifybyechy
- Iterator<Entry<K,V>>iter=map.entrySet().iterator();
- Map.Entry<K,V>toEvict=null;
- while(iter.hasNext())
- {
- toEvict=(Entry<K,V>)iter.next();
- break;
- }
- if(toEvict==null){
- break;
- }
- key=toEvict.getKey();
- value=toEvict.getValue();
- map.remove(key);
- size-=safeSizeOf(key,value);
- evictionCount++;
- }
- entryRemoved(true,key,value,null);
- }
- }
- /**
- *Removestheentryfor{@codekey}ifitexists.
- *
- *@returnthepreviousvaluemappedby{@codekey}.
- */
- publicfinalVremove(Kkey){
- if(key==null){
- thrownewNullPointerException("key==null");
- }
- Vprevious;
- synchronized(this){
- previous=map.remove(key);
- if(previous!=null){
- size-=safeSizeOf(key,previous);
- }
- }
- if(previous!=null){
- entryRemoved(false,key,previous,null);
- }
- returnprevious;
- }
- /**
- *Calledforentriesthathavebeenevictedorremoved.Thismethodis
- *invokedwhenavalueisevictedtomakespace,removedbyacallto
- *{@link#remove},orreplacedbyacallto{@link#put}.Thedefault
- *implementationdoesnothing.
- *
- *<p>Themethodiscalledwithoutsynchronization:otherthreadsmay
- *accessthecachewhilethismethodisexecuting.
- *
- *@paramevictedtrueiftheentryisbeingremovedtomakespace,false
- *iftheremovalwascausedbya{@link#put}or{@link#remove}.
- *@paramnewValuethenewvaluefor{@codekey},ifitexists.Ifnon-null,
- *thisremovalwascausedbya{@link#put}.Otherwiseitwascausedby
- *anevictionora{@link#remove}.
- */
- protectedvoidentryRemoved(booleanevicted,Kkey,VoldValue,VnewValue){}
- /**
- *Calledafteracachemisstocomputeavalueforthecorrespondingkey.
- *Returnsthecomputedvalueornullifnovaluecanbecomputed.The
- *defaultimplementationreturnsnull.
- *
- *<p>Themethodiscalledwithoutsynchronization:otherthreadsmay
- *accessthecachewhilethismethodisexecuting.
- *
- *<p>Ifavaluefor{@codekey}existsinthecachewhenthismethod
- *returns,thecreatedvaluewillbereleasedwith{@link#entryRemoved}
- *anddiscarded.Thiscanoccurwhenmultiplethreadsrequestthesamekey
- *atthesametime(causingmultiplevaluestobecreated),orwhenone
- *threadcalls{@link#put}whileanotheriscreatingavalueforthesame
- *key.
- */
- protectedVcreate(Kkey){
- returnnull;
- }
- privateintsafeSizeOf(Kkey,Vvalue){
- intresult=sizeOf(key,value);
- if(result<0){
- thrownewIllegalStateException("Negativesize:"+key+"="+value);
- }
- returnresult;
- }
- /**
- *Returnsthesizeoftheentryfor{@codekey}and{@codevalue}in
- *user-definedunits.Thedefaultimplementationreturns1sothatsize
- *isthenumberofentriesandmaxsizeisthemaximumnumberofentries.
- *
- *<p>Anentry'ssizemustnotchangewhileitisinthecache.
- */
- protectedintsizeOf(Kkey,Vvalue){
- return1;
- }
- /**
- *Clearthecache,calling{@link#entryRemoved}oneachremovedentry.
- */
- publicfinalvoidevictAll(){
- trimToSize(-1);//-1willevict0-sizedelements
- }
- /**
- *Forcachesthatdonotoverride{@link#sizeOf},thisreturnsthenumber
- *ofentriesinthecache.Forallothercaches,thisreturnsthesumof
- *thesizesoftheentriesinthiscache.
- */
- publicsynchronizedfinalintsize(){
- returnsize;
- }
- /**
- *Forcachesthatdonotoverride{@link#sizeOf},thisreturnsthemaximum
- *numberofentriesinthecache.Forallothercaches,thisreturnsthe
- *maximumsumofthesizesoftheentriesinthiscache.
- */
- publicsynchronizedfinalintmaxSize(){
- returnmaxSize;
- }
- /**
- *Returnsthenumberoftimes{@link#get}returnedavaluethatwas
- *alreadypresentinthecache.
- */
- publicsynchronizedfinalinthitCount(){
- returnhitCount;
- }
- /**
- *Returnsthenumberoftimes{@link#get}returnednullorrequiredanew
- *valuetobecreated.
- */
- publicsynchronizedfinalintmissCount(){
- returnmissCount;
- }
- /**
- *Returnsthenumberoftimes{@link#create(Object)}returnedavalue.
- */
- publicsynchronizedfinalintcreateCount(){
- returncreateCount;
- }
- /**
- *Returnsthenumberoftimes{@link#put}wascalled.
- */
- publicsynchronizedfinalintputCount(){
- returnputCount;
- }
- /**
- *Returnsthenumberofvaluesthathavebeenevicted.
- */
- publicsynchronizedfinalintevictionCount(){
- returnevictionCount;
- }
- /**
- *Returnsacopyofthecurrentcontentsofthecache,orderedfromleast
- *recentlyaccessedtomostrecentlyaccessed.
- */
- publicsynchronizedfinalMap<K,V>snapshot(){
- returnnewLinkedHashMap<K,V>(map);
- }
- @OverridepublicsynchronizedfinalStringtoString(){
- intaccesses=hitCount+missCount;
- inthitPercent=accesses!=0?(100*hitCount/accesses):0;
- returnString.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
- maxSize,hitCount,missCount,hitPercent);
- }
- }
更多相关文章
- android 一张图片实现 ImageView 实现 点击效果 图片明度变化
- Android清除数据、清除缓存、一键清理的区别
- 浅谈Android中的基础动画(图文详解)
- Android缓存机制Lrucache内存缓存和DiskLruCache磁盘缓存
- ReactNative之Image在Android设置圆角图片变形问题
- Android(安卓)OpenGLES2.0(八)——纹理贴图之显示图片
- Android(安卓)各种图片转黑白图和抖动算法的黑白图
- Android(安卓)各种菜单,弹出菜单,打开文件子菜单,文本框的复制粘贴
- Android(安卓)compress图片压缩介绍