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

上一篇记录了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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值