安卓缓存之DiskLruCache及设计(异步+缓存)图片加载器DiskCacheImageLoader

来源:互联网 发布:淘宝茶叶排名 编辑:程序博客网 时间:2024/05/22 16:46

DiskLruCache

DiskLruCache是一套硬盘缓存的解决方案,算法同LruCache基于LRU算法。

  1. DiskLruCache不是由Google官方编写的,这个类没有被包含在Android API当中。这个类需要从网上下载

    可以到作者的Github这里获取

一、DiskLruCache的基本使用

1. 初始化DiskLruCache

调用它的open()静态方法创建一个DiskLruCache的实例

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

参数说明:

  • File directory:指定的是数据的缓存地址,一般为应用的内部/外部缓存文件夹,这样当应用卸载时,里面的数据也会被清除。

  • int appVersion:指定当前应用程序的版本号。

  • int valueCount :指定同一个key可以对应多少个缓存文件,一般都是传1,这样key和value一一对应,方便查找。

  • long maxsize:指定最多可以缓存数据的总字节数。

  • 获取DiskLruCache的缓存目录directory

    当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取手机内部缓存路径,否则就调用getCacheDir()方法来获取手机外部缓存路径。前者获取到的就是 /sdcard/Android/data//cache 这个路径,后者获取到的是 /data/data//cache 这个路径。

    public File 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();  }  cacheDir = new File(cachePath + File.separator + uniqueName);  // 判断缓存文件夹是否存在,不存在则创建               if (!cacheDir.exists()) {        cacheDir.mkdirs();  }  return cacheDir;}       
  • 获取应用版本号appVersion

    每当版本号改变,缓存路径下存储的所有数据都会被清除掉。

    public int getAppVersion(Context context) {    try {      PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);      return info.versionCode;    } catch (NameNotFoundException e) {      e.printStackTrace();    }    return 1;  }
  • 创建DiskLruCache对象方式如下

    private DiskLruCache mDiskCache;//指定磁盘缓存大小private long DISK_CACHE_SIZE = 1024 * 1024 * 10;//10MB//得到缓存牡蛎File cacheDir = getDiskCacheDir(mContext, "Bitmap");mDiskCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1,DISK_CACHE_SIZE);

2. 将文件写入DiskLruCache缓存

写入操作是借助DiskLruCache.Editor这个类完成,需要调用DiskLruCache的edit()方法来获取实例。

public Editor edit(String key) throws IOException

edit()方法接收一个参数key,该key将会成为缓存文件的文件名,并且需要和文件的URL是一 一对应。

直接使用URL来作为key不太合适,因为文件的URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。
最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。

  • 将文件的URL字符串进行MD5编码:

    public String hashKeyForDisk(String key) {  String cacheKey;  try {    final MessageDigest mDigest = MessageDigest.getInstance("MD5");    mDigest.update(key.getBytes());    cacheKey = bytesToHexString(mDigest.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();}
  • 获取DiskLruCache.Editor的实例

    String imageUrl = "http://.....jpg";// 转化为对应的keyString key = hashKeyForDisk(imageUrl);DiskLruCache.Editor editor = mDiskLruCache.edit(key);
  • 通过editor获取输出流并写入数据

    if (editor != null) {    OutputStream outputStream = editor.newOutputStream(0);    if (downloadUrlToStream(url, outputStream)) {        editor.commit(); // 提交编辑    } else {        editor.abort(); // 放弃编辑    }    mDiskCache.flush(); // 刷新缓存}private boolean downloadUrlToStream(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;}

    注:newOutputStream()方法接收一个index参数,由于前面在设置valueCount的时候指定的是1,这里index传0就可以了。

    在写入操作执行完之后,我们还需要调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入。

3. 获取缓存文件:

  • 调用DiskLruCache的get方法来得到Snapshot对象

    get()方法要求传入一个key来获取到相应的缓存数据,而这个key就是将文件URL进行MD5编码后的值

    public synchronized Snapshot get(String key) throws IOExceptionString imageUrl = "http://.....jpg";// 转化为keyString key = hashKeyForDisk(imageUrl);DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
  • 通过Snapshot得到文件输入流

    InputStream is = snapShot.getInputStream(0);

    getInputStream()方法也需要传一个index参数,这里传入0。这是因为我们在open方法中参数设置为一个key对应一个文件

  • 通过文件输入流得到文件对象(以获取bitmap为例)

    Bitmap bitmap = BitmapFactory.decodeStream(is);

4. 移除某个key的缓存

借助DiskLruCache的remove()方法移除缓存

public synchronized boolean remove(String key) throws IOException

这个方法不经常被调用。因为DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。

5. 获取缓存的总字节数和删除全部缓存

借助DiskLruCache的size()方法获取缓存的总字节数

public synchronized long size() 

这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位。如果应用程序中需要在界面上显示当前缓存数据的总大小,就可以通过调用这个方法计算出。

public void delete() throws IOException

这个方法用于将所有的缓存数据全部删除,可以实现手动清理缓存功能。

6. 刷新缓存状态

 public synchronized void flush() throws IOException {        checkNotClosed();        trimToSize();        journalWriter.flush();    }

这个方法用于将内存中的操作记录同步到日志文件(journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。可在Activity的onPause()方法中去调用一次flush()方法。

二、使用DiskLruCache封装图片加载器

根据DiskLruCache,我们可以将它封装在DiskCacheImageLoader来对图片进行缓存。设计模式为使用单例模式来设计:

在Logcat中打印异步任务个数:

package com.cxmscb.cxm.cacheproject;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Build;import android.os.Environment;import android.os.Looper;import android.os.StatFs;import android.provider.ContactsContract;import android.util.Log;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.InputStream;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;/** * 利用DiskLruCache来缓存图片 */public class DiskCacheImageLoader {    private Context mContext;    private Set<DiskCacheAsyncTask> mTaskSet;    //DiskLruCache    private DiskLruCache mDiskCache;    private static DiskCacheImageLoader mDiskCacheImageLoader;    public static DiskCacheImageLoader getInstance(Context context){        if(mDiskCacheImageLoader==null){            synchronized (DiskCacheImageLoader.class){                if(mDiskCacheImageLoader==null){                    mDiskCacheImageLoader = new DiskCacheImageLoader(context);                }            }        }        return  mDiskCacheImageLoader;    }    private DiskCacheImageLoader(Context context) {        mTaskSet = new HashSet<>();        mContext = context.getApplicationContext();        //得到缓存文件        File diskCacheDir = getDiskCacheDir(mContext, "Bitmap");        //如果文件不存在 直接创建        if (!diskCacheDir.exists()) {            diskCacheDir.mkdirs();        }        try {            mDiskCache = DiskLruCache.open(diskCacheDir, 1, 1,                            1024*1024*20);        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 将一个URL转换成bitmap对象     *     */    public Bitmap getBitmapFromURL(String urlStr) {        Bitmap bitmap;        InputStream is = null;        try {            URL url = new URL(urlStr);            HttpURLConnection connection = (HttpURLConnection) url.openConnection();            is = new BufferedInputStream(connection.getInputStream(), 1024*8);            bitmap = BitmapFactory.decodeStream(is);            connection.disconnect();            return bitmap;        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (is != null) {                    is.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }    /**     * 将URL中的图片保存到输出流中     *     */    private boolean downloadUrlToStream(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(), 1024*8);            out = new BufferedOutputStream(outputStream, 1024*8);            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;    }    /**     * 普通地加载url     *     */    public void loadImage(ImageView imageView,String url){        //从缓存中取出图片        Bitmap bitmap = null;        try {            bitmap = getBitmapFromDiskCache(url);        } catch (IOException e) {            e.printStackTrace();        }        //如果缓存中没有,则需要从网络中下载        if (bitmap == null) {            DiskCacheAsyncTask task = new DiskCacheAsyncTask(imageView);            task.execute(url);            mTaskSet.add(task);        } else {            //如果缓存中有 直接设置            imageView.setImageBitmap(bitmap);        }    }    /**     * 为listview加载从start到end的所有的Image     *     */    public void loadTagedImagesInListView(int start, int end,String[] urls,ListView mListView) {        for (int i = start; i < end; i++) {            String url = urls[i];            ImageView imageView = (ImageView) mListView.findViewWithTag(url);            loadImage(imageView,url);        }        Log.i("num of task"," "+mTaskSet.size());    }    /**     * 创建缓存文件     *     */    public File getDiskCacheDir(Context context, String filePath) {        boolean externalStorageAvailable = Environment                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);        final String cachePath;        if (externalStorageAvailable) {            cachePath = context.getExternalCacheDir().getPath();        } else {            cachePath = context.getCacheDir().getPath();        }        return new File(cachePath + File.separator + filePath);    }    /**     * 将URL转换成key     *     */    private String hashKeyFormUrl(String url) {        String cacheKey;        try {            final MessageDigest mDigest = MessageDigest.getInstance("MD5");            mDigest.update(url.getBytes());            cacheKey = bytesToHexString(mDigest.digest());        } catch (NoSuchAlgorithmException e) {            cacheKey = String.valueOf(url.hashCode());        }        return cacheKey;    }    /**     * 将Url的字节数组转换成哈希字符串     *     */    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();    }    /**     * 将Bitmap写入缓存     *     */    private Bitmap addBitmapToDiskCache(String url) throws IOException {        if (mDiskCache == null) {            return null;        }        //设置key,并根据URL保存输出流的返回值决定是否提交至缓存        String key = hashKeyFormUrl(url);        //得到Editor对象        DiskLruCache.Editor editor = mDiskCache.edit(key);        if (editor != null) {            OutputStream outputStream = editor.newOutputStream(0);            if (downloadUrlToStream(url, outputStream)) {                //提交写入操作                editor.commit();            } else {                //撤销写入操作                editor.abort();            }            mDiskCache.flush();        }        return getBitmapFromDiskCache(url);    }    /**     * 从缓存中取出Bitmap     *     */    private Bitmap getBitmapFromDiskCache(String url) throws IOException {        //如果缓存中为空  直接返回为空        if (mDiskCache == null) {            return null;        }        //通过key值在缓存中找到对应的Bitmap        Bitmap bitmap = null;        String key = hashKeyFormUrl(url);        //通过key得到Snapshot对象        DiskLruCache.Snapshot snapShot = mDiskCache.get(key);        if (snapShot != null) {            //得到文件输入流            InputStream ins = snapShot.getInputStream(0);            bitmap = BitmapFactory.decodeStream(ins);        }        return bitmap;    }    /**     * 异步任务类     */    private class DiskCacheAsyncTask extends AsyncTask<String, Void, Bitmap> {        private ImageView imageView;        public DiskCacheAsyncTask(ImageView imageView){            this.imageView = imageView;        }        @Override        protected Bitmap doInBackground(String... params) {            Bitmap bitmap = getBitmapFromURL(params[0]);            //保存到缓存中            if (bitmap != null) {                try {                    //写入缓存                    addBitmapToDiskCache(params[0]);                } catch (IOException e) {                    e.printStackTrace();                }            }            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            if (imageView != null && bitmap != null) {                imageView.setImageBitmap(bitmap);            }            mTaskSet.remove(this);            Log.i("num of task"," "+mTaskSet.size());        }    }    /**     * 停止所有当前正在运行的任务     */    public void cancelAllTask() {        if (mTaskSet != null) {            for (DiskCacheAsyncTask task : mTaskSet) {                task.cancel(false);            }            mTaskSet.clear();            Log.i("num of task"," "+mTaskSet.size());        }    }}

三、 使用LruCacheImageLoader来加载网络图片:

一、项目效果图:

开始ListView从网络上加载图片,关闭网络退出应用后,再打开应用,从本地DiskLruCache中加载图片

这里写图片描述

二、对ListView加载图片的优化:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {            @Override            public void onScrollStateChanged(AbsListView absListView, int scrollState) {                if(scrollState==SCROLL_STATE_IDLE){                    mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView);                }else {                    mDiskImageLoader.cancelAllTask();                }            }    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {        mStart = firstVisibleItem;        mEnd = firstVisbleItem + visibleItemCount;        if(mFirstIn && visibleItemCount > 0){            mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView);             mFirstIn = false;        }    }});

完整项目地址:Github地址

参考

Android DiskLruCache完全解析,硬盘缓存的最佳方案

0 0