Bitmap的加载和Cache————读书笔记
2016-04-29 15:42
288 查看
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);
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); } }
相关文章推荐
- [精]Oracle 内存结构详解
- Unity5打包assetbundle
- Java Web 与 QtQuick
- 实验三 进程调度模拟程序
- Linux内核分析——Linux内核学习总结
- 集合框架(用LinkedList模拟栈数据结构的集合并测试案例)
- CentOS虚拟机中安装VMWare Tools
- 调试器工作原理(1):基础篇
- 【Leetcode】:96. Unique Binary Search Trees 问题 in Go语言
- 面试题12
- Light oj 1140 - How Many Zeroes? 数位dp
- Maven项目有工程报错,但是不影响运行
- 传统的错误处理方法
- java集合迭代器Iterator中的remove陷阱
- js 弹出确认 取消对话框
- Java多线程之synchronized和volatile
- nyoj_62 笨小熊
- Kafka Confluent 简介
- Ext.js添加子组件
- 丢掉幻想--《疯狂动物城》观后感