《Android开发艺术探索》笔记——Bitmap的加载和Cache(二)

来源:互联网 发布:磁条卡写卡软件 编辑:程序博客网 时间:2024/05/21 17:05

上一篇记录了Bitmap的(高效)加载,那么这一篇就记录Cache。
对于网络上的图片,第一次使用就需要从网络上去下载下来,但如果每次都去从网络上下载,那就非常浪费流量了,所以需要做缓存。另外的添加了缓存也要做好删除缓存,毕竟有些过久地图片或是很少会再用到的图片,就需要删掉了,释放空间。

这里用到的缓存算法是LRU(Least Recently Used),最近最少使用算法。在该算法的基础上有衍生出两种缓存,LruCache和DiskLruCache,前者用于实现内存缓存,后者用于实现存储设备的缓存。所以这里就是将这两者结合,实现了一个ImageLoader(图片加载器),这里用到了三级缓存(网络缓存,磁盘缓存和内存缓存)。

1. LruCache

引用原文的话:

LruCache是一个泛型类,它内部采用一个LinkedHashMap强引用的方式存储外界的缓存对象。
另外LruCache是线程安全的。

短短两句话涉及了不少概念。
LinkedHashMap如果去查找Lru算法的话,基本都是在它的基础上实现的;
从构造方法里可以看出它是个泛型类:
这里写图片描述

然后关于强引用:
这里写图片描述
这里写图片描述

之所以说线程安全,因为在LruCache里的添加,删除,获取都是有同步锁机制的。
这里写图片描述

这里写图片描述

这里写图片描述

1.1 LruCache的使用

 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//单位KB        //设定缓存的容量为总容量的1/8        int cacheSize = maxMemory / 8;        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap value) {                //返回bitmap大小的计算                return value.getRowBytes() * value.getHeight() / 1024;            }        };

总的来说就是提供缓存的总容量大小然后重写sizeof方法。这里缓存的总容量大小为当前进程的可用内存的1/8,sizeof里就返回的是bitmap对象的大小计算。这两个的单位应该一致,所以这里都除以1024。
然后是获取的方法:

mMemoryCache.get(key);

添加的方法:

mMemoryCache.put(key, bitmap);

2. DiskLruCache

2.1 DiskLruCache的使用

2.1.1 引用

用DiskLruCache来做磁盘缓存,可以通过依赖来获取:

 compile 'com.jakewharton:disklrucache:2.0.2'

2.1.2 创建

DiskLruCache需要通过open方法来创建,而不是普通的构造方法:

//利用open方法来创建,第一个参数是存储路径,第二个参数是版本号,//第三个参数是单个节点对应的数据的个数,第四个参数是缓存的总大小   mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);

2.1.3 添加

与前面的LruCache一样,DiskLruCache也是用到了LinkedHashMap,那么在LruCache里的操作都用到了“key”这个东西,这里也同样用到了key。由于在这个ImageLoader里他们都是操作同一个东西,所以当然是一样的。作者在这里是用图片的url来作key,但需要作一些转换,用url的md5值来作为key,主要是防止url里可能有些特殊字符导致出错:

    /**     * 将图片的url转换成key,这里采用url的md5的值作为key     * @param url     * @return     */    private String hashKeyFromUrl(String url) {        String cacheKey;        try {            MessageDigest messageDigest = MessageDigest.getInstance("MD5");            messageDigest.update(url.getBytes());            cacheKey = bytesToHexString(messageDigest.digest());        } catch (NoSuchAlgorithmException e) {            cacheKey = String.valueOf(url.hashCode());        }        return cacheKey;    }private String bytesToHexString(byte[] bytes) {        StringBuilder stringBuilder = new StringBuilder();        for (int i = 0; i < bytes.length; i++) {            String hex = Integer.toHexString(0xFF & bytes[i]);            if (hex.length() == 1) {                stringBuilder.append('0');            }            stringBuilder.append(hex);        }        return stringBuilder.toString();    }

那么DiskLruCache的缓存添加是通过Editor来完成的,通过edit()方法和key就可以获取到这个Editor对象,进而可以获得文件输出流。

String key = hashKeyFromUrl(url);        //使用Editor进行缓存添加的操作        DiskLruCache.Editor editor = mDiskLruCache.edit(key);        if (editor != null) {            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);        }

那么怎么操作这个文件输出流呢?或者说它的数据从哪来呢?
其实它的数据是从它的更上一级,网络缓存那里来的,我们通过url去做网络请求的时候会获得一个输入流,然后我们把输入流写到这个输出流里,那么它就有数据了。

private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {        HttpURLConnection urlConnection = null;        BufferedOutputStream out = null;        BufferedInputStream in = null;        try {            URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);            int b;            while ((b = in.read()) != -1) {                out.write(b);            }            return true;        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                in.close();                out.close();            } catch (IOException e) {                e.printStackTrace();            }        }        return false;    }

现在我们通过网络请求将流写给了磁盘缓存,但需要通过进一步的确认操作来真正的写入。即commit()方法,所以把前面的一块代码修改下:

 if (editor != null) {            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);            if (downloadUrlToStream(url, outputStream)) {                editor.commit();            } else {                editor.abort();            }            mDiskLruCache.flush();        }

2.1.4 获取

DiskLruCache对缓存的获取则是通过它的Snapshot对象,与上面的类似,它是通过get()方法和key得到的,然后可以进一步的得到文件输入流,那拿到了文件输入流我们通过上一篇的BitmapFactory提供的解码方法就可以得到一个Bitmap对象了。

 String key = hashKeyFromUrl(url);        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);        if (snapshot != null) {            //获取该图片的文件输入流            FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);            //获取该文件输入流的文件描述            FileDescriptor fileDescriptor = fileInputStream.getFD();            //通过文件描述得到想要的bitmap            bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);        }

这里对文件输入流的处理是用了FileDescriptor, 也就是对应于decodeFileDescriptor()方法。为什么这里要用这个方法?
作者给的解释是FileInputStream是一种有序的文件流,两次decodeStream调用影响文件流的位置属性,在第二次decodeStream的时候会得到null,那这里我做了测试,确实在第二次的时候会得到null。

阅读全文
0 0