Android缓存框架 DiskLruCache

笑谈风云,一语定乾坤。大家好,我是皖江。今天,我继续和大家分享我的Android框架学习经验之路。上次我熟悉了Android自带的二级内存缓存机制LruCache的使用,这次来学习由第三方JakeWharton的git开源项目DiskLruCache.

这是项目的git地址:https://github.com/JakeWharton/DiskLruCache

jar包下载链接:https://search.maven.org/remote_content?g=com.jakewharton&a=disklrucache&v=LATEST

依赖注入

<dependency>

<groupId>com.jakewharton</groupId>

<artifactId>disklrucache</artifactId>

<version>2.0.2</version>

</dependency>

gradle

compile 'com.jakewharton:disklrucache:2.0.2'

相关介绍:

DiskLruCache是在文件系统上使用有限空间的高速缓存。每个缓存条目都有一个字符串键和固定数量的值。每个键必须匹配正则表达式[a-z0-9_-]{1,120}。值是二进制,可以通过流或者文件访问。值的长度必须大于0,不可为空。

缓存数据的目录必须是单独的,不提倡多线程操作缓存目录,线程上是不安全的。缓存可以设置最大值,当存储的字节数超过了最大值时,缓存将删除较先进入缓存的文件。

当调用edit来创建或者更新条目的值时,只能用一个edit实例来进行操作。如果当前值不可操作时,该次编辑返回为null。

当编辑条目时,不必为每个值提供数据,其值默认为之前的值。每个编辑必须调用Editor.commit或者Editor.abort进行匹配。

我是用AS来编译项目的,这次是通过JAR包的形式引入。

首先,设置缓存所在路径:

/**
 *
 * @param context
 * @param dataType   区分存储文件类型
 * 当内存卡存在时:存储路径为/sdcard/Android/data/${包名}/cache
 * 当内存卡不存在是:存储路径为/data/data/${包名}/cache
 * @return
 */
public static File getDiskLruCacheDir(Context context, String dataType) {
    String path;
    File cacheFile = null;
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
            !Environment.isExternalStorageRemovable()) {//判断内存卡是否挂载
        path=context.getExternalCacheDir().getPath();
    } else {
        path=context.getCacheDir().getPath();
    }
    cacheFile=new File(path+File.separator+dataType);
    return cacheFile;
}

然后获取APP版本号

/**
 * 获取APP版本号
 * @param ctx
 * @return
 */
public static int getAppVersionValue(Context ctx){
    int version = 1;
    try{
        String packageName = ctx.getPackageName();
        PackageManager pm = ctx.getPackageManager();
        PackageInfo info = pm.getPackageInfo(packageName,0);
        version = info.versionCode;
    }catch (PackageManager.NameNotFoundException e){
        e.printStackTrace();
    }
    return version;
}

再封装一个将URL路径转换为MD5串的方法,因为DiskLruCache的key只认MD5

/**
 * 字符串MD5加密
 * @param str
 * @return
 */
public static String getStringMD5(String str){
    String md5Str = null;
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.update(str.getBytes());
        byte bytes[] = messageDigest.digest();
        if (bytes == null || bytes.length == 0) {
            return md5Str;
        }
        StringBuffer stringBuffer = new StringBuffer();
        for(int i=0;i<bytes.length;i++){
            stringBuffer.append(Integer.toHexString(0xFF & bytes[i]));//byte与OxFF进行与运算,然后通过toHexString获取16进制字符串
        }
        md5Str = stringBuffer.toString();
        return md5Str;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return md5Str;
}
接下来我再写一个简单的网络请求图片方法,该方法会将返回的流转换成Bitmap进行返回

/**
 * 从网络下载图片
 * @param imageUrl
 * @return
 */
public static Bitmap getBitmapFromNet(String imageUrl){
    Bitmap bitmap = null;
    URL url=null;
    HttpURLConnection httpURLConnection=null;
    InputStream inputStream=null;
    try {
        url=new URL(imageUrl);
        Log.e("test","网络请求路径:"+imageUrl);
        httpURLConnection=(HttpURLConnection) url.openConnection();
        httpURLConnection.setConnectTimeout(5*1000);//设置连接超时时长
        httpURLConnection.setReadTimeout(10*1000);//设置读取超时时长
        httpURLConnection.setDoInput(true);
        httpURLConnection.setDoOutput(true);
        Log.e("test","网络请求返回码:"+httpURLConnection.getResponseCode()+"");
        if (httpURLConnection.getResponseCode()==200) {
            inputStream=httpURLConnection.getInputStream();
            bitmap = BitmapFactory.decodeStream(inputStream);
        } else {
            Log.e("test","下载失败");
        }
    } catch (Exception e) {
        Log.e("test","下载失败");
    }finally{
        try {
            if (inputStream!=null) {
                inputStream.close();
            }
            if (httpURLConnection!=null) {
                httpURLConnection.disconnect();
            }
        } catch (Exception e) {
            Log.e("test","关闭流失败");
        }
    }
    return bitmap;
}
一开始我的思路是这样的,先初始化DiskLruCache,然后拿着图片的URL路径去缓存中找图片,如果没有图片则上网下载。下载成功后返回Bitmap,并将Bitmap存入缓存中,且显示在页面。但是将Bitmap存入缓存中的时候,遇到了要将Bitmap压缩转换成InputStream流的麻烦。转换方法如下:

/**
 * Bitmap 转 InputStream
 * @param bitmap
 * @return
 */
public static InputStream bitmapToIS(Bitmap bitmap){
    InputStream is = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG,100,baos);// 压缩图片    PNG会最大程度不让图片失真
    is = new ByteArrayInputStream(baos.toByteArray());
    return is;
}
这种方法我通过实验得知,唯一的缺憾就是转换PNG图片比较耗时,转换JPG的图片失真严重。真的不能要啊,这样的话,我这思路就行不通了啊。大王,臣妾做不到啊!!后来我想了一下,我在下载的时候就已经拿到流了,何不将流直接写入缓存中,岂不美哉。中间也少了不必要的步骤。于是就有了下面这个方法:

/**
 * 从网络获取图片且保存至SD卡中的缓存
 */
public static boolean getBitmapFromNetWorkAndSaveToDiskLruCache(String imageUrl,OutputStream outputStream){//直接将DiskLruCache需要写的流对象传进来
    boolean isSuccessfull=false;
    URL url=null;
    HttpURLConnection httpURLConnection=null;
    BufferedOutputStream bufferedOutputStream=null;
    InputStream inputStream=null;
    BufferedInputStream bufferedInputStream=null;
    try {
        url=new URL(imageUrl);
        Log.e("test","网络请求路径:"+imageUrl);
        httpURLConnection=(HttpURLConnection) url.openConnection();
        httpURLConnection.setConnectTimeout(5*1000);
        httpURLConnection.setReadTimeout(10*1000);
        httpURLConnection.setDoInput(true);
        httpURLConnection.setDoOutput(true);
        Log.e("test","网络请求返回码:"+httpURLConnection.getResponseCode()+"");
        if (httpURLConnection.getResponseCode()==200) {
            bufferedOutputStream=new BufferedOutputStream(outputStream);
            inputStream=httpURLConnection.getInputStream();
            bufferedInputStream=new BufferedInputStream(inputStream);
            int len=0;
            byte [] buffer=new byte[1024];
            while((len=bufferedInputStream.read(buffer))!=-1){//将inputStream流写入到bufferedOutputStream中
                bufferedOutputStream.write(buffer, 0, len);
                bufferedOutputStream.flush();
            }
            isSuccessfull=true;
        } else {
            isSuccessfull=false;
            System.out.println("下载失败");
        }
    } catch (Exception e) {
        isSuccessfull=false;
        System.out.println(e+e.toString());
    }finally{
        try {
            if (bufferedOutputStream!=null) {
                bufferedOutputStream.close();
            }
            if (inputStream!=null) {
                inputStream.close();
            }
            if (bufferedInputStream!=null) {
                bufferedInputStream.close();
            }
            if (httpURLConnection!=null) {
                httpURLConnection.disconnect();
            }
        } catch (Exception e) {
            System.out.println(e+e.toString());
        }
    }
    return isSuccessfull;
}
这样的话,图片流在拿到的同时就直接写入缓存了。以下是具体思路流程:

/**
 * 从网络获取图片并存入缓存中
 * @param url
 */
public void getImageFromNet(final String url){
    new Thread(){
        @Override
        public void run() {
            String key = Utils.getStringMD5(url);//1.生成key
            try {
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);//2.获取本次编辑器
                if(editor!=null){
                    OutputStream os = editor.newOutputStream(0);
                    boolean isSuccess = Utils.getBitmapFromNetWorkAndSaveToDiskLruCache(url,os);//3.下载图片并将图片写入缓存
                    if(isSuccess){//4.写入成功,提交本次写入
                        editor.commit();
                        mHandler.sendEmptyMessage(1);
                    }else{//4.写入失败,放弃本次提交
                        editor.abort();
                        mHandler.sendEmptyMessage(2);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }.start();
}
初始化

/**
 * 初始化缓存
 */
public void initDiskLruCache(){
    //初始化DiskLruCache
    File fileCache = Utils.getDiskLruCacheDir(this,"bitmap");//缓存文件夹
    if(!fileCache.exists()){
        fileCache.mkdirs();
    }
    int versionValue = Utils.getAppVersionValue(this);//版本值
    int maxSize = 10*1024*1024;//缓存区大小
    try {
        mDiskLruCache = DiskLruCache.open(fileCache,versionValue,1,maxSize);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

从缓存中读取图片

/**
 * 从缓存中读取图片
 * @param url
 * @return
 */
public Bitmap getBitmapFromCache(String url){
    String key = Utils.getStringMD5(url);//获取key
    Bitmap bitmap = null;
    try {
        if(!mDiskLruCache.isClosed()){
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);//获取快照
            if(snapshot!=null){
                InputStream is = snapshot.getInputStream(0);//从快照中获取流
                bitmap = BitmapFactory.decodeStream(is);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bitmap;
}
清除所有缓存

	mDiskLruCache.delete();
以上就是DiskLruCache的基本用法了,我再将Activit 的代码贴一下:

public class DiskLruCacheActivity extends BaseActivity{

    private DiskLruCache mDiskLruCache;

    private String[] urls = {
            "http://www.baidu.com/img/bdlogo.png",
            "http://image.baidu.com/search/down?tn=download&ipn=dwnl&word=download&ie=utf8&fr=result&url=http%3A%2F%2Fwww.icosky.com%2Ficon%2Fpng%2FSystem%2FVista%2520System%2FVista%2520%252816%2529.png&thumburl=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D2282442470%2C974371679%26fm%3D21%26gp%3D0.jpg",
            "http://image.baidu.com/search/down?tn=download&ipn=dwnl&word=download&ie=utf8&fr=result&url=http%3A%2F%2Fimg.article.pchome.net%2F00%2F40%2F48%2F13%2Fpic_lib%2Fs960x639%2Fstariconrt2s960x639.JPG&thumburl=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D2571315283%2C182922750%26fm%3D21%26gp%3D0.jpg",
            "http://image.baidu.com/search/down?tn=download&ipn=dwnl&word=download&ie=utf8&fr=result&url=http%3A%2F%2Fwww.icosky.com%2Ficon%2Fpng%2FSystem%2FFrenzic%2520System%2FHome%2520%2528Alternate%2529.png&thumburl=http%3A%2F%2Fimg3.imgtn.bdimg.com%2Fit%2Fu%3D1576708873%2C969493934%26fm%3D21%26gp%3D0.jpg"};

    @Bind(R.id.iv)
    ImageView mImageView;

    @Bind(R.id.et_url)
    EditText mEtUrl;

    @Bind(R.id.btn_get_from_net)
    Button mBtnGetFromNet;

    @Bind(R.id.btn_get_from_local)
    Button mBtnGetFromLocal;

    @Bind(R.id.btn_clean)
    Button mBtnClean;

    @Bind(R.id.btn_change)
    Button mBtnChange;

    private String url;

    private int nowPosition = 0;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    Bitmap bitmap = getBitmapFromCache(url);
                    mImageView.setImageBitmap(bitmap);
                    break;
                case 2:
                    break;
            }
        }
    };

    @Override
    protected void onActivityCreate(Bundle savedInstanceState) {
        super.onActivityCreate(savedInstanceState);
        setContentView(R.layout.activity_disklrucache);
        ButterKnife.bind(this);
    }

    @Override
    protected void initView() {
        super.initView();
    }

    @Override
    protected void initListener() {
        super.initListener();
        mBtnChange.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(nowPosition
布局文件:

<?xml version="1.0" encoding="utf-8"?>


    

    

    

                                                                         


以上。

猜你喜欢

转载自blog.csdn.net/shirakawakanaki/article/details/52835985