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

原创 2016年08月29日 22:06:04

为什么要用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,欢迎各路大神指正!

相关文章推荐

Android中Bitmap的加载和Cache(三级缓存 :LruCache,DiskLruCache)学习笔记

Android设备作为客服端,最明显的特点就是:将服务端数据按照某种格式展示给Android用户。图片又是其中最重要的数据加载格式,所以对图片加载和缓存的学习和掌握是一个Android开发人员必备的基...

图片缓存LruCache 高效加载图片 学习笔记 + 开源项目:DiskLruCache

获得图片: LruCache如果有则取出返回,如果没有的话则从软引用几何中取出,如果再次没有的话,则只能从 服务器中取出,然后显示出来,并再存入LruCache。 问题:服务器上的图片与LruCach...

Android DiskLruCache学习笔记

转载请注明出处:http://blog.csdn.net/ibelieveyouwxy/article/details/50403659 最近无聊刷糗百,发现糗百在没有网络的情况下,还是可以看到上次...

某宅的Android学习笔记(二)——图片三级缓存

自学三个月的Android,磕磕碰碰写了一个小项目“看知乎”,数据源来自大神的看知乎API。这里记录一下自己在coding中收获的一些知识点。...
  • mouzhai
  • mouzhai
  • 2016年08月22日 16:24
  • 430

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量。对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理...

android图片的异步加载和双缓存学习笔记——DisplayImageOptions

DisplayImageOptions       用于设置图片显示的类。       1.此类的功能: 1 //设置图片在下载期间显示的图片 2 showS...

Glide源码分析(一)——DiskLruCache磁盘缓存的实现

Glide源码分析(一)——DiskLruCache磁盘缓存的实现Glide磁盘的实现主要是通过DiskLruCache来实现的。DiskLruCache并非针对Glide编写的,而是一个通用的磁盘缓...

Android 实现内存+SD卡 图片缓存策略 (LurCache+DiskLruCache)

以前之用过图片框架,这次因为项目中图片量不多,于是想试试封装一个小型的缓存工具,效果还不错代码主要来自郭大神的文章Android照片墙完整版,完美结合LruCache和DiskLruCache主要实现...
  • Mr_Sk
  • Mr_Sk
  • 2017年03月24日 17:06
  • 227

Android本地缓存DiskLruCache完整详细学习示例

MainActivity如下: package cc.vv; import java.io.File; import java.io.InputStream; import java.io.Out...
  • lfdfhl
  • lfdfhl
  • 2015年01月09日 17:12
  • 2059
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:某宅的 Android 学习笔记(四)——用 DiskLruCache 实现本地缓存
举报原因:
原因补充:

(最多只允许输入30个字)