Bitmap的高效加载和 Cache

Bitmap的高效加载和 Cache

Bitmap 的高效加载

如何加载一个图片呢?BitmapFactory 类提供了四类方法:

  • decodeFile 文件
  • decodeResource 资源
  • decodeStream 输入流
  • decodeByteArray 字节数组

可以采用 BitmapFactory.Options 来加载所需尺寸的图片,这样就可以按照一定的采样率来加载缩小后的图片,这样可以较低内存占有从而在一定程度上避免 OOM,提高 Bitmap 加载时的性能。

通过 BitmapFactory.Options 来缩放图片,主要用到它的 inSampleSize 参数,即采样率。

当inSampleSize = 1 时,采样后的图片大小为图片原始大小;当 inSampleSize 大小等于2时,图片宽高均为原来的 1/2 ,像素数为原图的 1/4 ,内存也为原图的 1/4.

如何获取采样率呢?可以遵循如下的规则:

  1. 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 true 并加载图片。
  2. 从 BitmapFactory.Options 中获取图片的原始宽高信息,他们对应于 outWidth 和 outHeight 参数。
  3. 根据采样率规则并结合目标 View 的所需大小计算出采样率 inSampleSize。
  4. 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 false,然后重新加载图片。

注:inJustDecodeBounds设为 true时,BitmapFactory 只会解析图片的原始宽/高信息,并不会正在去加载图片,所以这个操作是轻量级的。

Android 中的缓存策略

目前常用的的一中缓存算法是 LRU(Least Recently Used),近期最少使用算法,它的核心思想就是当缓存满时,会优先删除那些近期最少使用的缓存对象。

采用 LRU 算法的缓存有两种: LruCache 和 DiskLruCache,LruCache 用于实现内存缓存,而 DiskLruCache 用于存储设备缓存。

LruCache

LruCache 是一个泛型类,它内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象,其提供了 get 和 put 方法来完成缓存的获取和添加操作,当缓存满时,LruCache 会移除较早使用的缓存对象,然后再添加新的缓存对象。

  • 强引用:直接对象的引用;
  • 软引用:当一个对象只用软引用存在时,系统内存不足时此对象会被 gc 回收;
  • 弱引用:当一个对象只用弱引用存在时,此对象会随时被 gc 回收;

另外 LruCache 是线程安全的:

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;
    ...
}

LruCache 典型初始化过程:

        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };

这里只需要提供缓存的总容量大小(一般为进程可用内存的1/8)并重写 sizeOf 方法即可.sizeOf方法作用是计算缓存对象的大小。

还有获取和添加方法,都比较简单:

mMemoryCache.get(key)
mMemoryCache.put(key,bitmap)

DiskLruCache

DiskLruCache 用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存的效果。

1. DiskLruCache 的创建

它提供了 open 方法用于创建自身:

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  • 第一个参数表示磁盘缓存在文件系统中额存储路劲。
  • 第二个参数为版本号,一般为1即可。
  • 第三个参数表示单个节点所对应的数据的个数,一般为1即可
  • 第四个参数表示缓存的总大小

2. DiskLruCache 的缓存添加

DiskLruCache 的缓存添加的操作是通过 Editor 完成的,Editor 表示一个缓存对象的编辑对象。

首先获取图片 url 所对应的 key,然后根据 key 通过 editor() 来获取 Editor 对象,如果这个缓存正在被编辑,那么 editor()会返回 null,即 DiskLruCache 不允许同时编辑一个缓存对象。

    private String hashKeyFromUrl(String url) {
        String cacheKey;

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey = bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return null;
    }

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

将图片的 url 转成 key 之后,就可以获取 Editor 对象了。

        String key = hashKeyFromUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor == null){
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
        }

有了文件输出流,当从网络下载图片时,图片就可以通过这个文件输出流写入到文件系统:

    public boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final 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 (IOException e) {
            Log.e(TAG, "downloadBitmap failed." + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(out);
            MyUtils.close(in);
        }
        return false;
    }

经过上面的步骤,其实并没有真正地将图片写入文件系统,还必须通过 Editor 的 commit() 来提交写入操作:

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

3. DiskLruCache 的缓存查找

缓存查找也需要将 url 转换成 key,然后通过 DiskLruCache 的 get 方法得到一个 Snapshot 对象,接着通过 Snapshot 对象即可得到缓存的文件输入流,有了文件输入流,就可以得到 Bitmap 对象。

        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

打完收工!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值