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:
File cacheDir = getFileCache(context, "disk_caches");if (!cacheDir.exists()) {    cacheDir.mkdirs();}try {    mDiskCaches = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);} catch (IOException e) {    e.printStackTrace();}private File getFileCache(Context context, String cacheFileName) {    String cachePath;    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())            || !Environment.isExternalStorageRemovable()) {        cachePath = context.getExternalCacheDir().getPath();    } else {        cachePath = context.getCacheDir().getPath();    }    return new File(cachePath + File.separator + cacheFileName);}
DiskLruCache.open方法有这样几个参数: 缓存目录 程序版本号:版本更新后,缓存清0 valueCount 缓存大小:随意,但也不能太任性,按字节算
应该不用解释了,唯一值得说的是valueCount这个参数,它是说同一个key可以对应Value的个数,一般都是1,基本没用。最后我们来看看最后返回的:
return new File(cachePath + File.separator + cacheFileName)

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

写入缓存

权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

都说了写缓存,那读写权限肯定是不能少了。
DiskLruCache写入缓存与使用SharedPreferences方法类似,需要使用Editor对象:
DiskLruCache.Editor editor = mDiskCaches.edit(key);

传入的key,就是我们需要下载的url地址,例如图片的地址,但是,url经常具有很多非法字符,这些会对我们的解析工作造成很多困难,而且,有时候我们的url地址也是需要保密的,所以我们经常通过MD5来进行url的加密,这样不仅可以加密,而且可以让所有的URL都变为规则的十六进制字符串。下面我们展示一个经典的写入缓存模板代码:
String key = toMD5String(url);/////////////////////////////////////////////////////////////////////////////////DiskLruCache.Editor editor = mDiskCaches.edit(key);if (editor != null) {    OutputStream outputStream = editor.newOutputStream(0);    if (getBitmapUrlToStream(url, outputStream)) {        editor.commit();    } else {        editor.abort();    }}mDiskCaches.flush();/////////////////////////////////////////////////////////////////////////////////public String toMD5String(String key) {    String cacheKey;    try {        final MessageDigest digest = MessageDigest.getInstance("MD5");        digest.update(key.getBytes());        cacheKey = bytesToHexString(digest.digest());    } catch (NoSuchAlgorithmException e) {        cacheKey = String.valueOf(key.hashCode());    }    return cacheKey;}private String bytesToHexString(byte[] bytes) {    StringBuilder sb = new StringBuilder();    for (int i = 0; i < bytes.length; i++) {        String hex = Integer.toHexString(0xFF & bytes[i]);        if (hex.length() == 1) {            sb.append('0');        }        sb.append(hex);    }    return sb.toString();}private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {    HttpURLConnection urlConnection = null;    BufferedOutputStream out = null;    BufferedInputStream in = null;    try {        final URL url = new URL(urlString);        urlConnection = (HttpURLConnection) url.openConnection();        in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);        out = new BufferedOutputStream(outputStream, 8 * 1024);        int b;        while ((b = in.read()) != -1) {            out.write(b);        }        return true;    } catch (final IOException e) {        e.printStackTrace();    } finally {        if (urlConnection != null) {            urlConnection.disconnect();        }        try {            if (out != null) {                out.close();            }            if (in != null) {                in.close();            }        } catch (final IOException e) {            e.printStackTrace();        }    }    return false;}

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

读取缓存

读取缓存的方法大家应该也能想到了,自然是调用get方法:
DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);

不过它返回的是DiskLruCache的Snapshot对象。当我们获取到了Snapshot对象,就可以从它里面获取输出流,从而取出缓存的数据:
DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);InputStream is = snapShot.getInputStream(0);  Bitmap bitmap = BitmapFactory.decodeStream(is);  mImageView.setImageBitmap(bitmap);

移除缓存

移除缓存,我们可以猜到,我们需要使用remove方法来实现:
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即可。
package com.imooc.listviewacyncloader;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Environment;import android.util.LruCache;import android.widget.ImageView;import android.widget.ListView;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.IOException;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.HashSet;import java.util.Set;import libcore.io.DiskLruCache;public class ImageLoaderWithDoubleCaches {    private Set<ASyncDownloadImage> mTasks;    private LruCache<String, Bitmap> mMemoryCaches;    private DiskLruCache mDiskCaches;    private ListView mListView;    public ImageLoaderWithDoubleCaches(Context context, ListView listview) {        this.mListView = listview;        mTasks = new HashSet<>();        int maxMemory = (int) Runtime.getRuntime().maxMemory();        int cacheSize = maxMemory / 10;        mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getByteCount();            }        };        File cacheDir = getFileCache(context, "disk_caches");        if (!cacheDir.exists()) {            cacheDir.mkdirs();        }        try {            mDiskCaches = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);        } catch (IOException e) {            e.printStackTrace();        }    }    public void showImage(String url, ImageView imageView) {        Bitmap bitmap = getBitmapFromMemoryCaches(url);        if (bitmap == null) {            imageView.setImageResource(R.drawable.ic_launcher);        } else {            imageView.setImageBitmap(bitmap);        }    }    public Bitmap getBitmapFromMemoryCaches(String url) {        return mMemoryCaches.get(url);    }    public void addBitmapToMemoryCaches(String url, Bitmap bitmap) {        if (getBitmapFromMemoryCaches(url) == null) {            mMemoryCaches.put(url, bitmap);        }    }    public void loadImages(int start, int end) {        for (int i = start; i < end; i++) {            String url = Images.IMAGE_URLS[i];            Bitmap bitmap = getBitmapFromMemoryCaches(url);            if (bitmap == null) {                ASyncDownloadImage task = new ASyncDownloadImage(url);                mTasks.add(task);                task.execute(url);            } else {                ImageView imageView = (ImageView) mListView.findViewWithTag(url);                imageView.setImageBitmap(bitmap);            }        }    }    private File getFileCache(Context context, String cacheFileName) {        String cachePath;        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())                || !Environment.isExternalStorageRemovable()) {            cachePath = context.getExternalCacheDir().getPath();        } else {            cachePath = context.getCacheDir().getPath();        }        return new File(cachePath + File.separator + cacheFileName);    }    private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {        HttpURLConnection urlConnection = null;        BufferedOutputStream out = null;        BufferedInputStream in = null;        try {            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);            out = new BufferedOutputStream(outputStream, 8 * 1024);            int b;            while ((b = in.read()) != -1) {                out.write(b);            }            return true;        } catch (final IOException e) {            e.printStackTrace();        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                if (out != null) {                    out.close();                }                if (in != null) {                    in.close();                }            } catch (final IOException e) {                e.printStackTrace();            }        }        return false;    }    public void cancelAllTasks() {        if (mTasks != null) {            for (ASyncDownloadImage task : mTasks) {                task.cancel(false);            }        }    }    public String toMD5String(String key) {        String cacheKey;        try {            final MessageDigest digest = MessageDigest.getInstance("MD5");            digest.update(key.getBytes());            cacheKey = bytesToHexString(digest.digest());        } catch (NoSuchAlgorithmException e) {            cacheKey = String.valueOf(key.hashCode());        }        return cacheKey;    }    private String bytesToHexString(byte[] bytes) {        StringBuilder sb = new StringBuilder();        for (int i = 0; i < bytes.length; i++) {            String hex = Integer.toHexString(0xFF & bytes[i]);            if (hex.length() == 1) {                sb.append('0');            }            sb.append(hex);        }        return sb.toString();    }    public void flushCache() {        if (mDiskCaches != null) {            try {                mDiskCaches.flush();            } catch (IOException e) {                e.printStackTrace();            }        }    }    class ASyncDownloadImage extends AsyncTask<String, Void, Bitmap> {        private String url;        public ASyncDownloadImage(String url) {            this.url = url;        }        @Override        protected Bitmap doInBackground(String... params) {            url = params[0];            FileDescriptor fileDescriptor = null;            FileInputStream fileInputStream = null;            DiskLruCache.Snapshot snapShot = null;            String key = toMD5String(url);            try {                snapShot = mDiskCaches.get(key);                if (snapShot == null) {                    DiskLruCache.Editor editor = mDiskCaches.edit(key);                    if (editor != null) {                        OutputStream outputStream = editor.newOutputStream(0);                        if (getBitmapUrlToStream(url, outputStream)) {                            editor.commit();                        } else {                            editor.abort();                        }                    }                    snapShot = mDiskCaches.get(key);                }                if (snapShot != null) {                    fileInputStream = (FileInputStream) snapShot.getInputStream(0);                    fileDescriptor = fileInputStream.getFD();                }                Bitmap bitmap = null;                if (fileDescriptor != null) {                    bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);                }                if (bitmap != null) {                    addBitmapToMemoryCaches(params[0], bitmap);                }                return bitmap;            } catch (IOException e) {                e.printStackTrace();            } finally {                if (fileDescriptor == null && fileInputStream != null) {                    try {                        fileInputStream.close();                    } catch (IOException e) {                    }                }            }            return null;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(url);            if (imageView != null && bitmap != null) {                imageView.setImageBitmap(bitmap);            }            mTasks.remove(this);        }    }}

整体代码与之前使用一级缓存的代码基本相同,大家只要在AsyncTask修改一定逻辑就好了。
再次运行程序,与之前使用一级缓存的图相同,这里就不贴了,只是这里在断网后,同样可以加载缓存中的图片。
以上,未完待续,后面我们会进一步优化>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


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


更多相关文章

  1. Activity的构成
  2. 你真的懂Android(安卓)Handler吗?(二)
  3. Android(安卓)让人又爱又恨的触摸机制(二)
  4. mac下Android(安卓)Studio常用的一些快捷键
  5. Android(安卓)--- App列表之分组ListView
  6. android Activity状态以及生命周期
  7. 老生常谈Android(安卓)HapticFeedback(震动反馈)
  8. Android(安卓)UI事件处理
  9. Android中的WebView的使用

随机推荐

  1. Android最佳性能实践(四)——布局优化技
  2. 从Eclipse到Android(安卓)Studio经历
  3. android 显示gif图片实例详解
  4. android 和云计算
  5. 如何脱离Android源码环境编译aapt
  6. android listview 一行高亮
  7. java.lang.NullPointerException空指针问
  8. Android(安卓)XML解析学习——Pull方式
  9. 转:一个Demo学完Android中所有的服务
  10. 转:关于android多任务同时下载的一点心得