Bitmap的高效加载和 Cache

Posted Young_xiaoT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
            
        

打完收工!

以上是关于Bitmap的高效加载和 Cache的主要内容,如果未能解决你的问题,请参考以下文章

Bitmap 的加载和 Cache

《Android开发艺术探索》之Bitmap的加载和Cache(十四)

Bitmap的加载和Cache

Android 基础 十二 Bitmap的加载和Cache

Bitmap的加载和Cache

Android艺术——Bitmap高效加载和缓存