本章主要学习Bitmap的加载和Cache,由于Bitmap的特殊性以及android对单个应用所施加的内存限制,导致加载Bitmap很容易出现内存溢出,因此这里主要探讨下如何高效地加载Bitmap以及其中所使用的的缓存策略.
- Bitmap的高效加载
- Android中的缓存策略
一.Bitmap的高效加载
1.核心思想
按一定的采样率把图片缩小后再加载.
2.核心类和核心参数
(1)BitmapFactor提供了四类方法加载图片:
//从文件系统加载一个bitmap对象
decodeFile
//从资源加载一个bitmap对象
decodeResource
//从输入流加载一个bitmap对象
decodeStream
//从字节数组加载一个bitmap对象
decodeByteArray
注意:decodeFile和decodeResource又间接调用了decodeStream方法
(2)BitmapFactory.Options缩放图片
通过BitmapFactory.Options来缩放图片,主要用到了inSampleSize参数,即采样率.
a.inSampleSize的取值
应总是为2的指数
如不为2的指数,那么系统会向下取整并选择一个最接近的2的指数来代替,比如3,系统会选择2来代替
b.inSampleSize的变化
值k为1时,采样后的图片大小为图片的原始大小
值k小于1时,其作用相当于1,无缩放效果
值k大于1时,采样后的图片宽高为原图的1/k,像素为原图的1/k^2,占用内存为原图的1/k^2
(3)inJustDecodeBounds参数
值为true时,BitmapFactory只会解析图片的原始宽高信息,并不会真正地加载图片
值为false时,真正的加载图片到内存
3.获取采样率的流程
(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true,并加载图片.
(2)从BitmapFactory.Options取出原始的宽高信息,他们对应于outWidth和outHeight参数.
(3)根据采样率的规则并结合目标View所需的大小计算出采样率.
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,并重新加载图片.
4.代码实现
/**
* 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
*
* @param res 资源文件对象
* @param resId 要操作的图片id
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样之后的bitmap对象
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//1.设置inJustDecodeBounds=true获取图片尺寸
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//3.计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//4.再设为false,重新从资源文件中加载图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeResource(res,resId,options);
}
/**
* 一个计算工具类的方法, 传入图片的属性对象和想要实现的目标宽高. 通过计算得到采样值
* @param options 要操作的原始图片属性
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样率
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
//2.height、width为图片的原始宽高
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
//加载一个像素为100*100的ImageView
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
二.缓存策略
1.概述
使用缓存的目的是为了避免过多的流量消耗,缓存策略主要包含缓存的添加,获取和删除这三类操作,如何对这些缓存进行操作就是一种策略,不同的策略对应着不同的缓存算法,目前常用的一种缓存算法是LRU(Least Recently Used),LRU是近期最少使用算法,他的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象.采用LRU算法的缓存有两种:LruCache和DiskLruCache.
(1)LruCache
LruCache用于实现内存缓存.它是一个泛型类.内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCacche会移除较早使用的缓存对象,然后再添加新的缓存对象.这里使用了强引用,因此这里要明白一下强引用,软应用和若引用的区别:
强引用:直接的对象引用
软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
弱应用,当一个对象只有弱引用存在时,此对象会随时被gc回收.
具体的实现:
a.计算当前可用内存的大小
b.分配LruCache缓存容量
c.创建LruCache对象并传入LruCache的缓存容量,复写sizeOf方法,计算缓存对象的大小.
d.通过put,get和remove方法.实现缓存的添加,获取和删除.
//获取当前进程可用内存大小
int maxMemory = (int)(Runtime.getRuntime().maxMemory())/1024;
//获取缓存的总容量
int cacheSize = maxMemory/8;
//创建LruCache对象
mLruCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
//计算缓存对象的大小
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
//添加缓存对象
mLruCache.put("test1",bitmap1);
mLruCache.put("test2",bitmap2);
//获取缓存对象
mLruCache.get("test1");
//删除缓存对象
mLruCache.remove("test2");
(2)DisLruCache
DisLruCache用于实现存储设备缓存,即磁盘缓存.,它通过将缓存对象写入文件系统从而实现缓存的效果.
使用步骤:
a.设置DisLruCache的容量.
b.设置缓存目录.
c.通过open的方法,创建DisLruCache对象.
d.利用Editor,SnapShop和remove实现数据的添加,获取和删除.
e.调用flush将数据写入磁盘.
//使用DiskLruCach需要添加添加依赖
//implementation 'com.jakewharton:disklrucache:2.0.2'
public class DiskLruCacheUtil {
//设置DiskLruCache的容量
private static final long DISK_CACHE_SIZE = 1024*1024*50;
private static final int DISK_CACHE_INDEX = 0;
private Context mContext;
private DiskLruCache mDisLruCache;
private DiskLruCache.Editor mEdit;
public void initDiskLruCache(Context context){
mContext= context;
try {
//设置缓存目录
File diskCacheDir = getDisCacheDir(mContext,"bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
//参数1传入存储路径
//参数2传入应用的版本号,一般设置为1,当版本号改变会清空之前的所有的缓存文件,在实际开发中,版本号改变,很多情况下缓存文件仍然有效
//参数3传入单个节点对应的数据的个数,一般设置为1即可
//参数4传入缓存的总大小
mDisLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
private File getDisCacheDir(Context context, String fileName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.contains(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
//当内存卡存在或不可移除时
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath+File.separator+fileName);
}
//DisLruCache添加数据
public void addDisLruCacheData(String url,String data){
//这里以缓存图片为例,因为url可能有特殊字符,这里采用url的MD5值作为key
String key = hashKeyFromUrl(url);
try {
//获取Editor对象
mEdit = mDisLruCache.edit(key);
//创建输出流
OutputStream outputStream = mEdit.newOutputStream(DISK_CACHE_INDEX);
//写入数据
outputStream.write(data.getBytes());
//提交写操作
mEdit.commit();
//写入磁盘
mDisLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//DisLruCache查找数据
public String getDisLruCacheData(String url){
//通过url获取key
StringBuffer sb = new StringBuffer();
String key = hashKeyFromUrl(url);
try {
//获取Snapshot对象
DiskLruCache.Snapshot snapshot = mDisLruCache.get(key);
//创建输入流
InputStream inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
//读取数据
int len;
byte[] bytes = new byte[1024];
if ((len = inputStream.read(bytes)) != -1) {
sb.append(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
//DisLruCache删除数据
public void deleteDisLruCacheData(String url) {
String key = hashKeyFromUrl(url);
//删除缓存
try {
mDisLruCache.remove(key);
} catch (IOException e) {
e.printStackTrace();
}
}
private String hashKeyFromUrl(String url) {
String cacheKey;
try {
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
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();
}
}