Bitmap的加载和Cache————读书笔记

Bitmap的加载和Cache

Bitmap的高效加载

BitmapFactory类提供了四类方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分别支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象。
采用BitmapFactory.Options来加载所需尺寸的图片。通过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只会解析图片的原始宽高信息,并不会去真正地加载图片,所以这个操作是轻量级的。

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        Log.d(TAG, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }

Android中的缓存策略

缓存策略主要包含缓存的添加、获取和删除这三类操作。
最常用的缓存算法是LRU,近期最少用算法。采用此算法的缓存有两种:LruCache和DiskLruCache。

LruCache

LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,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 bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };

sizeOf方法的作用是计算缓存的大小,这里大小的单位要和总容量的单位一致。
添加缓存对象

mMemoryCache.put(key, bitmap);

获取缓存对象

mMemoryCache.get(key);

DiskLruCache

DiskLruCache用于实现存储设备缓存,即磁盘缓存,它通过缓存对象写入文件系统从而实现缓存的效果。
1. DiskLruCache的创建
DiskLruCache提供open方法用于创建自身。

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

参数1表示磁盘缓存在文件系统中的存储路径;参数2表示应用版本号;参数3表示单个节点所对应的数据的个数一般设为1;参数4表示缓存总大小。
典型创建过程

        private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);   
  1. DiskLruCache的缓存添加

DiskLruCache的缓存添加的操作是通过Editor完成的,Editor表示一个缓存对象的编辑对象。以图片缓存为例,首先需要获取图片的url所对应的key,然后根据key就可以通过edit()来获取Editor对象。

    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
        checkNotClosed();
        validateKey(key);
        Entry entry = lruEntries.get(key);
        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
                && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
            return null; // snapshot is stale
        }
        if (entry == null) {
            entry = new Entry(key);
            lruEntries.put(key, entry);
        } else if (entry.currentEditor != null) {
            return null; // another edit is in progress
        }

        Editor editor = new Editor(entry);
        entry.currentEditor = editor;

        // flush the journal before creating files to prevent file leaks
        journalWriter.write(DIRTY + ' ' + key + '\n');
        journalWriter.flush();
        return editor;
    }

典型使用代码:

 private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    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。

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

获取Editor对象。
有了文件输出流,可以写入到文件系统上。

    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()来提交写入过程,如有异常可以通过Editor的abort()来退回整个操作。

            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对象。本文前面介绍了采样率,但是那种方法对FileInputStream的缩放存在问题,原因是FileInputStream是一种有序的文件流,而两次decodeStream调用影响了文件流的位置属性,导致了第二次decodeStream时得到的是null。为了解决这一问题,可以通过文件流来得到它所对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor方法来加载一张缩放后的图片。
过程如下:

        Bitmap bitmap = null;
        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、付费专栏及课程。

余额充值