当前位置: 首页 > 图灵资讯 > 技术篇> Android网络图片三级缓存策略

Android网络图片三级缓存策略

来源:图灵教育
时间:2023-06-05 09:29:12

在移动应用程序中,我们通常将网络图片分为三个层次。第一级是网络层,即根据图片的URL地址在服务器上找到相应的图片。获取此层的图片会消耗流量,因此我们希望在获取后在本地永久使用,因此将有下一个缓存策略;第二层缓存是将第一层图片下载到手机内存中的手机内存。这种缓存读取速度非常快,但是当图片内存被回收时,图片自然不会存在,第三层是在手机硬盘层,会缓存到sd卡。然而,与内存相比,这一层的读取速度要慢得多。因此,协调这三层图像缓存可以提高应用性能和用户体验。

本着不重复轮子的原则,我在这里使用Volley+LruCache+谷歌官方认可的三个库,DiskLrucache,实现了网络图片的三级缓存。并以“one line“风格可以在不关心任何缓存细节的情况下,在Imageview上显示网络图片。

类库下载
  1. 2013年Goolegole是Goole 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-v4jar包。

3.DiskLrucache是非谷歌的官方编写,但官方认证的硬盘缓存并不局限于Android,因此理论上,Java应用程序也可以使用DiskLrecache进行缓存。您可以下载此类:

方法与流程

1.为了实现图片的三级缓存,我们需要将图片下载到当地。我们所有的在线图片请求都是通过Volley统一管理的。Voley需要我们声明RequesQuetManager来维持请求队列。因此,我们首先定义它RequesQueuetManager类管理RequesQuetManager,代码如下:

1. publicclassRequesQueuetManager{2. publicstaticRequestQueue mRequestQueue =Volley.newRequestQueue(DemoApplication.getInstance());3. 4. publicstaticvoid addRequest(Request<?> request,Objectobject){5. if(object!=null){6.  request.setTag(object);7. }8.  mRequestQueue.add(request);9. }10. 11. publicstaticvoid cancelAll(Object tag){12.  mRequestQueue.cancelAll(tag);13. }14. }

因为RequestQueue需要Context类型参数,所以我们在Volley.newRequestQueue(DemoApplication.getInstance())这句话是传入的DemoApplication.getInstance(),这种静态方法是什么?这是应用程序的aplication实例。我们定制一个application,然后将application传输到requestQueue。我们定制的application如下:

1. publicclassDemoApplicationextendsApplication{2. publicstaticString TAG;3. privatestaticDemoApplication application;4. publicstaticDemoApplication getInstance(){5. return application;6. }7. 8. @Override9. publicvoid onCreate(){10. super.onCreate();11.  TAG =this.getClass().getSimpleName();12.  application =this;13. }14. }

记得将自定义的application添加到AndroidManifest中.在xml文件aplication标签的name属性中:

2.RequestQueue负责图片请求顺序,具体图片请求工作由Voley中的Imageloader类完成,new它将接收两个参数,一个是我们刚刚声明的RequestQueue请求队列,另一个是ImageLoader.ImageCache接口,看名字,这个接口方便我们写缓存,也就是说,我们的下一个二级缓存和三级缓存是在实现这一点的基础上进行的:首先,我们建立了一个新的ImageLreCache让它继承LreCache,实现Imageloader.ImageCache接口,本着父债子偿还的原则,需要实现父类未实现的接口子类,因此需要重写LreCache接口sizeOf方法必须重写Imageloader.ImageCachegetBitmapputBitmap方法。

因此,这类下会有以下三个函数:

1. @Override2. protectedint sizeOf(String key,Bitmap bitmap){3. if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.HONEYCOMB_MR1){4. return bitmap.getByteCount();5. }6. // Pre HC-MR17. return bitmap.getRowBytes()* bitmap.getHeight();8. }9. 10. 11. 12. @Override13. publicBitmap getBitmap(String s){14. returnget(s);15. }16. 17. @Override18. publicvoid putBitmap(String s,Bitmap bitmap){19.  put(s,bitmap)20. }

sizeof法是Lrucachee 留给子类重写来衡量Bitmap大小的函数,因为Lrucache会判断size大小是否小于0,size小于0的bitmap会被抛出IllegalStateException异常。

Imageloaderertmap和putbitmap的方法.ImageCache留给子类实现的接口,Volley在要求网络数据时,会先回调GetBitmap方法,查看缓存中是否有必要的图片,有的话会直接使用缓存图片,不再要求,Volley下载图片后,会回调putbitmap方法缓存图片。因此,我们可以实现这两种方法,然后直接使用Lrecache的get和set方法。

综上所述,我们将完成二级缓存,然后在此基础上完成硬盘级的第三级缓存。DiskLrucache的使用方法会有点复杂。首先,我们需要DiskLrucache的例子构造方法是私有的,所以我们需要通过它提供的open方法来生成。open原型如下:

1. /**2.  * Opens the cache in {@code directory}, creating a cache if none exists3.  * there.4.  *5.  * @param directory a writable directory6.  * @param apppversion7.  * @param valueCount the number of values per cache entry. Must be positive.8.  * @param maxSize the maximum number of bytes this cache should use to store9.  * @throws java.io.IOException if reading or writing the cache directory fails10.  */11. publicstaticDiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)12. throwsIOException{}

它将接收四个参数,directory是与缓存路径对应的file类别,appversion代表缓存版本,valuecount代表每个key对应的缓存数量,一般为1,maxsize代表最大的缓存文件。因此,在android中,directory和appversion可以从系统中获得,所以我们会这样写:

1. privatestaticDiskLruCache mDiskLruCache =DiskLruCache.open(getDiskCacheDir(DemoApplication.getInstance(),CACHE_FOLDER_NAME),2.  getAppVersion(DemoApplication.getInstance(),10*1024*1024);3. 4. 5. //该方法将判断当前SD卡是否存在,然后选择缓存地址6. publicstaticFile getDiskCacheDir(Context context,String uniqueName){7. String cachePath;8. if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())9. ||!Environment.isExternalStorageRemovable()){10.  cachePath = context.getExternalCacheDir().getPath();11. }else{12.  cachePath = context.getCacheDir().getPath();13. }14. returnnewFile(cachePath +File.separator + uniqueName);15. }16. 17. ///获得应用version号码188. publicint getAppVersion(Context context){19. try{20. PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);21. return info.versionCode;22. }catch(NameNotFoundException e){23.  e.printStackTrace();24. }25. return1;26. }

在获得DiskLrucache实例后,我们可以改进它 ImageLoader.在ImageCache接口下的getBitmap和putBitmap方法。主要思想是在方法上多做一层逻辑判断。当图片不在Lrucache时,再次查询DiskLrucache是否存在。如果存在,取出并转换为Bitmap并返回。需要注意的是,Disklrucache对缓存内容的读写是通过内部包装的Editor和Snapshot实现的,所以代码会有点复杂,但很容易理解。完善后的代码如下:

1. @Override2. publicBitmap getBitmap(String s){3. String key = hashKeyForDisk(s);4. try{5. if(mDiskLruCache.get(key)==null){6. returnget(s);7. }else{8. DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);9. Bitmap bitmap =null;10. if(snapShot !=null){11. InputStreamis= snapShot.getInputStream(0);12.  bitmap =BitmapFactory.decodeStream(is);13. }14. return bitmap;15. }16. }catch(IOException e){17.  e.printStackTrace();18. }19. returnnull;20. }21. 22. @Override233. publicvoid putBitmap(String s,Bitmap bitmap){24.  put(s,bitmap);25. String key = hashKeyForDisk(s);26. try{27. if(null== mDiskLruCache.get(key)){28. DiskLruCache.Editor editor = mDiskLruCache.edit(key);29. if(editor !=null){30. OutputStream outputStream = editor.newOutputStream(0);31. if(bitmap.compress(CompressFormat.JPEG,100, outputStream)){32.  editor.commit();33. }else{34.  editor.abort();35. }36. }37.  mDiskLruCache.flush();38. }39. }catch(IOException e){40.  e.printStackTrace();41. }42. 43. }

这样,缓存类就结束了,但我们回到了这一步的开始。有了这个实现Imageloader.Imagecache接口类可用于生成Imageloader实例:

1. publicstaticImageLoader mImageLoder =newImageLoader(RequesQueuetManager.mRequestQueue,newImageLreCache());

3.拿到mimageloder后,我们可以在网上请求图片,请求函数为get,接收参数为图片远程地址url,回调接口listener,图片所需的宽度maxwidth和高度maxheight,这里很难理解listener参数,它是Imageloader内部接口Imagelistener,主要是图片请求成功或失败的两种回调方法。在这里,我们写一个统一生成isterner的函数:

1. publicstaticImageLoader.ImageListener getImageLinseter(finalImageView view,2. finalBitmap defaultImageBitmap,finalBitmap errorImageBitmap){3. 4. returnnewImageLoader.ImageListener(){5. @Override6. publicvoid onResponse(ImageLoader.ImageContainer imageContainer,boolean b){7. if(imageContainer.getBitmap()!=null){8.  view.setImageBitmap(imageContainer.getBitmap());9. }elseif(defaultImageBitmap !=null){10.  view.setImageBitmap(defaultImageBitmap);11. }12. }13. 14. @Override15. publicvoid onErrorResponse(VolleyError volleyError){16. if(errorImageBitmap !=null){17.  view.setImageBitmap(errorImageBitmap);18. }19. }20. };21. }

该函数接收view,defaultImageBitmap,errrorimageBitmap有三个参数。图片请求成功后,将下载的bitmap显示在view上,失败时显示errorimageBitmap。

综上所述,我们可以包装一个函数提供给外部,接收六个参数,实现它。”one line“编程,让网络图片请求变得更容易。代码如下:

1. /**2.  * 将url处的图片放在view上,并自动实现内存和硬盘的双缓存。3.  * @param url 远程url地址4.  * @param view view55待现实图片.  * @param defaultImageBitmap 图片6默认显示.  * @param errorImageBitmap 网络错误时显示的图片7.  * @param maxWidtn8.  * @param maxHeight 9.  */10. publicstaticImageLoader.ImageContainer loadImage(finalString url,finalImageView view,11. finalBitmap defaultImageBitmap,finalBitmap errorImageBitmap,int maxWidtn,int maxHeight){12. return mImageLoder.get(url, getImageLinseter(view,defaultImageBitmap,13.  errorImageBitmap),maxWidtn,maxHeight);14. }

效果

让我们先看看它的使用方法,先拿到控件:

1. ImageView iv =(ImageView) findViewById(R.id.iv_hello);

然后调用以下方法对图片进行调用http://ww2.sinaimg.cn/large/7cc829d3gw1eahy2zrjlj20kd0a0t9.jpg显示在上述控件上:

1. ImageCacheManger.loadImage("http://ww2.sinaimg.cn/large/7cc829d3gw1eahy2zrjlj20kd0a0t9.jpg", iv,2.  getBitmapFromResources(this, R.drawable.ic_launcher), getBitmapFromResources(this, R.drawable.error));

所谓无图无真相,是请求成功的效果(请求成功后,由于缓存在本地硬盘中,断网后第二次打开仍然可以显示):

硬盘中缓存在DiskLrucache的文件:

代码

您可以在这里Clone或下载整个项目:https://github.com/saymagic/PictureThreeCache