Android异步加载全解析之引入二级缓存

来源:互联网 发布:查车档案软件 编辑:程序博客网 时间:2024/06/05 16:29


目录(?)[-]

  1. Android异步加载全解析之引入二级缓存
    1. 为啥要二级缓存
    2. DiskLruCache
      1. 配置
      2. 初始化
      3. 写入缓存
      4. 读取缓存
      5. 移除缓存
      6. 与生命周期绑定
      7. 日志同步
    3. 引入二级缓存

原文地址:http://blog.csdn.net/eclipsexys/article/details/44495285


Android异步加载全解析之引入二级缓存


为啥要二级缓存

前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据,但是,内存资源有限,所以我们可以把数据保存到硬盘上,这就是二级缓存,硬盘虽然读取速度慢,但是人家容量大。
Android的缓存技术也是使用了这样一个特性,总的来说,使用二级缓存的方案,就是先从一级缓存——内存中拿,没有的话,再去二级缓存——手机中拿,如果还没有,那就只能去下载了。
有了DiskLruCache,我们就可以很方便的将一部分内容缓存到手机存储中,做暂时的持久化保存,像我们经常用的一些新闻聚合类App、ZARKER等,基本都利用了DiskLruCache,浏览过的网页,即使在没有网络的情况下,也可以浏览。

DiskLruCache

配置

DiskLruCache,听名字就知道是LruCache的兄弟,只不过这个应该是Google的私生子,还没有像LruCache一样添加到API中,所以我们只能去官网上下载DiskLruCache的代码,其实也就一个类。下载地址:
https://developer.android.com/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html#l22

在工程中使用DiskLruCache非常简单,只需要在项目中新建一个libcore.io的包,并将DiskLruCache.java文件copy过去即可。

初始化

在使用DiskLruCache之前,我们需要对缓存的目录进行下配置,DiskLruCache并不需要限定缓存保存的位置,但一般情况下,我们的缓存都保存在缓存目录下: /sdcard/Android/data/package name/cache,当然,如果没有sdcard,那么我们就使用内置存储的缓存区域:/data/data/package name/cache。
在设置好缓存目录后,我们就可以使用DiskLruCache.open方法来创建DiskLruCache:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. File cacheDir = getFileCache(context, "disk_caches");  
  2. if (!cacheDir.exists()) {  
  3.     cacheDir.mkdirs();  
  4. }  
  5. try {  
  6.     mDiskCaches = DiskLruCache.open(cacheDir, 1110 * 1024 * 1024);  
  7. catch (IOException e) {  
  8.     e.printStackTrace();  
  9. }  
  10.   
  11. private File getFileCache(Context context, String cacheFileName) {  
  12.     String cachePath;  
  13.     if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
  14.             || !Environment.isExternalStorageRemovable()) {  
  15.         cachePath = context.getExternalCacheDir().getPath();  
  16.     } else {  
  17.         cachePath = context.getCacheDir().getPath();  
  18.     }  
  19.     return new File(cachePath + File.separator + cacheFileName);  
  20. }  
DiskLruCache.open方法有这样几个参数:
缓存目录
程序版本号:版本更新后,缓存清0
valueCount
缓存大小:随意,但也不能太任性,按字节算
应该不用解释了,唯一值得说的是valueCount这个参数,它是说同一个key可以对应Value的个数,一般都是1,基本没用。最后我们来看看最后返回的:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. return new File(cachePath + File.separator + cacheFileName)  

这里通过cacheFileName在缓存目录下再创建一个目录是干嘛呢?这个目录是用来对不同的缓存对象进行区分的,例如images、text等等。我们可以通过size()方法来获取所有缓存数据的大小。也可以使用delete()方法来删除所有缓存。

写入缓存

权限:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

都说了写缓存,那读写权限肯定是不能少了。

DiskLruCache写入缓存与使用SharedPreferences方法类似,需要使用Editor对象:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. DiskLruCache.Editor editor = mDiskCaches.edit(key);  

传入的key,就是我们需要下载的url地址,例如图片的地址,但是,url经常具有很多非法字符,这些会对我们的解析工作造成很多困难,而且,有时候我们的url地址也是需要保密的,所以我们经常通过MD5来进行url的加密,这样不仅可以加密,而且可以让所有的URL都变为规则的十六进制字符串。下面我们展示一个经典的写入缓存模板代码:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. String key = toMD5String(url);  
  2.   
  3. /////////////////////////////////////////////////////////////////////////////////  
  4. DiskLruCache.Editor editor = mDiskCaches.edit(key);  
  5. if (editor != null) {  
  6.     OutputStream outputStream = editor.newOutputStream(0);  
  7.     if (getBitmapUrlToStream(url, outputStream)) {  
  8.         editor.commit();  
  9.     } else {  
  10.         editor.abort();  
  11.     }  
  12. }  
  13. mDiskCaches.flush();  
  14. /////////////////////////////////////////////////////////////////////////////////  
  15.   
  16. public String toMD5String(String key) {  
  17.     String cacheKey;  
  18.     try {  
  19.         final MessageDigest digest = MessageDigest.getInstance("MD5");  
  20.         digest.update(key.getBytes());  
  21.         cacheKey = bytesToHexString(digest.digest());  
  22.     } catch (NoSuchAlgorithmException e) {  
  23.         cacheKey = String.valueOf(key.hashCode());  
  24.     }  
  25.     return cacheKey;  
  26. }  
  27.   
  28. private String bytesToHexString(byte[] bytes) {  
  29.     StringBuilder sb = new StringBuilder();  
  30.     for (int i = 0; i < bytes.length; i++) {  
  31.         String hex = Integer.toHexString(0xFF & bytes[i]);  
  32.         if (hex.length() == 1) {  
  33.             sb.append('0');  
  34.         }  
  35.         sb.append(hex);  
  36.     }  
  37.     return sb.toString();  
  38. }  
  39.   
  40. private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {  
  41.     HttpURLConnection urlConnection = null;  
  42.     BufferedOutputStream out = null;  
  43.     BufferedInputStream in = null;  
  44.     try {  
  45.         final URL url = new URL(urlString);  
  46.         urlConnection = (HttpURLConnection) url.openConnection();  
  47.         in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);  
  48.         out = new BufferedOutputStream(outputStream, 8 * 1024);  
  49.         int b;  
  50.         while ((b = in.read()) != -1) {  
  51.             out.write(b);  
  52.         }  
  53.         return true;  
  54.     } catch (final IOException e) {  
  55.         e.printStackTrace();  
  56.     } finally {  
  57.         if (urlConnection != null) {  
  58.             urlConnection.disconnect();  
  59.         }  
  60.         try {  
  61.             if (out != null) {  
  62.                 out.close();  
  63.             }  
  64.             if (in != null) {  
  65.                 in.close();  
  66.             }  
  67.         } catch (final IOException e) {  
  68.             e.printStackTrace();  
  69.         }  
  70.     }  
  71.     return false;  
  72. }  

这里唯一的需要注意的是,下载的方法与我们之前使用的方法有所不同,主要是为了通用性,DiskLruCache将对应URL的内容以流的形式进行存储,文件名就是MD5加密后的字符串。

读取缓存

读取缓存的方法大家应该也能想到了,自然是调用get方法:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);  

不过它返回的是DiskLruCache的Snapshot对象。当我们获取到了Snapshot对象,就可以从它里面获取输出流,从而取出缓存的数据:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);  
  2. InputStream is = snapShot.getInputStream(0);    
  3. Bitmap bitmap = BitmapFactory.decodeStream(is);    
  4. mImageView.setImageBitmap(bitmap);  

移除缓存

移除缓存,我们可以猜到,我们需要使用remove方法来实现:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. mDiskCache.remove(key);  
当然,DiskLruCache并不希望我们手动去移除缓存,因为人家用了Lru算法,跟我们在内存中使用的算法一样,该死的时候,它自己会死。

与生命周期绑定

DiskLruCache在使用时,经常与我们的Activity的生命周期进行绑定,例如在onPause()方法中调用flush()方法,将内容与journal日志文件同步,在onDestroy()方法中去调用close()方法结束DiskLruCache的open。

日志同步

通过前面的方法,我们已经可以缓存一个来自网络的图片了。下面我们进入缓存的文件夹,并查看里面的数据:


我们可以发现,这些文件,就是以MD5命名的缓存文件,它的最后面,有一个journal文件,我们通过cat命令打开:



这里我们选取一类记录,这些记录总是以dirty开头,然后clean,最后read。这个是什么意思呢?第一行dirty代表我们准备开始缓存数据,clean代表我们缓存到数据了,后面的30405代表缓存的大小,最后的read代表进行了读取操作。
看到这里,相信大家已经想起了我们非常熟悉的sqlite,它实际上也是利用文件来进行存储的。DiskLruCache实际上就是模拟了一个简化的sqlite,它的实现机制与sqlite基本类似。

引入二级缓存

ok,我们回到原来的项目,给工程增加二级缓存,导入DiskLruCache的源文件,这里就不讲了。我们在前面一级缓存的基础上,修改下ImageLoaderWithCaches类,创建ImageLoaderWithDoubleCaches类,这里面我们只需要在构造方法中增加对DiskLruCache的初始化,在AsyncTask中,我们来修改二级缓存的逻辑。前面的步骤相同,在取图像的时候都从内存缓存中取,如果取不到,那么在AsyncTask在硬盘缓存中取,如果还取不到,那就去下载,同时,将下载好的图像加入内存缓存,如果硬盘缓存中有,那么就直接加入内存缓存。看起来其实还是非常简单的,只要修改下AsyncTask即可。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. package com.imooc.listviewacyncloader;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5. import android.graphics.BitmapFactory;  
  6. import android.os.AsyncTask;  
  7. import android.os.Environment;  
  8. import android.util.LruCache;  
  9. import android.widget.ImageView;  
  10. import android.widget.ListView;  
  11.   
  12. import java.io.BufferedInputStream;  
  13. import java.io.BufferedOutputStream;  
  14. import java.io.File;  
  15. import java.io.FileDescriptor;  
  16. import java.io.FileInputStream;  
  17. import java.io.IOException;  
  18. import java.io.OutputStream;  
  19. import java.net.HttpURLConnection;  
  20. import java.net.URL;  
  21. import java.security.MessageDigest;  
  22. import java.security.NoSuchAlgorithmException;  
  23. import java.util.HashSet;  
  24. import java.util.Set;  
  25.   
  26. import libcore.io.DiskLruCache;  
  27.   
  28.   
  29. public class ImageLoaderWithDoubleCaches {  
  30.   
  31.     private Set<ASyncDownloadImage> mTasks;  
  32.     private LruCache<String, Bitmap> mMemoryCaches;  
  33.     private DiskLruCache mDiskCaches;  
  34.     private ListView mListView;  
  35.   
  36.     public ImageLoaderWithDoubleCaches(Context context, ListView listview) {  
  37.         this.mListView = listview;  
  38.         mTasks = new HashSet<>();  
  39.         int maxMemory = (int) Runtime.getRuntime().maxMemory();  
  40.         int cacheSize = maxMemory / 10;  
  41.         mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {  
  42.             @Override  
  43.             protected int sizeOf(String key, Bitmap value) {  
  44.                 return value.getByteCount();  
  45.             }  
  46.         };  
  47.   
  48.         File cacheDir = getFileCache(context, "disk_caches");  
  49.         if (!cacheDir.exists()) {  
  50.             cacheDir.mkdirs();  
  51.         }  
  52.         try {  
  53.             mDiskCaches = DiskLruCache.open(cacheDir, 1110 * 1024 * 1024);  
  54.         } catch (IOException e) {  
  55.             e.printStackTrace();  
  56.         }  
  57.     }  
  58.   
  59.     public void showImage(String url, ImageView imageView) {  
  60.         Bitmap bitmap = getBitmapFromMemoryCaches(url);  
  61.         if (bitmap == null) {  
  62.             imageView.setImageResource(R.drawable.ic_launcher);  
  63.         } else {  
  64.             imageView.setImageBitmap(bitmap);  
  65.         }  
  66.     }  
  67.   
  68.     public Bitmap getBitmapFromMemoryCaches(String url) {  
  69.         return mMemoryCaches.get(url);  
  70.     }  
  71.   
  72.     public void addBitmapToMemoryCaches(String url, Bitmap bitmap) {  
  73.         if (getBitmapFromMemoryCaches(url) == null) {  
  74.             mMemoryCaches.put(url, bitmap);  
  75.         }  
  76.     }  
  77.   
  78.     public void loadImages(int start, int end) {  
  79.         for (int i = start; i < end; i++) {  
  80.             String url = Images.IMAGE_URLS[i];  
  81.             Bitmap bitmap = getBitmapFromMemoryCaches(url);  
  82.             if (bitmap == null) {  
  83.                 ASyncDownloadImage task = new ASyncDownloadImage(url);  
  84.                 mTasks.add(task);  
  85.                 task.execute(url);  
  86.             } else {  
  87.                 ImageView imageView = (ImageView) mListView.findViewWithTag(url);  
  88.                 imageView.setImageBitmap(bitmap);  
  89.             }  
  90.         }  
  91.     }  
  92.   
  93.     private File getFileCache(Context context, String cacheFileName) {  
  94.         String cachePath;  
  95.         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
  96.                 || !Environment.isExternalStorageRemovable()) {  
  97.             cachePath = context.getExternalCacheDir().getPath();  
  98.         } else {  
  99.             cachePath = context.getCacheDir().getPath();  
  100.         }  
  101.         return new File(cachePath + File.separator + cacheFileName);  
  102.     }  
  103.   
  104.     private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {  
  105.         HttpURLConnection urlConnection = null;  
  106.         BufferedOutputStream out = null;  
  107.         BufferedInputStream in = null;  
  108.         try {  
  109.             final URL url = new URL(urlString);  
  110.             urlConnection = (HttpURLConnection) url.openConnection();  
  111.             in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);  
  112.             out = new BufferedOutputStream(outputStream, 8 * 1024);  
  113.             int b;  
  114.             while ((b = in.read()) != -1) {  
  115.                 out.write(b);  
  116.             }  
  117.             return true;  
  118.         } catch (final IOException e) {  
  119.             e.printStackTrace();  
  120.         } finally {  
  121.             if (urlConnection != null) {  
  122.                 urlConnection.disconnect();  
  123.             }  
  124.             try {  
  125.                 if (out != null) {  
  126.                     out.close();  
  127.                 }  
  128.                 if (in != null) {  
  129.                     in.close();  
  130.                 }  
  131.             } catch (final IOException e) {  
  132.                 e.printStackTrace();  
  133.             }  
  134.         }  
  135.         return false;  
  136.     }  
  137.   
  138.     public void cancelAllTasks() {  
  139.         if (mTasks != null) {  
  140.             for (ASyncDownloadImage task : mTasks) {  
  141.                 task.cancel(false);  
  142.             }  
  143.         }  
  144.     }  
  145.   
  146.     public String toMD5String(String key) {  
  147.         String cacheKey;  
  148.         try {  
  149.             final MessageDigest digest = MessageDigest.getInstance("MD5");  
  150.             digest.update(key.getBytes());  
  151.             cacheKey = bytesToHexString(digest.digest());  
  152.         } catch (NoSuchAlgorithmException e) {  
  153.             cacheKey = String.valueOf(key.hashCode());  
  154.         }  
  155.         return cacheKey;  
  156.     }  
  157.   
  158.     private String bytesToHexString(byte[] bytes) {  
  159.         StringBuilder sb = new StringBuilder();  
  160.         for (int i = 0; i < bytes.length; i++) {  
  161.             String hex = Integer.toHexString(0xFF & bytes[i]);  
  162.             if (hex.length() == 1) {  
  163.                 sb.append('0');  
  164.             }  
  165.             sb.append(hex);  
  166.         }  
  167.         return sb.toString();  
  168.     }  
  169.   
  170.     public void flushCache() {  
  171.         if (mDiskCaches != null) {  
  172.             try {  
  173.                 mDiskCaches.flush();  
  174.             } catch (IOException e) {  
  175.                 e.printStackTrace();  
  176.             }  
  177.         }  
  178.     }  
  179.   
  180.     class ASyncDownloadImage extends AsyncTask<String, Void, Bitmap> {  
  181.   
  182.         private String url;  
  183.   
  184.         public ASyncDownloadImage(String url) {  
  185.             this.url = url;  
  186.         }  
  187.   
  188.         @Override  
  189.         protected Bitmap doInBackground(String... params) {  
  190.             url = params[0];  
  191.             FileDescriptor fileDescriptor = null;  
  192.             FileInputStream fileInputStream = null;  
  193.             DiskLruCache.Snapshot snapShot = null;  
  194.             String key = toMD5String(url);  
  195.             try {  
  196.                 snapShot = mDiskCaches.get(key);  
  197.                 if (snapShot == null) {  
  198.                     DiskLruCache.Editor editor = mDiskCaches.edit(key);  
  199.                     if (editor != null) {  
  200.                         OutputStream outputStream = editor.newOutputStream(0);  
  201.                         if (getBitmapUrlToStream(url, outputStream)) {  
  202.                             editor.commit();  
  203.                         } else {  
  204.                             editor.abort();  
  205.                         }  
  206.                     }  
  207.                     snapShot = mDiskCaches.get(key);  
  208.                 }  
  209.                 if (snapShot != null) {  
  210.                     fileInputStream = (FileInputStream) snapShot.getInputStream(0);  
  211.                     fileDescriptor = fileInputStream.getFD();  
  212.                 }  
  213.                 Bitmap bitmap = null;  
  214.                 if (fileDescriptor != null) {  
  215.                     bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);  
  216.                 }  
  217.                 if (bitmap != null) {  
  218.                     addBitmapToMemoryCaches(params[0], bitmap);  
  219.                 }  
  220.                 return bitmap;  
  221.             } catch (IOException e) {  
  222.                 e.printStackTrace();  
  223.             } finally {  
  224.                 if (fileDescriptor == null && fileInputStream != null) {  
  225.                     try {  
  226.                         fileInputStream.close();  
  227.                     } catch (IOException e) {  
  228.                     }  
  229.                 }  
  230.             }  
  231.             return null;  
  232.         }  
  233.   
  234.         @Override  
  235.         protected void onPostExecute(Bitmap bitmap) {  
  236.             super.onPostExecute(bitmap);  
  237.             ImageView imageView = (ImageView) mListView.findViewWithTag(url);  
  238.             if (imageView != null && bitmap != null) {  
  239.                 imageView.setImageBitmap(bitmap);  
  240.             }  
  241.             mTasks.remove(this);  
  242.         }  
  243.     }  
  244. }  

整体代码与之前使用一级缓存的代码基本相同,大家只要在AsyncTask修改一定逻辑就好了。

再次运行程序,与之前使用一级缓存的图相同,这里就不贴了,只是这里在断网后,同样可以加载缓存中的图片。

以上,未完待续,后面我们会进一步优化>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>



代码下载地址  http://download.csdn.net/detail/x359981514/8562525

0 0
原创粉丝点击