ImageLoader中如何实现磁盘命名和图片缓存算法
ImageLoader中如何实现磁盘命名和图片缓存算法
这篇文章主要为大家展示了“ImageLoader中如何实现磁盘命名和图片缓存算法”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“ImageLoader中如何实现磁盘命名和图片缓存算法”这篇文章吧。
一. 前言
ImageLoader的图片缓存分成磁盘和内存两种,这里分析一下磁盘缓存以及图片文件名算法的实现
默认是不存储在磁盘上的,需要手动打开开关
如下
DisplayImageOptionsoptions=newDisplayImageOptions.Builder().cacheInMemory(true)//defaultfalse.cacheOnDisk(true)//defaultfalseimageLoader.displayImage("",imageView,options,null,null);
二. 磁盘文件命名
/***Generatesnamesforfilesatdiskcache**@authorSergeyTarasevich(nostra13[at]gmail[dot]com)*@since1.3.1*/publicinterfaceFileNameGenerator{/**GeneratesuniquefilenameforimagedefinedbyURI*/Stringgenerate(StringimageUri);}
接口是FileNameGenerator,此接口非常简单明了,只有一个根据图片uri产生一个图片文件名称的方法。
它包含两个实现类
HashCodeFileNameGenerator
Md5FileNameGenerator
接下来,分别看这两个类的实现
2.1 HashCodeFileNameGenerator
/***NamesimagefileasimageURI{@linkplainString#hashCode()hashcode}**@authorSergeyTarasevich(nostra13[at]gmail[dot]com)*@since1.3.1*/publicclassHashCodeFileNameGeneratorimplementsFileNameGenerator{@OverridepublicStringgenerate(StringimageUri){returnString.valueOf(imageUri.hashCode());}}
实现比较简单,根据uri的hashcode转化成String即可,默认就是Hashcode命名。
2.2 Md5FileNameGenerator
/***NamesimagefileasMD5hashofimageURI**@authorSergeyTarasevich(nostra13[at]gmail[dot]com)*@since1.4.0*/publicclassMd5FileNameGeneratorimplementsFileNameGenerator{privatestaticfinalStringHASH_ALGORITHM="MD5";privatestaticfinalintRADIX=10+26;//10digits+26letters@OverridepublicStringgenerate(StringimageUri){byte[]md5=getMD5(imageUri.getBytes());BigIntegerbi=newBigInteger(md5).abs();returnbi.toString(RADIX);}privatebyte[]getMD5(byte[]data){byte[]hash=null;try{MessageDigestdigest=MessageDigest.getInstance(HASH_ALGORITHM);digest.update(data);hash=digest.digest();}catch(NoSuchAlgorithmExceptione){L.e(e);}returnhash;}}
通过imageUri得到byte数组,然后通过MD5算法得到文件名
三. 磁盘目录选择
一般默认优先选择sdk/android/data/packageName/cache/uil-images卡,如果sdk目录创建失败,那么会选择/data/data/packageName目录
四. 图片缓存示例
其中-1557665659.0和1238391484.0两个就是图片存储文件
journal是操作记录描述性文件,内容如下
DIRTY: 操作记录创建,如果DIRTY后面没有CLEAN或者REMOVE,那么这个图片会被删除。
CLEAN: 记录成功创建和访问
READ: 记录成功访问
REMOVE: 记录删除
五. 磁盘缓存接口
磁盘缓存算法的接口是DiskCache,接口很简单明了。
publicinterfaceDiskCache{FilegetDirectory();Fileget(StringimageUri);booleansave(StringimageUri,InputStreamimageStream,IoUtils.CopyListenerlistener)throwsIOException;booleansave(StringimageUri,Bitmapbitmap)throwsIOException;booleanremove(StringimageUri);voidclose();voidclear();}
方法名 | 解释 |
---|---|
getDirectory() | 获取存储目录 |
get(String imageUri) | 根据imageUri获取图片文件 |
save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) | 保存图片 |
remove(String imageUri) | 删除图片缓存 |
close() | 关闭磁盘缓存,释放资源 |
clear() | 清理所有的磁盘缓存 |
5.1 实现类
下面详细看每个类的实现
六. LruDiskCache
publicclassLruDiskCacheimplementsDiskCache{protectedDiskLruCachecache;...protectedfinalFileNameGeneratorfileNameGenerator;...publicLruDiskCache(FilecacheDir,FilereserveCacheDir,FileNameGeneratorfileNameGenerator,longcacheMaxSize,intcacheMaxFileCount)throwsIOException{...initCache(cacheDir,reserveCacheDir,cacheMaxSize,cacheMaxFileCount);}privatevoidinitCache(FilecacheDir,FilereserveCacheDir,longcacheMaxSize,intcacheMaxFileCount)throwsIOException{try{cache=DiskLruCache.open(cacheDir,1,1,cacheMaxSize,cacheMaxFileCount);}catch(IOExceptione){...}}@OverridepublicFilegetDirectory(){returncache.getDirectory();}@OverridepublicFileget(StringimageUri){DiskLruCache.Snapshotsnapshot=null;try{snapshot=cache.get(getKey(imageUri));returnsnapshot==null?null:snapshot.getFile(0);}catch(IOExceptione){L.e(e);returnnull;}finally{if(snapshot!=null){snapshot.close();}}}@Overridepublicbooleansave(StringimageUri,InputStreamimageStream,IoUtils.CopyListenerlistener)throwsIOException{DiskLruCache.Editoreditor=cache.edit(getKey(imageUri));if(editor==null){returnfalse;}OutputStreamos=newBufferedOutputStream(editor.newOutputStream(0),bufferSize);booleancopied=false;try{copied=IoUtils.copyStream(imageStream,os,listener,bufferSize);}finally{IoUtils.closeSilently(os);if(copied){editor.commit();}else{editor.abort();}}returncopied;}...@Overridepublicbooleanremove(StringimageUri){try{returncache.remove(getKey(imageUri));}catch(IOExceptione){L.e(e);returnfalse;}}@Overridepublicvoidclose(){try{cache.close();}catch(IOExceptione){L.e(e);}cache=null;}@Overridepublicvoidclear(){try{cache.delete();}catch(IOExceptione){L.e(e);}try{initCache(cache.getDirectory(),reserveCacheDir,cache.getMaxSize(),cache.getMaxFileCount());}catch(IOExceptione){L.e(e);}}privateStringgetKey(StringimageUri){returnfileNameGenerator.generate(imageUri);}}
LruDiskCache有几个比较重要的属性,
protectedDiskLruCachecache;protectedfinalFileNameGeneratorfileNameGenerator;
FileNameGenerator就是上面说的文件命名生成器,包括hashcode和md5算法。我们思考下,为什么需要FileNameGenerator?
个人以为网络上面的uri可能是千奇百怪的,甚至包括特殊字符,那作为文件名显然不合适。所以,这个时候来一次hashcode,或者md5转换,获取文件名是最好的。
DiskLruCache,窃以为这个命名不是很好,因为跟LruDiskCache很类似(我第一眼就看成一个东西了!)
这个DiskLruCache很重要,它维护了磁盘图片文件缓存的操作记录,缓存和文件对应关系等。
而且如果你仔细看LruDiskCache的各个方法时会发现,基本都是调用cache的对应方法。
所以,我们主要接下来看DiskLruCache代码
finalclassDiskLruCacheimplementsCloseable{...privatefinalFiledirectory;privatefinalFilejournalFile;...privateWriterjournalWriter;privatefinalLinkedHashMap
DiskLruCache包含了journalFile,文件里面具体的含义可以第四点的样例。包含了
LinkedHashMap
表示每个图片的缓存记录,String表示key, Entry表示图片的描述信息
privatefinalclassEntry{privatefinalStringkey;/**Lengthsofthisentry'sfiles.*/privatefinallong[]lengths;/**Trueifthisentryhaseverbeenpublished.*/privatebooleanreadable;/**Theongoingeditornullifthisentryisnotbeingedited.*/privateEditorcurrentEditor;/**Thesequencenumberofthemostrecentlycommittededittothisentry.*/privatelongsequenceNumber;publicFilegetCleanFile(inti){returnnewFile(directory,key+"."+i);}publicFilegetDirtyFile(inti){returnnewFile(directory,key+"."+i+".tmp");}}
我们以保存图片缓存为例,分析下LruDiskCache的工作流程,首先看LruDiskCache的save方法
publicbooleansave(StringimageUri,InputStreamimageStream,IoUtils.CopyListenerlistener)throwsIOException{DiskLruCache.Editoreditor=cache.edit(getKey(imageUri));if(editor==null){returnfalse;}OutputStreamos=newBufferedOutputStream(editor.newOutputStream(0),bufferSize);booleancopied=false;try{copied=IoUtils.copyStream(imageStream,os,listener,bufferSize);}finally{IoUtils.closeSilently(os);if(copied){editor.commit();}else{editor.abort();}}returncopied;}
6.1 getkey(imageUri)
首先根据imageUri生成文件名,也就是key,目前我们用的是hashCode
privateStringgetKey(StringimageUri){returnfileNameGenerator.generate(imageUri);}
6.2 cache.edit
privatesynchronizedEditoredit(Stringkey,longexpectedSequenceNumber)throwsIOException{checkNotClosed();validateKey(key);Entryentry=lruEntries.get(key);if(expectedSequenceNumber!=ANY_SEQUENCE_NUMBER&&(entry==null||entry.sequenceNumber!=expectedSequenceNumber)){returnnull;//Snapshotisstale.}if(entry==null){entry=newEntry(key);lruEntries.put(key,entry);}elseif(entry.currentEditor!=null){returnnull;//Anothereditisinprogress.}Editoreditor=newEditor(entry);entry.currentEditor=editor;//Flushthejournalbeforecreatingfilestopreventfileleaks.journalWriter.write(DIRTY+''+key+'\n');journalWriter.flush();returneditor;}
从lruEntries里面根据key获取到对应的图片Entry对象,如果没有就新建一个。
然后利用journalWriter写入一条DIRTY记录。
6.3 DiskLruCache 打开Dirty图片文件流
publicOutputStreamnewOutputStream(intindex)throwsIOException{synchronized(DiskLruCache.this){if(entry.currentEditor!=this){thrownewIllegalStateException();}if(!entry.readable){written[index]=true;}FiledirtyFile=entry.getDirtyFile(index);FileOutputStreamoutputStream;try{outputStream=newFileOutputStream(dirtyFile);}catch(FileNotFoundExceptione){//Attempttorecreatethecachedirectory.directory.mkdirs();try{outputStream=newFileOutputStream(dirtyFile);}catch(FileNotFoundExceptione2){//Weareunabletorecover.Silentlyeatthewrites.returnNULL_OUTPUT_STREAM;}}returnnewFaultHidingOutputStream(outputStream);}}
publicFilegetDirtyFile(inti){returnnewFile(directory,key+"."+i+".tmp");}
注意这里打开的是drity文件,就是正常的文件后面加上一个.tmp后缀。
6.4 copyStream把网络图片流写入Dirty文件
publicstaticbooleancopyStream(InputStreamis,OutputStreamos,CopyListenerlistener,intbufferSize)throwsIOException{intcurrent=0;inttotal=is.available();if(total<=0){total=DEFAULT_IMAGE_TOTAL_SIZE;}finalbyte[]bytes=newbyte[bufferSize];intcount;if(shouldStopLoading(listener,current,total))returnfalse;while((count=is.read(bytes,0,bufferSize))!=-1){os.write(bytes,0,count);current+=count;if(shouldStopLoading(listener,current,total))returnfalse;}os.flush();returntrue;}
privatestaticbooleanshouldStopLoading(CopyListenerlistener,intcurrent,inttotal){if(listener!=null){booleanshouldContinue=listener.onBytesCopied(current,total);if(!shouldContinue){if(100*current/total 很普通的文件流读写,有意思的是shouldStopLoading,它给了我们一个使用listener终止copy的时机。 publicstaticinterfaceCopyListener{/***@paramcurrentLoadedbytes*@paramtotalTotalbytesforloading*@returntrue-ifcopyingshouldbecontinued;false-ifcopyingshouldbeinterrupted*/booleanonBytesCopied(intcurrent,inttotal);} IoUtils.closeSilently(os); 假设没有出错,completeEdit里面,会把dirty文件正式名称成图片缓存文件 dirty.renameTo(clean); 然后写入一条CLEAN或者REMOVE操作日志到journal文件中。 具体可以看代码 editor.commit(); publicvoidcommit()throwsIOException{if(hasErrors){completeEdit(this,false);remove(entry.key);//Thepreviousentryisstale.}else{completeEdit(this,true);}committed=true;} privatesynchronizedvoidcompleteEdit(Editoreditor,booleansuccess)throwsIOException{...for(inti=0;i 这样一次文件保存操作就完成了。 BaseDiskCache是抽象类,实现了基本的图片文件存储,获取,删除等操作,并没有做什么限制。 如save和get, remove等操作 publicabstractclassBaseDiskCacheimplementsDiskCache{...protectedfinalFileNameGeneratorfileNameGenerator;...@OverridepublicFilegetDirectory(){returncacheDir;}@OverridepublicFileget(StringimageUri){returngetFile(imageUri);}@Overridepublicbooleansave(StringimageUri,InputStreamimageStream,IoUtils.CopyListenerlistener)throwsIOException{FileimageFile=getFile(imageUri);FiletmpFile=newFile(imageFile.getAbsolutePath()+TEMP_IMAGE_POSTFIX);booleanloaded=false;try{OutputStreamos=newBufferedOutputStream(newFileOutputStream(tmpFile),bufferSize);try{loaded=IoUtils.copyStream(imageStream,os,listener,bufferSize);}finally{IoUtils.closeSilently(os);}}finally{if(loaded&&!tmpFile.renameTo(imageFile)){loaded=false;}if(!loaded){tmpFile.delete();}}returnloaded;}@Overridepublicbooleanremove(StringimageUri){returngetFile(imageUri).delete();}@Overridepublicvoidclose(){//Nothingtodo}@Overridepublicvoidclear(){File[]files=cacheDir.listFiles();if(files!=null){for(Filef:files){f.delete();}}}protectedFilegetFile(StringimageUri){StringfileName=fileNameGenerator.generate(imageUri);Filedir=cacheDir;if(!cacheDir.exists()&&!cacheDir.mkdirs()){if(reserveCacheDir!=null&&(reserveCacheDir.exists()||reserveCacheDir.mkdirs())){dir=reserveCacheDir;}}returnnewFile(dir,fileName);}} 以save为例,首先会生成一个tmp文件,然后把网络图片文件流写入tmp文件。 OutputStreamos=newBufferedOutputStream(newFileOutputStream(tmpFile),loaded=IoUtils.copyStream(imageStream,os,listener,bufferSize); 然后把tmp文件重新名称成正式的文件 tmpFile.renameTo(imageFile) 和BaseDiskCache完全一样,并没有新的逻辑 限制存储时间的文件存储管理,当我们尝试获取缓存文件的时候会去删除时间过长的文件,存储的空间没有限制。 我们以save和get为例 privatefinalMap @Overridepublicbooleansave(StringimageUri,Bitmapbitmap)throwsIOException{booleansaved=super.save(imageUri,bitmap);rememberUsage(imageUri);returnsaved;} privatevoidrememberUsage(StringimageUri){Filefile=getFile(imageUri);longcurrentTime=System.currentTimeMillis();file.setLastModified(currentTime);loadingDates.put(file,currentTime);} save的时候,会调用rememberUsage方法,使用一个HashMap来存储缓存时间。 get @OverridepublicFileget(StringimageUri){Filefile=super.get(imageUri);if(file!=null&&file.exists()){booleancached;LongloadingDate=loadingDates.get(file);if(loadingDate==null){cached=false;loadingDate=file.lastModified();}else{cached=true;}if(System.currentTimeMillis()-loadingDate>maxFileAge){file.delete();loadingDates.remove(file);}elseif(!cached){loadingDates.put(file,loadingDate);}}returnfile;} get的时候会根据当前时间和缓存时间比较,如果大于maxFileAge,那么就删除它,从而实现了限制时间文件存储。 以上是“ImageLoader中如何实现磁盘命名和图片缓存算法”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注恰卡编程网行业资讯频道!6.5 关闭Dirty文件流
6.6 写入图片文件
七. BaseDiskCache
八. UnlimitedDiskCache
九. LimitedAgeDiskCache
推荐阅读
-
Web应用从零开始,初学者友好型开发教程
-
容器化最佳实践:Docker 与 Kubernetes 在微服务架构中的协同设计
-
AWS Cloud9 使用攻略:云端 IDE 如何无缝集成 Lambda 与 S3 服务?
-
Heroku vs AWS Elastic Beanstalk:快速部署 Web 应用的平台对比
-
Kubernetes 集群部署避坑:资源调度、服务发现与滚动更新策略
-
Docker 镜像优化指南:分层构建、瘦身技巧与多阶段编译实践
-
Postman 接口测试全流程:从 API 设计到自动化测试脚本编写
-
pytest 框架进阶:自定义 fixture、插件开发与持续集成集成方案
-
JUnit 5 新特性:参数化测试、扩展模型与微服务测试实践
-
Chrome DevTools 性能分析:FPS 监控、内存快照与网络请求优化指南