本次主要是包含3个方面的内容,首先讲述如何有效的加载一个bitmap。由于bitmap特殊性以及Android对单个应用所施加的内容限制。这个导致加载bitmap的时候很容易出现内存溢出。
java.lang.OutOfMemoryError:bitmap size exceeds VM budget.
根据上述情况,在android开发中需要经常对bitmap做缓存,通过缓存策略,我们不需要每次都从网络上请求图片或者从内存中加载图片,会极大的提高加载效率以及产品的用户体验。目前常用的缓存策略是LruCache和DiskLruCache。其中LruCache被看做是内存缓存,DiskLruCache被看做是存储缓存。通俗的来说就是:最少使用算法。
算法的核心思想:
当缓存快满的时候,会淘汰近期最少使用的缓存目标,很显然Lru算法的思想是很容易被接受的。
Bitmap的高校加载:
这个介绍biemap的高效加载之前,先说一下如何加载一个biemap。BitmapFactory类提供了四类方法:decodeFile、decodeRessource、decodeStream和decodeByteArray,分别是用于支持从文件系统、资源、输入流以及字节数组中加载出一二Bitmap对象。
如何高效的加载一个Bitmap对象:其实核心思想也很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。通过inSampleSize()可以获取到采样率来加载缩小后的图片,将缩小后的图片在对应的位置显示,这样会降低内存占用从而在一定程度上避免OOM。提高了加载时的性能。
inSampleSize方法的值为1的时候,表示采样后的图片大小为图片的原始大小,当值为2的时候:
公式:
采样后的图片大小其宽/高均为原图大小的1/2,像素数为原图的1/4
通过采样率就能有效的加载图片,那么到底如何获取采样率:
(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
(2)BitmapFactory.Options中获取图片的原始宽高信息,它们对应于outWidth和outHeight参数。
(3)根据采样率的规则结合目标view的所需大小计算出采样率inSampleSize。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后冲洗加载图片。
将上面的四个流程用程序来实现,如下:
Android中的缓存策略:
缓存的机制:
当程序第一次从网上加载图片后,就将其缓存到内存设备上,这样下次使用这张图片就不用再从网络上获取了,这样就节省了用户的流量,提高了应用的用户体验。往往还会把图片在内存中再缓存一份,这样当应用打算从网络上请求一张图片的时,程序会先从内存中获取,如果内存设备中也没有,就从存储设备中获取,如果存储设备也没有,那就从网络上获取。
目的:
内存中加载图片比在存储中加载图片要快。既提高了程序的效率又为用户节省了不不必要的流量开销。
缓存策略主要包含缓存的添加、获取和删除三类操作。
添加:
获取:
删除:缓存是有大小的,所以会有删除的操作。
LRU算法:
概念:常用的缓存算法,LRU是近期最少使用算法。核心思想就是当缓存满时,会优先淘汰近期使用最少的缓存对象。LRU算法的缓存有两种:LurCache和DiskLruCache,
LurCache用于实现内存缓存,DiskLruCache则充当了存储设备缓存。
LurCache:
LurCache是android3.1提供的缓存类,通过support-V4兼容包可以兼容到早期的android版本。
LurCache是一个泛型类,内部采用LinkedHashMap以强引用的方式存储外界的缓存对象。提供了get和put方法来完成缓存的获取和添加操作。
public class
LruCache<
K
,
V
> {
private final
LinkedHashMap<
K
,
V
>
map
;
初始化:
/**
* LruCache的创建
* return: 返回的是位图每一行 所占用的空间数*位图的行数
* 最后除以1024得到的是KB。
*/
int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
//总容量的大小为当前进程的可用内存的1/8,单位是KB。
int cacheSize = maxMemory/8;
LruCache<String,Bitmap>linkedHashMap = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
获取:
linkedHashMap .get(key);
添加:linkedHashMap.put(key,bitmap);
删除:linkedHashMap.remove(key);
DiskLruCache:
概念:DiskLruCache用于实现存储设备缓存,即磁盘缓存,他通过将缓存对象写入文件系统从而实现缓存效果。
创建:
public static DiskLruCache open(File directory,int appVersion,iint valueCount,long maxSize)
private static final long Disk_cache_size = 1024*1024*50; //50MB
File diskCacheDir = getDiskCacheDir(mContent,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
添加:
private String hashKeyFormurl(String url) {
String
cacheKey;
try {
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (Exception e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < digest.length; i++) {
String hex = Integer.toHexString(0xff & digest[i]);
if(hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
调用:
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor =
mDiskLruCache .edit(key);
if(editor != null ){
OutPutStream ops = editor.newOutPutStream(DISK_CACHE_INDEX);
}
当从网络下载图片时,图片流可以通过这个文件输入流写入到文件系统上,实现代码如下:
public boolean downloadUrlToStream(String urlString,
OutputStream outputStream) {
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 (Exception e) {
// TODO: handle exception
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return false;
}
调用:
OutputStream outputStream = Editor.newOutPutStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
上述代码,图片已经被正确的写入到文件系统中了,
缓存查找:
通过
DiskLruCache方法的get方法得到一个Snapshot对象,紧接着通过Snapshot得到一个文件输入流,自然就能得到bitmap对象了。为了避免OOM,需要参考前面所说的,对图片进行缩放处理,再予以显示,事例代码如下显示:
String key = hashKeyFormurl(url);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get("key");
if (snapshot != null) {
FileInputStream fileInputStream = (FileInputStream) snapshot
.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
Object mImageResizer;
Bitmap bitmap = mImageResizer
.decodeSampleBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key,bitmap);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}