关于Bitmap高效加载
Bitmap是如何加载的
介绍bitmap高效加载之前,先说一下bitmap是如何加载的。首先呢,bitmap在安卓里面你可以理解为就是指的是一张图片,如何加载bitmap呢,BitmapFactory提供了四个方法:decodeFile、decodeResource、decodeStream和decodeByteArray,对应的就是从文件系统、资源、输入流和字节数组中加载出一个bitmap对象,然后再调用各类view的set**Bitmap方法,我看了一下这四个方法的实现,前俩个方法最后还是会调用decodeStream方法,也就是说实际上最后都是通过流或者字节数组去生成一个bitmap对象,具体如何生成的,这里就不去讨论了,底层是几个native方法,感兴趣的 自己去瞅瞅吧。
Bitmap实现高效加载要素
如何高效的加载Bitmap呢?四个字,按需加载,按照控件的大小来加载所需的尺寸的图片。说详细点儿就是,通过BitmapFactory.options的inSampleSize参数,也就是采样率来缩放图片大小,从而减少内存占用和绘制时间来做到高效加载。举个例子,比如一张图片是1024x1024那么大,而你的imageview实际上是100x100,如果bitmap不处理,按照ARG8888格式,那么加载这一张图片占有的内存为1024x1024x4=4MB。那如果我们通过BitmapFactory.options来缩放图片,当inSampleSize(采样率)等于1的时候,大小不变,比如为2,那么采样后的图片宽高都为原先图片宽高的1/2,像素大小就是原来的1/4,imageview大小为100*100时,那么宽高就大约缩小了10倍,这时候所占内存大小就是102x102x4=0.04MB,结果对比一下就知道了吧,下面看看代码如何实现这个流程:
public static Bitmap decodeSampledFromResuorce(Resources res,
int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 最关键在此,把options.inJustDecodeBounds = true;
* 表示只读图片,不加载到内存中,设置这个参数为ture,就不会给图片分配内存空间,但是可以获取到图片的大小等属性; 设置为false, 就是要加载这个图片
*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
//计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight, reqWidth)
;
option.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options,
int reqHeight, int reqWidth){
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;
}
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
主要流程如下:
1.将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
2.从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
3.根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
4.将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
这里我再贴段代码,主要是用来传入网络图的url生成bitmap,因为之前碰到了一个坑,这里记录一下。
当时是用HttpURLConnection去做请求,亲娘诶,他有个建立连接的步骤,图片要是比较大,那是真的耗时,返回半天无响应(debug是那里耗时比较久,可能是别的原因),所以我用Rxjava配合Retrofit写了个方法,但是没有做缓存,项目实际没这个需求。
private void getUrlBitMap(final String imageUrl) {
commObserver_url = new CommObserver<ResponseBody>(getApplicationContext(), false) {
@Override
public void success(ResponseBody data) {
try {
InputStream inputStream = data.byteStream();
byte[] inputStream2ByteArr = inputStream2ByteArr(inputStream);
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 最关键在此,把options.inJustDecodeBounds = true;
* 表示只读图片,不加载到内存中,设置这个参数为ture,就不会给图片分配内存空间,但是可以获取到图片的大小等属性; 设置为false, 就是要加载这个图片
* InputStream 被调用两次,第一次调用流被关闭清空了!!!
*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(inputStream2ByteArr, 0, inputStream2ByteArr.length, options);
// BitmapFactory.decodeStream(inputStream, null, options);
originalWidth = options.outWidth;
originalHeight = options.outHeight;
options.inJustDecodeBounds = false;
Message message = new Message();
message.obj = BitmapFactory.decodeByteArray(inputStream2ByteArr, 0, inputStream2ByteArr.length, options);
weakHandler.sendMessage(message);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void error(Throwable e) {
ALog.e(e.toString());
}
};
RetrofitFactory.getInstance().create(AppApi.class)
.getImage(imageUrl).subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe(commObserver_url);
}
Bitmap的缓存策略
其实缓存策略,很多都是通用的原理,这个原理就是LRU(least recently used)算法 ,最近最少使用。
我们不可能无限的去缓存,一般来说我们会去设置一个值,当缓存的大小快要越过这个值的时候,我们就会删除一些缓存,要删除那些缓存呢?不怎么用的呗,那就是最近最少使用的咯。
Lru算法安卓的实现主要是俩个类,一个是LruCache,另外一个是DiskLruCache,瞅名字就知道了,一个是内存缓存,一个是磁盘缓存。说到这是不是想起了Glide的三级缓存,其实都差不多,都这么个原理,下面说一下这俩个类的用法:
LruCache使用demo:
int MaxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);// kB 获取当前进程的可用内存
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key,Bitmap bitmap){
//bitmap.getByteCount() = bitmap.getRowBytes() * bitmap.getHeight();
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;// KB
}
}//初始化
mMemoryCache.get(key)
mMemoryCache.put(key,bitmap)//存取值
mMemoryCache.remove()//删除某一个值
DiskLruCache使用demo:
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB
File diskCacheDir = new File(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
DiskLruCache不能通过new来实例化,只能通过open方法,其中四个参数分别对应的是:diskCacheDir 缓存文件夹, 1 应用版本号,一般写为1,1 单个节点所对应的数据的个数,一般写1, 缓存大小。
算了,没时间了 ,详情参考这个:Android缓存的俩种实现方式