关闭

某宅的 Android 学习笔记(四)——用 DiskLruCache 实现本地缓存

标签: android缓存DiskLruCac本地缓存
216人阅读 评论(0) 收藏 举报
分类:

为什么要用DiskLruCache?

 离线数据对于依赖网络加载数据的APP来说具有很重要的意义,当无网络或者是网络状况不好时,APP依然具备部分功能是一种很好的用户体验。假设网易新闻这类新闻客户端,数据完全存储在缓存中而不使用DiskLruCache技术存储,那么当客户端被销毁,缓存被释放,意味着再次打开APP将是一片空白。DiskLruCache是Google官方推荐的一套硬盘缓存的API,虽然Android中并没有包含她,但是能得到官方推荐,必然有着过人之处。

将DiskLruCache加入项目

 首先我们需要下载DiskLruCache的文件,github。不过如果用的是AndroidStudio的话,只需要在build.gradle中添加如下代码即可:

compile 'com.jakewharton:disklrucache:2.0.2'

 接着重新构建gradle文件就可以了。

使用DiskLruCache

1.创建DiskLruCache实例

 DiskLruCache是不能直接new的,如果要创建一个DiskLruCache的实例,需要调用它的open()方法。open()方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据。

 那么缓存地址如何获得呢?当然可以直接制定,但是为了用户体验考虑,不建议这么做。一般会放在/sdcard/Android/data//cache 下,因为选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。

 但是考虑到不是所有的手机都一定会有SD卡,这里还是需要做一下判断。

/**
 * 获取缓存目录
 *
 * @param context
 * @param uniqueName 用于区分缓存内容
 * @return
 */
private File getDiskCacheDir(Context context, String uniqueName) {
    String cachePath;
    //判断 SD 卡是否存在,从而获取不同的缓存地址
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
            !Environment.isExternalStorageRemovable()) {
        cachePath = context.getExternalCacheDir().getPath();
    } else {
        cachePath = context.getCacheDir().getPath();
    }
    return new File(cachePath + File.separator + uniqueName);
}

 获取版本号。每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。

/**
 * 获取应用版本号
 *
 * @param context
 * @return
 */
private int getAppVersion(Context context) {
    try {
        PackageInfo info = context.getPackageManager().getPackageInfo(
                context.getPackageName(), 0);
        return info.versionCode;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return 1;
}

 参数准备好了,我们就可以得到DiskLruCache的实例啦:

private DiskLruCache diskLruCache;
//获取 DiskLruCache 的实例
    try {
        File cacheFile = getDiskCacheDir(listView.getContext(), "bitmap");
        if (!cacheFile.exists()) {
            cacheFile.mkdirs();
        }
        diskLruCache = DiskLruCache.open(cacheFile, getAppVersion(listView.getContext()), 1,
                10 * 1024 * 1024);
    } catch (IOException e) {
        e.printStackTrace();
    }

2.获取DiskLruCache.Editor与DiskLruCache.Snapshot

 DiskLruCache.Editor可以得到一个输出流outputStream,我们可以通过这个输出流将网络请求得到的数据写入本地。网络请求方法如下:

/**
 * 开启网络请求
 * 根据url来下载图片
 */
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
    HttpURLConnection connection = null;
    BufferedInputStream in = null;
    BufferedOutputStream out = null;
    try {
        final URL url = new URL(urlString);
        connection = (HttpURLConnection) url.openConnection();
        in = new BufferedInputStream(connection.getInputStream());
        out = new BufferedOutputStream(outputStream);
        int b;
        while ((b = in.read()) != -1) {
            out.write(b);
        }
        return true;
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
        try {
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    return false;
}

 获取Editor的edit()方法接收一个参数key,这个key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。但是图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。不过可以将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。

 /**
 * 为了保证文件命名合法,使用 MD5 编码 url
 */
private String hashKeyForDisk(String url) {
    String cacheKey;
    try {
        final MessageDigest digest = MessageDigest.getInstance("MD5");
        digest.update(url.getBytes());
        cacheKey = byteToHexString(digest.digest());
    } catch (NoSuchAlgorithmException e) {
        cacheKey = String.valueOf(url.hashCode());
    }
    return cacheKey;
}

private String byteToHexString(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();
}

 这样只需要传入key,就能得到Editor对象了。不过要读取缓存,我们还需要Snapshot对象。要得到Snapshot很简单:

DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  

 接着调用它的getInputStream()方法就可以得到缓存文件的输入流了。同样地,getInputStream()方法也需要传一个index参数,这里传入0就好。
 图片加载的方法就可以写成这样:(这里用了AsyncTask,由于要开启网络请求,所以必须放在子线程中处理)

@Override
    protected Bitmap doInBackground(String... params) {
        String url = params[0];
        String key = hashKeyForDisk(url);
        Bitmap bitmap = null;
        InputStream in = null;

        try {
            //获取 snapshot 对象
            snapshot = diskLruCache.get(key);
            //如果为空,则需要从网络下载图片,并存入缓存
            if (snapshot == null) {
                editor = diskLruCache.edit(key);
                if (editor != null) {
                    OutputStream outputStream = editor.newOutputStream(0);
                    if (downloadUrlToStream(url, outputStream)) {
                        editor.commit();
                    } else {
                        editor.abort();
                    }
                }
                //同步文件记录
                diskLruCache.flush();
                //下载完成后,重新获取 snapshot 对象
                snapshot = diskLruCache.get(key);
            }
            //从 snapshot 中获取 bitmap
            if (snapshot != null) {
                in = snapshot.getInputStream(0);
                bitmap = BitmapFactory.decodeStream(in);
            }
            //将 bitmap 存入内存缓存中
            if (bitmap != null) {
                addBitmapToLruCache(url, bitmap);
            }

            return bitmap;

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

 首次启动时的图片加载方法:

/**
 * 若缓存中存在bitmap,直接设置
 * 反之设置默认的本地图片
 */
public void showImg(ImageView imageView, String url) {
    //优先从缓存获取图片
    Bitmap bitmap = getBitmapFromLruCache(url);
    try {
        if (bitmap == null) {
            snapshot = diskLruCache.get(hashKeyForDisk(url));
            if(snapshot != null){
                bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    //若bitmap存在,直接设置
    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        //初始化ImageView的图片,未加载网络图片时显示
        imageView.setImageResource(R.mipmap.ic_launcher);
    }
}

 示例如下,可以看到成功从本地取出了缓存的图片。

最后附上自己项目的地址:https://github.com/Zhai-Wang/KanZhiHu,欢迎各路大神指正!

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3990次
    • 积分:149
    • 等级:
    • 排名:千里之外
    • 原创:11篇
    • 转载:1篇
    • 译文:0篇
    • 评论:1条
    文章分类
    最新评论