在移动应用中,我们一般将网络图片分为三个级别,第一级别是网络层,即根据图片的url地址可以找到服务器上相应图片,获取这一层的图片会消耗流量,所以我们希望可以获取后本地就永久使用,所以就会有接下来的缓存策略;第二层缓存是在手机内存层,是将第一层的图片下载到手机内存,这种缓存读取速度非常快,但当图片内存被回收时,图片自然就不会存在了,第三层则是在手机硬盘层,是会缓存到sd卡。但这一层相对于内存的读取速度会慢很多,所以,很好的协调这三层图片缓存就可以提升应用性能和用户体验。
秉着不重复造轮子原则,这里我采用Volley+LruCache+DiskLruCache三个谷歌官方认可的库来实现网络图片三级缓存。并且以“one line”风格来实现将网络图片显示在ImageView上,而无需关心任何缓存细节。
类库下载
- Volley是Goole在2013年Google I/O大会上推出了一个新的网络通信框架,它是开源的,你可以通过git来clone源码并倒入项目:
git clone https://android.googlesource.com/platform/frameworks/volley
这个地址可能会被和谐,所以你可以在这里下载完整的jar包:
http://cdn.saymagic.cn/150131132336.jar
2.LruCache这个类是Android3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。
3.DiskLruCache是非Google官方编写,但获得官方认证的硬盘缓存类,该类没有限定在Android内,所以理论上java应用也可以使用DiskLreCache来缓存。该类你可以在这个下载:http://cdn.saymagic.cn/150131132428.java
方法与流程
1.要想实现图片三级缓存,就需要将图片下载到本地,我们所有网络图片请求都是通过Volley来统一管理的,Volley需要我们声明一个RequesQueuetManager来维持请求队列,因此,我们首先定义RequesQueuetManager
类来管理RequesQueuetManager,代码如下:
publicclassRequesQueuetManager{
publicstaticRequestQueue mRequestQueue =Volley.newRequestQueue(DemoApplication.getInstance());
publicstaticvoid addRequest(Request<?> request,Objectobject){
if(object!=null){
request.setTag(object);
}
mRequestQueue.add(request);
}
publicstaticvoid cancelAll(Object tag){
mRequestQueue.cancelAll(tag);
}
}
因为RequestQueue需要一个Context类型参数,所以我们在Volley.newRequestQueue(DemoApplication.getInstance())
这句里传入了DemoApplication.getInstance()
,这个静态方法是什么呢?就是应用的application实例,我们自定义一个application,然后将这个application传入给RequestQueue,我们自定义的application如下:
publicclassDemoApplicationextendsApplication{
publicstaticString TAG;
privatestaticDemoApplication application;
publicstaticDemoApplication getInstance(){
return application;
}
@Override
publicvoid onCreate(){
super.onCreate();
TAG =this.getClass().getSimpleName();
application =this;
}
}
要记得将自定义的application添加到AndroidManifest.xml文件的application标签的name属性里:
2.RequestQueue是负责图片请求顺序的,具体的图片请求工作由Volley里的ImageLoader类来完成,new它的时候它会接收两个参数,一个是我们刚刚声明的RequestQueue请求队列,另外一个是ImageLoader.ImageCache
接口,看名字就知道,这个接口是方便我们写缓存用的,也就是说,我们接下来的二级缓存与三级缓存就是在实现此基础上进行的:
首先我们新建ImageLreCache
类来让它继承LreCache并实现ImageLoader.ImageCache接口,秉着父债子偿的原理,父类没有实现的接口子类就需要来实现,所以我们需要重写LreCache类的sizeOf
方法,须要重写ImageLoader.ImageCache的getBitmap
与putBitmap
方法。
于是该类下会有如下三个函数:
@Override
protectedint sizeOf(String key,Bitmap bitmap){
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.HONEYCOMB_MR1){
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes()* bitmap.getHeight();
}
@Override
publicBitmap getBitmap(String s){
returnget(s);
}
@Override
publicvoid putBitmap(String s,Bitmap bitmap){
put(s,bitmap)
}
sizeOf方法是LruCache 留给子类重写来衡量Bitmap大小的函数,因为LruCache里面会对size大小是否小于0进行判断,size小与0的Bitmap会抛出IllegalStateException
异常。
getBitmap与putBitmap方法是ImageLoader.ImageCache留给子类实现的接口,Volley在请求网络数据时会先回调getBitmap方法来查看缓存中是否有所需图片,有的话会直接使用缓存的图片,没有再去请求,同理,当Volley下载完图片后会来回调putBitmap方法来将图片进行缓存。所以,我们实现这两个方法,然后在方法里直接使用LreCache的get与set方法即可。
综上,我们就将二级缓存完成,接下来一鼓作气,在此基础上完成硬盘级的第三级缓存,DiskLruCache的使用方法会稍微有些复杂,首先,我们需要DiskLruCache实例,它的构造方法是私有的,所以我们需要通过它提供的open方法来生成。open原型如下:
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param appVersion
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws java.io.IOException if reading or writing the cache directory fails
*/
publicstaticDiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
throwsIOException{}
它会接收四个参数,directory是缓存路径对应的File类,appVersion代表缓存版本,valueCount代表每个key对应的缓存个数,一般为1,maxSize代表缓存文件最大size。所以,在Android里,directory与appVersion可以从系统中获得,因此我们会这样写:
privatestaticDiskLruCache mDiskLruCache =DiskLruCache.open(getDiskCacheDir(DemoApplication.getInstance(),CACHE_FOLDER_NAME),
getAppVersion(DemoApplication.getInstance()),1,10*1024*1024);
//该方法会判断当前sd卡是否存在,然后选择缓存地址
publicstaticFile getDiskCacheDir(Context context,String uniqueName){
String cachePath;
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
||!Environment.isExternalStorageRemovable()){
cachePath = context.getExternalCacheDir().getPath();
}else{
cachePath = context.getCacheDir().getPath();
}
returnnewFile(cachePath +File.separator + uniqueName);
}
//获得应用version号码
publicint getAppVersion(Context context){
try{
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);
return info.versionCode;
}catch(NameNotFoundException e){
e.printStackTrace();
}
return1;
}
在获得DiskLruCache实例后,我们就可以来完善 ImageLoader.ImageCache接口下的getBitmap与putBitmap方法。主要思想是在方法里多一层逻辑判断,当图片不在LruCache时,再次查询DiskLruCache中是否存在,存在的话去取出然后转换成Bitmap并返回。需要注意的是DiskLruCache关于缓存内容的读取与写入是通过其内部封装的Editor与Snapshot两给类来实现,所以代码会稍有些复杂,但是很好理解。完善后的代码如下:
@Override
publicBitmap getBitmap(String s){
String key = hashKeyForDisk(s);
try{
if(mDiskLruCache.get(key)==null){
returnget(s);
}else{
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
Bitmap bitmap =null;
if(snapShot !=null){
InputStreamis= snapShot.getInputStream(0);
bitmap =BitmapFactory.decodeStream(is);
}
return bitmap;
}
}catch(IOException e){
e.printStackTrace();
}
returnnull;
}
@Override
publicvoid putBitmap(String s,Bitmap bitmap){
put(s,bitmap);
String key = hashKeyForDisk(s);
try{
if(null== mDiskLruCache.get(key)){
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor !=null){
OutputStream outputStream = editor.newOutputStream(0);
if(bitmap.compress(CompressFormat.JPEG,100, outputStream)){
editor.commit();
}else{
editor.abort();
}
}
mDiskLruCache.flush();
}
}catch(IOException e){
e.printStackTrace();
}
}
这样,缓存类以完结,然我们回到这一步的最开始,有了这个实现ImageLoader.ImageCache接口的类就可以用来生成ImageLoader实例了:
publicstaticImageLoader mImageLoder =newImageLoader(RequesQueuetManager.mRequestQueue,newImageLreCache());
3.拿到mImageLoder之后我们就可以请求网络上的图片了,请求的函数为get,接收的参数为图片远程地址url,回调接口listener,图片需要的宽度maxWidth与高度maxHeight,这里比较难以理解的是listener参数,它是ImageLoader的内部接口ImageListener,主要是图片请求成功或者失败的两个回调方法,这里我们写一个统一生成isterner的函数:
publicstaticImageLoader.ImageListener getImageLinseter(finalImageView view,
finalBitmap defaultImageBitmap,finalBitmap errorImageBitmap){
returnnewImageLoader.ImageListener(){
@Override
publicvoid onResponse(ImageLoader.ImageContainer imageContainer,boolean b){
if(imageContainer.getBitmap()!=null){
view.setImageBitmap(imageContainer.getBitmap());
}elseif(defaultImageBitmap !=null){
view.setImageBitmap(defaultImageBitmap);
}
}
@Override
publicvoid onErrorResponse(VolleyError volleyError){
if(errorImageBitmap !=null){
view.setImageBitmap(errorImageBitmap);
}
}
};
}
这个函数接收view,defaultImageBitmap,errorImageBitmap三个参数,当图片请求成功后将下载后的bitmap显示到view上,失败则显示errorImageBitmap。
综上,我们可以封装一个函数来提供给外部,接收6个参数,实现”one line”式编程,让网络图片请求变的更容易。代码如下:
/**
* 外部调用次方法即可完成将url处图片现在view上,并自动实现内存和硬盘双缓存。
* @param url 远程url地址
* @param view 待现实图片的view
* @param defaultImageBitmap 默认显示的图片
* @param errorImageBitmap 网络出错时显示的图片
* @param maxWidtn
* @param maxHeight
*/
publicstaticImageLoader.ImageContainer loadImage(finalString url,finalImageView view,
finalBitmap defaultImageBitmap,finalBitmap errorImageBitmap,int maxWidtn,int maxHeight){
return mImageLoder.get(url, getImageLinseter(view,defaultImageBitmap,
errorImageBitmap),maxWidtn,maxHeight);
}
效果
我们先看一下它的使用方法,首先拿到控件:
ImageView iv =(ImageView) findViewById(R.id.iv_hello);
接着调用如下方法即可将图片http://ww2.sinaimg.cn/large/7cc829d3gw1eahy2zrjlxj20kd0a10t9.jpg
显示在上面的控件上:
ImageCacheManger.loadImage("http://ww2.sinaimg.cn/large/7cc829d3gw1eahy2zrjlxj20kd0a10t9.jpg", iv,
getBitmapFromResources(this, R.drawable.ic_launcher), getBitmapFromResources(this, R.drawable.error));
所谓无图无真相,这是请求成功的效果(在请求成功后,断网后第二次打开依然可以显示,因为缓存在本地硬盘里):
DiskLruCache缓存在硬盘中的文件:
代码
整个项目你可以在这里Clone或者下载:https://github.com/saymagic/PictureThreeCache
相关推荐
图片下载的缓存策略采用LruCache+软引用+DiskLruCache,希望对大家有所帮助!
图片的三级缓存策略,即内存、硬盘和网络(其实网络不算是缓存,姑且算是吧~~),内存缓存使用的时LRUCache,这是一个存放键值对的列表,如果内存不够会根据最近使用次数的多少来删除其中的一个item,最少使用的就会...
图片下载的缓存策略采用LruCache+软引用+DiskLruCache,希望对大家有所帮助!
三级缓存策略,最实在的意义就是 减少不必要的流量消耗,增加加载速度 。 如今的 APP 网络交互似乎已经必不可少,通过网络获取图片再正常不过了。但是,每次启动应用都要从网络获取图片,或者是想重复浏览一些图片的...
通常情况下,Android应用程序中图片的缓存策略采用“内存-本地-网络”三级缓存策略,首先应用程序访问网络拉取图片,分别将加载的图片保存在本地SD卡中和内存中,当程序再一次需要加载图片的时候,先判断内存中是否...
所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量 什么是三级缓存 网络缓存, 不优先加载, 速度慢,浪费流量 本地缓存, 次优先加载, 速度快 内存缓存, 优先加
当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。 2.图片缓存的原理 实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制...
详解Android中图片的三级缓存及实例 ... 所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量 什么是三级缓存 网络缓存, 不优先加载, 速度慢,浪费流量 本地缓存,
秉着不重复造轮子原则,这里我采用Volley LruCache DiskLruCache三个谷歌官方认可的库来实现网络图片三级缓存。并且以“one line”风格来实现将网络图片显示在ImageView上,而无需关心任何缓存细节。 类库下载 ...
最近写项目,遇到一个图片的三级缓存问题,于是我研究了一下LruCache和DiskLruCache缓存,把代码上传到这里和大家交流学习
该app主要实现的功能是食谱分类,查看,搜索,收藏。图片采用三级缓存策略,请求数据时运用线程池进行处理。数据接口来自网上,数据网址在我源码里有。你们也可以按照你们自己的想法开发出这样一个app。
/ 410 第12章 Bitmap的加载和Cache / 413 12.1 Bitmap的高效加载 / 414 12.2 Android中的缓存策略 / 417 12.2.1 Lru Cache / 418 12.2.2 Disk Lru Cache / 419 12.2.3 Image Loader的实现 / 424 12.3 Image ...
12.2 Android中的缓存策略 / 417 12.2.1 LruCache / 418 12.2.2 DiskLruCache / 419 12.2.3 ImageLoader的实现 / 424 12.3 ImageLoader的使用 / 441 12.3.1 照片墙效果 / 441 12.3.2 优化列表的卡顿...
android端图片加载框架的老 大哥了,15.3k个star足以证明它的热门,UIL与gilde...以及提供了移动端图片加载框架的缓存思路:三级缓存策略 sd卡-内存-网络;值得注意的是,UIL以及两年未更新了,但笔者仍推荐各位使用!
1.2.4 Android移动Web项目开发的三种解决方案:Native, Web和Hybrid优缺陷分析 4 1.2.5国内外应用现状 6 1.2.6 研究现状总结 7 1.3研究目标与内容 7 1.3.1多窗口浏览器模式的实现机制 7 1.3.2跨域交互即缓存处理方法...
android大作业,本项目包含三个基本功能,分别是博客爬虫,缓存服务,本地随笔。 博客爬虫是分页爬取博客园博客,在app上显示博客的html格式(包括博客中的图片); 缓存服务是按策略缓存一些博客到本地数据库,当...