`
810364804
  • 浏览: 779679 次
文章分类
社区版块
存档分类
最新评论

Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

 
阅读更多

转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/26810303),请尊重他人的辛勤劳动成果,谢谢!

本篇文章继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,如果大家对这个开源框架的使用还不了解,大家可以看看我之前写的一篇文章Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用,我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存,我之前也写了几篇异步加载大量图片的文章,使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近期最少使用算法,我们可以给LruCache设定一个缓存图片的最大值,它会自动帮我们管理好缓存的图片总大小是否超过我们设定的值, 超过就删除近期最少使用的图片,而作为一个强大的图片加载框架,Universal-Image-Loader自然也提供了多种图片的缓存策略,下面就来详细的介绍下


内存缓存


首先我们来了解下什么是强引用和什么是弱引用?

强引用是指创建一个对象并把这个对象赋给一个引用变量,强引用有引用变量指向时永远不会被垃圾回收。即使内存不足的时候宁愿报OOM也不被垃圾回收器回收,我们new的对象都是强引用

弱引用通过weakReference类来实现,它具有很强的不确定性,如果垃圾回收器扫描到有着WeakReference的对象,就会将其回收释放内存


现在我们来看Universal-Image-Loader有哪些内存缓存策略

1. 只使用的是强引用缓存

  • LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用,下面我会从源码上面分析这个类)

2.使用强引用和弱引用相结合的缓存有

  • UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
  • LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)
  • FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)
  • LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
  • LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)

3.只使用弱引用缓存

  • WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)

上面介绍了Universal-Image-Loader所提供的所有的内存缓存的类,当然我们也可以使用我们自己写的内存缓存类,我们还要看看要怎么将这些内存缓存加入到我们的项目中,我们只需要配置ImageLoaderConfiguration.memoryCache(...),如下

  1. ImageLoaderConfigurationconfiguration=newImageLoaderConfiguration.Builder(this)
  2. .memoryCache(newWeakMemoryCache())
  3. .build();

下面我们来分析LruMemoryCache这个类的源代码

  1. packagecom.nostra13.universalimageloader.cache.memory.impl;
  2. importandroid.graphics.Bitmap;
  3. importcom.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
  4. importjava.util.Collection;
  5. importjava.util.HashSet;
  6. importjava.util.LinkedHashMap;
  7. importjava.util.Map;
  8. /**
  9. *AcachethatholdsstrongreferencestoalimitednumberofBitmaps.EachtimeaBitmapisaccessed,itismovedto
  10. *theheadofaqueue.WhenaBitmapisaddedtoafullcache,theBitmapattheendofthatqueueisevictedandmay
  11. *becomeeligibleforgarbagecollection.<br/>
  12. *<br/>
  13. *<b>NOTE:</b>ThiscacheusesonlystrongreferencesforstoredBitmaps.
  14. *
  15. *@authorSergeyTarasevich(nostra13[at]gmail[dot]com)
  16. *@since1.8.1
  17. */
  18. publicclassLruMemoryCacheimplementsMemoryCacheAware<String,Bitmap>{
  19. privatefinalLinkedHashMap<String,Bitmap>map;
  20. privatefinalintmaxSize;
  21. /**Sizeofthiscacheinbytes*/
  22. privateintsize;
  23. /**@parammaxSizeMaximumsumofthesizesoftheBitmapsinthiscache*/
  24. publicLruMemoryCache(intmaxSize){
  25. if(maxSize<=0){
  26. thrownewIllegalArgumentException("maxSize<=0");
  27. }
  28. this.maxSize=maxSize;
  29. this.map=newLinkedHashMap<String,Bitmap>(0,0.75f,true);
  30. }
  31. /**
  32. *ReturnstheBitmapfor{@codekey}ifitexistsinthecache.IfaBitmapwasreturned,itismovedtothehead
  33. *ofthequeue.ThisreturnsnullifaBitmapisnotcached.
  34. */
  35. @Override
  36. publicfinalBitmapget(Stringkey){
  37. if(key==null){
  38. thrownewNullPointerException("key==null");
  39. }
  40. synchronized(this){
  41. returnmap.get(key);
  42. }
  43. }
  44. /**Caches{@codeBitmap}for{@codekey}.TheBitmapismovedtotheheadofthequeue.*/
  45. @Override
  46. publicfinalbooleanput(Stringkey,Bitmapvalue){
  47. if(key==null||value==null){
  48. thrownewNullPointerException("key==null||value==null");
  49. }
  50. synchronized(this){
  51. size+=sizeOf(key,value);
  52. Bitmapprevious=map.put(key,value);
  53. if(previous!=null){
  54. size-=sizeOf(key,previous);
  55. }
  56. }
  57. trimToSize(maxSize);
  58. returntrue;
  59. }
  60. /**
  61. *Removetheeldestentriesuntilthetotalofremainingentriesisatorbelowtherequestedsize.
  62. *
  63. *@parammaxSizethemaximumsizeofthecachebeforereturning.Maybe-1toevicteven0-sizedelements.
  64. */
  65. privatevoidtrimToSize(intmaxSize){
  66. while(true){
  67. Stringkey;
  68. Bitmapvalue;
  69. synchronized(this){
  70. if(size<0||(map.isEmpty()&&size!=0)){
  71. thrownewIllegalStateException(getClass().getName()+".sizeOf()isreportinginconsistentresults!");
  72. }
  73. if(size<=maxSize||map.isEmpty()){
  74. break;
  75. }
  76. Map.Entry<String,Bitmap>toEvict=map.entrySet().iterator().next();
  77. if(toEvict==null){
  78. break;
  79. }
  80. key=toEvict.getKey();
  81. value=toEvict.getValue();
  82. map.remove(key);
  83. size-=sizeOf(key,value);
  84. }
  85. }
  86. }
  87. /**Removestheentryfor{@codekey}ifitexists.*/
  88. @Override
  89. publicfinalvoidremove(Stringkey){
  90. if(key==null){
  91. thrownewNullPointerException("key==null");
  92. }
  93. synchronized(this){
  94. Bitmapprevious=map.remove(key);
  95. if(previous!=null){
  96. size-=sizeOf(key,previous);
  97. }
  98. }
  99. }
  100. @Override
  101. publicCollection<String>keys(){
  102. synchronized(this){
  103. returnnewHashSet<String>(map.keySet());
  104. }
  105. }
  106. @Override
  107. publicvoidclear(){
  108. trimToSize(-1);//-1willevict0-sizedelements
  109. }
  110. /**
  111. *Returnsthesize{@codeBitmap}inbytes.
  112. *<p/>
  113. *Anentry'ssizemustnotchangewhileitisinthecache.
  114. */
  115. privateintsizeOf(Stringkey,Bitmapvalue){
  116. returnvalue.getRowBytes()*value.getHeight();
  117. }
  118. @Override
  119. publicsynchronizedfinalStringtoString(){
  120. returnString.format("LruCache[maxSize=%d]",maxSize);
  121. }
  122. }
我们可以看到这个类中维护的是一个LinkedHashMap,在LruMemoryCache构造函数中我们可以看到,我们为其设置了一个缓存图片的最大值maxSize,并实例化LinkedHashMap, 而从LinkedHashMap构造函数的第三个参数为ture,表示它是按照访问顺序进行排序的,
我们来看将bitmap加入到LruMemoryCache的方法put(String key, Bitmap value), 第61行,sizeOf()是计算每张图片所占的byte数,size是记录当前缓存bitmap的总大小,如果该key之前就缓存了bitmap,我们需要将之前的bitmap减掉去,接下来看trimToSize()方法,我们直接看86行,如果当前缓存的bitmap总数小于设定值maxSize,不做任何处理,如果当前缓存的bitmap总数大于maxSize,删除LinkedHashMap中的第一个元素,size中减去该bitmap对应的byte数

我们可以看到该缓存类比较简单,逻辑也比较清晰,如果大家想知道其他内存缓存的逻辑,可以去分析分析其源码,在这里我简单说下FIFOLimitedMemoryCache的实现逻辑,该类使用的HashMap来缓存bitmap的弱引用,然后使用LinkedList来保存成功加入到FIFOLimitedMemoryCache的bitmap的强引用,如果加入的FIFOLimitedMemoryCache的bitmap总数超过限定值,直接删除LinkedList的第一个元素,所以就实现了先进先出的缓存策略,其他的缓存都类似,有兴趣的可以去看看。


硬盘缓存


接下来就给大家分析分析硬盘缓存的策略,这个框架也提供了几种常见的缓存策略,当然如果你觉得都不符合你的要求,你也可以自己去扩展

  • FileCountLimitedDiscCache(可以设定缓存图片的个数,当超过设定值,删除掉最先加入到硬盘的文件)
  • LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)
  • TotalSizeLimitedDiscCache(设定缓存bitmap的最大值,当超过这个值,删除最先加入到硬盘的文件)
  • UnlimitedDiscCache(这个缓存类没有任何的限制)

下面我们就来分析分析TotalSizeLimitedDiscCache的源码实现

  1. /*******************************************************************************
  2. *Copyright2011-2013SergeyTarasevich
  3. *
  4. *LicensedundertheApacheLicense,Version2.0(the"License");
  5. *youmaynotusethisfileexceptincompliancewiththeLicense.
  6. *YoumayobtainacopyoftheLicenseat
  7. *
  8. *http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  11. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  12. *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
  13. *SeetheLicenseforthespecificlanguagegoverningpermissionsand
  14. *limitationsundertheLicense.
  15. *******************************************************************************/
  16. packagecom.nostra13.universalimageloader.cache.disc.impl;
  17. importcom.nostra13.universalimageloader.cache.disc.LimitedDiscCache;
  18. importcom.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
  19. importcom.nostra13.universalimageloader.core.DefaultConfigurationFactory;
  20. importcom.nostra13.universalimageloader.utils.L;
  21. importjava.io.File;
  22. /**
  23. *Disccachelimitedbytotalcachesize.Ifcachesizeexceedsspecifiedlimitthenfilewiththemostoldestlast
  24. *usagedatewillbedeleted.
  25. *
  26. *@authorSergeyTarasevich(nostra13[at]gmail[dot]com)
  27. *@seeLimitedDiscCache
  28. *@since1.0.0
  29. */
  30. publicclassTotalSizeLimitedDiscCacheextendsLimitedDiscCache{
  31. privatestaticfinalintMIN_NORMAL_CACHE_SIZE_IN_MB=2;
  32. privatestaticfinalintMIN_NORMAL_CACHE_SIZE=MIN_NORMAL_CACHE_SIZE_IN_MB*1024*1024;
  33. /**
  34. *@paramcacheDirDirectoryforfilecaching.<b>Important:</b>Specifyseparatefolderforcachedfiles.It's
  35. *neededforrightcachelimitwork.
  36. *@parammaxCacheSizeMaximumcachedirectorysize(inbytes).Ifcachesizeexceedsthislimitthenfilewiththe
  37. *mostoldestlastusagedatewillbedeleted.
  38. */
  39. publicTotalSizeLimitedDiscCache(FilecacheDir,intmaxCacheSize){
  40. this(cacheDir,DefaultConfigurationFactory.createFileNameGenerator(),maxCacheSize);
  41. }
  42. /**
  43. *@paramcacheDirDirectoryforfilecaching.<b>Important:</b>Specifyseparatefolderforcachedfiles.It's
  44. *neededforrightcachelimitwork.
  45. *@paramfileNameGeneratorNamegeneratorforcachedfiles
  46. *@parammaxCacheSizeMaximumcachedirectorysize(inbytes).Ifcachesizeexceedsthislimitthenfilewiththe
  47. *mostoldestlastusagedatewillbedeleted.
  48. */
  49. publicTotalSizeLimitedDiscCache(FilecacheDir,FileNameGeneratorfileNameGenerator,intmaxCacheSize){
  50. super(cacheDir,fileNameGenerator,maxCacheSize);
  51. if(maxCacheSize<MIN_NORMAL_CACHE_SIZE){
  52. L.w("Yousettoosmalldisccachesize(lessthan%1$dMb)",MIN_NORMAL_CACHE_SIZE_IN_MB);
  53. }
  54. }
  55. @Override
  56. protectedintgetSize(Filefile){
  57. return(int)file.length();
  58. }
  59. }
这个类是继承LimitedDiscCache,除了两个构造函数之外,还重写了getSize()方法,返回文件的大小,接下来我们就来看看LimitedDiscCache
  1. /*******************************************************************************
  2. *Copyright2011-2013SergeyTarasevich
  3. *
  4. *LicensedundertheApacheLicense,Version2.0(the"License");
  5. *youmaynotusethisfileexceptincompliancewiththeLicense.
  6. *YoumayobtainacopyoftheLicenseat
  7. *
  8. *http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  11. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  12. *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
  13. *SeetheLicenseforthespecificlanguagegoverningpermissionsand
  14. *limitationsundertheLicense.
  15. *******************************************************************************/
  16. packagecom.nostra13.universalimageloader.cache.disc;
  17. importcom.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
  18. importcom.nostra13.universalimageloader.core.DefaultConfigurationFactory;
  19. importjava.io.File;
  20. importjava.util.Collections;
  21. importjava.util.HashMap;
  22. importjava.util.Map;
  23. importjava.util.Map.Entry;
  24. importjava.util.Set;
  25. importjava.util.concurrent.atomic.AtomicInteger;
  26. /**
  27. *Abstractdisccachelimitedbysomeparameter.Ifcacheexceedsspecifiedlimitthenfilewiththemostoldestlast
  28. *usagedatewillbedeleted.
  29. *
  30. *@authorSergeyTarasevich(nostra13[at]gmail[dot]com)
  31. *@seeBaseDiscCache
  32. *@seeFileNameGenerator
  33. *@since1.0.0
  34. */
  35. publicabstractclassLimitedDiscCacheextendsBaseDiscCache{
  36. privatestaticfinalintINVALID_SIZE=-1;
  37. //记录缓存文件的大小
  38. privatefinalAtomicIntegercacheSize;
  39. //缓存文件的最大值
  40. privatefinalintsizeLimit;
  41. privatefinalMap<File,Long>lastUsageDates=Collections.synchronizedMap(newHashMap<File,Long>());
  42. /**
  43. *@paramcacheDirDirectoryforfilecaching.<b>Important:</b>Specifyseparatefolderforcachedfiles.It's
  44. *neededforrightcachelimitwork.
  45. *@paramsizeLimitCachelimitvalue.Ifcacheexceedsthislimitthenfilewiththemostoldestlastusagedate
  46. *willbedeleted.
  47. */
  48. publicLimitedDiscCache(FilecacheDir,intsizeLimit){
  49. this(cacheDir,DefaultConfigurationFactory.createFileNameGenerator(),sizeLimit);
  50. }
  51. /**
  52. *@paramcacheDirDirectoryforfilecaching.<b>Important:</b>Specifyseparatefolderforcachedfiles.It's
  53. *neededforrightcachelimitwork.
  54. *@paramfileNameGeneratorNamegeneratorforcachedfiles
  55. *@paramsizeLimitCachelimitvalue.Ifcacheexceedsthislimitthenfilewiththemostoldestlastusagedate
  56. *willbedeleted.
  57. */
  58. publicLimitedDiscCache(FilecacheDir,FileNameGeneratorfileNameGenerator,intsizeLimit){
  59. super(cacheDir,fileNameGenerator);
  60. this.sizeLimit=sizeLimit;
  61. cacheSize=newAtomicInteger();
  62. calculateCacheSizeAndFillUsageMap();
  63. }
  64. /**
  65. *另开线程计算cacheDir里面文件的大小,并将文件和最后修改的毫秒数加入到Map中
  66. */
  67. privatevoidcalculateCacheSizeAndFillUsageMap(){
  68. newThread(newRunnable(){
  69. @Override
  70. publicvoidrun(){
  71. intsize=0;
  72. File[]cachedFiles=cacheDir.listFiles();
  73. if(cachedFiles!=null){//rarelybutitcanhappen,don'tknowwhy
  74. for(FilecachedFile:cachedFiles){
  75. //getSize()是一个抽象方法,子类自行实现getSize()的逻辑
  76. size+=getSize(cachedFile);
  77. //将文件的最后修改时间加入到map中
  78. lastUsageDates.put(cachedFile,cachedFile.lastModified());
  79. }
  80. cacheSize.set(size);
  81. }
  82. }
  83. }).start();
  84. }
  85. /**
  86. *将文件添加到Map中,并计算缓存文件的大小是否超过了我们设置的最大缓存数
  87. *超过了就删除最先加入的那个文件
  88. */
  89. @Override
  90. publicvoidput(Stringkey,Filefile){
  91. //要加入文件的大小
  92. intvalueSize=getSize(file);
  93. //获取当前缓存文件大小总数
  94. intcurCacheSize=cacheSize.get();
  95. //判断是否超过设定的最大缓存值
  96. while(curCacheSize+valueSize>sizeLimit){
  97. intfreedSize=removeNext();
  98. if(freedSize==INVALID_SIZE)break;//cacheisempty(havenothingtodelete)
  99. curCacheSize=cacheSize.addAndGet(-freedSize);
  100. }
  101. cacheSize.addAndGet(valueSize);
  102. LongcurrentTime=System.currentTimeMillis();
  103. file.setLastModified(currentTime);
  104. lastUsageDates.put(file,currentTime);
  105. }
  106. /**
  107. *根据key生成文件
  108. */
  109. @Override
  110. publicFileget(Stringkey){
  111. Filefile=super.get(key);
  112. LongcurrentTime=System.currentTimeMillis();
  113. file.setLastModified(currentTime);
  114. lastUsageDates.put(file,currentTime);
  115. returnfile;
  116. }
  117. /**
  118. *硬盘缓存的清理
  119. */
  120. @Override
  121. publicvoidclear(){
  122. lastUsageDates.clear();
  123. cacheSize.set(0);
  124. super.clear();
  125. }
  126. /**
  127. *获取最早加入的缓存文件,并将其删除
  128. */
  129. privateintremoveNext(){
  130. if(lastUsageDates.isEmpty()){
  131. returnINVALID_SIZE;
  132. }
  133. LongoldestUsage=null;
  134. FilemostLongUsedFile=null;
  135. Set<Entry<File,Long>>entries=lastUsageDates.entrySet();
  136. synchronized(lastUsageDates){
  137. for(Entry<File,Long>entry:entries){
  138. if(mostLongUsedFile==null){
  139. mostLongUsedFile=entry.getKey();
  140. oldestUsage=entry.getValue();
  141. }else{
  142. LonglastValueUsage=entry.getValue();
  143. if(lastValueUsage<oldestUsage){
  144. oldestUsage=lastValueUsage;
  145. mostLongUsedFile=entry.getKey();
  146. }
  147. }
  148. }
  149. }
  150. intfileSize=0;
  151. if(mostLongUsedFile!=null){
  152. if(mostLongUsedFile.exists()){
  153. fileSize=getSize(mostLongUsedFile);
  154. if(mostLongUsedFile.delete()){
  155. lastUsageDates.remove(mostLongUsedFile);
  156. }
  157. }else{
  158. lastUsageDates.remove(mostLongUsedFile);
  159. }
  160. }
  161. returnfileSize;
  162. }
  163. /**
  164. *抽象方法,获取文件大小
  165. *@paramfile
  166. *@return
  167. */
  168. protectedabstractintgetSize(Filefile);
  169. }
在构造方法中,第69行有一个方法calculateCacheSizeAndFillUsageMap(),该方法是计算cacheDir的文件大小,并将文件和文件的最后修改时间加入到Map中

然后是将文件加入硬盘缓存的方法put(),在106行判断当前文件的缓存总数加上即将要加入缓存的文件大小是否超过缓存设定值,如果超过了执行removeNext()方法,接下来就来看看这个方法的具体实现,150-167中找出最先加入硬盘的文件,169-180中将其从文件硬盘中删除,并返回该文件的大小,删除成功之后成员变量cacheSize需要减掉改文件大小。

FileCountLimitedDiscCache这个类实现逻辑跟TotalSizeLimitedDiscCache是一样的,区别在于getSize()方法,前者返回1,表示为文件数是1,后者返回文件的大小。

等我写完了这篇文章,我才发现FileCountLimitedDiscCache和TotalSizeLimitedDiscCache在最新的源码中已经删除了,加入了LruDiscCache,由于我的是之前的源码,所以我也不改了,大家如果想要了解LruDiscCache可以去看最新的源码,我这里就不介绍了,还好内存缓存的没变化,下面分析的是最新的源码中的部分,我们在使用中可以不自行配置硬盘缓存策略,直接用DefaultConfigurationFactory中的就行了

我们看DefaultConfigurationFactory这个类的createDiskCache()方法

  1. /**
  2. *Createsdefaultimplementationof{@linkDiskCache}dependsonincomingparameters
  3. */
  4. publicstaticDiskCachecreateDiskCache(Contextcontext,FileNameGeneratordiskCacheFileNameGenerator,
  5. longdiskCacheSize,intdiskCacheFileCount){
  6. FilereserveCacheDir=createReserveDiskCacheDir(context);
  7. if(diskCacheSize>0||diskCacheFileCount>0){
  8. FileindividualCacheDir=StorageUtils.getIndividualCacheDirectory(context);
  9. LruDiscCachediskCache=newLruDiscCache(individualCacheDir,diskCacheFileNameGenerator,diskCacheSize,
  10. diskCacheFileCount);
  11. diskCache.setReserveCacheDir(reserveCacheDir);
  12. returndiskCache;
  13. }else{
  14. FilecacheDir=StorageUtils.getCacheDirectory(context);
  15. returnnewUnlimitedDiscCache(cacheDir,reserveCacheDir,diskCacheFileNameGenerator);
  16. }
  17. }
如果我们在ImageLoaderConfiguration中配置了diskCacheSize和diskCacheFileCount,他就使用的是LruDiscCache,否则使用的是UnlimitedDiscCache,在最新的源码中还有一个硬盘缓存类可以配置,那就是LimitedAgeDiscCache,可以在ImageLoaderConfiguration.diskCache(...)配置

今天就给大家分享到这里,有不明白的地方在下面留言,我会尽量为大家解答的,下一篇文章我将继续更深入的分析这个框架,希望大家继续关注!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics