@946898963
2020-06-16T14:58:17.000000Z
字数 6175
阅读 2450
Bitmap
inBitmap是在BitmapFactory中的内部类Options的一个变量,简单而言,使用该变量可以复用旧的Bitmap的内存而不用重新分配以及销毁旧Bitmap,进而改善运行效率。
关于Bitmap的相关知识可以查看我写的Android中Bitmap的深入探讨总结。
bitmap是很消耗内存的,当bitmap不用时我们需要回收
bitmap.recycle();
以上代码可以回收bitmap在堆中的内存,回收之后,bitmap不得再重新使用。
Android 2.3(3.0) 前后。
在Android 3.0以前,Bitmap的存储分为两部分,像素数据存储在Native堆上,而Bitmap本身,或者说像素的引用,存储在Java堆上,Java虚拟机本身可以回收掉Java堆上的数据,但是对于Native堆上的数据,不受到Java虚拟机垃圾回收机制的影响,所以,当我们确定一个Bitmap不可用的时候,需要手动的调用recycle()方法去回收掉这部分Native内存(recycle方法是调用jni方法释放内存的),注意recycle()方法是不可逆的,当调用了这个方法后,Bitmap就会被标记为死亡状态,这时候再调用Bitmap的相关方法就会出错,所以只有当我们确定Bitmap不会再被使用的时候,才能调用这个方法。在Android 3.0以后,Bitmap的像素部分和Bitmap本身都是存储在Java堆上的,所以这部分的内存会由Java虚拟机的垃圾回收机制自动进行回收,我们没必要在手动的去调用recycle()方法去释放内存。
当调用recycle()方法后,Bitmap并不会被立即回收掉,只是会被标记为死亡状态,等待垃圾收集器将其进行回收。
建议阅读
Bitmap为什么要调用recycle()方法来显示释放内存
bitmap是很消耗内存的,当bitmap不用时我们需要回收
bitmap.recycle();
以上代码可以回收bitmap在堆中的内存,回收之后,bitmap不得再重新使用。但是,当我们使用listview加载多张图片时,我们都知道,listview的Item有复用的特性,必然经常复用bitmap缓存,这时inBitmap的作用尤为重要。
inBitmap变量是在Android 3.0+版本加入到系统源码当中,也就意味着inBitmap参数只有在Android 3.0+版本及以上能够正常使用,当你的app版本低于3.0的时候,只能使用bitmap.recycle()进行Bitmap的回收操作;在Android 3.0+以上根据系统版本的不同,使用inBitmap的规则也不相同,具体区分如下:
4.4之前的版本inBitmap只能够重用相同大小的Bitmap内存区域。简单而言,被重用的Bitmap需要与新的Bitmap规格完全一致,否则不能重用。
4.4之后的版本系统不再限制旧Bitmap与新Bitmap的大小,只要保证旧Bitmap的大小是大于等于新Bitmap大小即可。
除上述规则之外,旧Bitmap必须是mutable的,这点也很好理解,如果一个Bitmap不支持修改,那么其内存自然也重用不了,代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;//如果为true, 那么该bitmap缓存区可以被修改,否则不可被修改
Ok,关于inBitmap的知识点理论上也就那么多。Google为了帮助我们更好的管理Bitmap,也出了一个视频,视频地址如下:Bitmap使用视频。并且附带上了一个使用inBitmap的Demo:Bitmap的使用Demo。
下面贴视频中两张图更好的帮助一下理解:
使用inBitmap之前:
使用inBitmap之后:
针对上述的理解,这里有一个疑问需要去确认一下:
- inBitmap的解码模式跟新Bitmap的不同是否能够重用成功
解决这个疑问可以查看Google官方的inBitmap Demo来回答问题:
/**
* candidate:旧的图片,targetOptions:新的图片的Options
*/
private fun canUseForInBitmap(candidate: Bitmap, targetOptions: BitmapFactory.Options): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
val width: Int = targetOptions.outWidth / targetOptions.inSampleSize
val height: Int = targetOptions.outHeight / targetOptions.inSampleSize
val byteCount: Int = width * height * getBytesPerPixel(candidate.config)
byteCount <= candidate.allocationByteCount
} else {
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
candidate.width == targetOptions.outWidth
&& candidate.height == targetOptions.outHeight
&& targetOptions.inSampleSize == 1
}
}
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
private fun getBytesPerPixel(config: Bitmap.Config): Int {
return when (config) {
Bitmap.Config.ARGB_8888 -> 4
Bitmap.Config.RGB_565, Bitmap.Config.ARGB_4444 -> 2
Bitmap.Config.ALPHA_8 -> 1
else -> 1
}
}
这里可以看出,只要新的Bitmap的内存小于旧Bitmap的内存大小,即可进行复用的操作,那么跟解码模式没有必然的联系。
示例代码,下面的例子主要用到了Bitmap的采样压缩,同时使用到了inBitmap内存复用。
/**
* 采样率压缩,这个和矩阵来实现缩放有点类似,但是有一个原则是“大图小用用采样,小图大用用矩阵”。
* 也可以先用采样来压缩图片,这样内存小了,可是图的尺寸也小。如果要是用 Canvas 来绘制这张图时,再用矩阵放大
* @param image
* @param compressFormat
* @param requestWidth 要求的宽度
* @param requestHeight 要求的长度
* @return
*/
public static Bitmap compressbySample(Bitmap image, Bitmap.CompressFormat compressFormat, int requestWidth, int requestHeight){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(compressFormat, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inJustDecodeBounds = true;//只读取图片的头信息,不去解析真是的位图
BitmapFactory.decodeStream(isBm,null,options);
options.inSampleSize = calculateInSampleSize(options,requestWidth,requestHeight);
//-------------inBitmap------------------
options.inMutable = true;
try{
if (image != null && canUseForInBitmap(image, options)) {
options.inBitmap = image;
}
}catch (OutOfMemoryError e){
options.inBitmap = null;
System.gc();
}
//---------------------------------------
options.inJustDecodeBounds = false;//真正的解析位图
isBm.reset();
Bitmap compressBitmap;
try{
compressBitmap = BitmapFactory.decodeStream(isBm, null, options);//把ByteArrayInputStream数据生成图片
}catch (OutOfMemoryError e){
compressBitmap = null;
System.gc();
}
return compressBitmap;
}
/**
* 采样压缩比例
* @param options
* @param reqWidth 要求的宽度
* @param reqHeight 要求的长度
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
int inSampleSize = 1;
if (originalHeight > reqHeight || originalWidth > reqHeight){
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) originalHeight / (float) reqHeight);
final int widthRatio = Math.round((float) originalWidth / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
//用来判断bitmap是否能够被复用。
static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
if (candidate == null || !candidate.isMutable()) {
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(targetOptions.inPreferredConfig);
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1;
}
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
static int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
关于inBitmap的使用,可以根据谷歌的官方例子进行设计和开发,不过维护起来需要一定的工作量。当然在市面上成熟的图片框架中,如Glide内部也使用了inBitmap作为缓存复用的一种方式。总而言之,根据项目以及业务来选择实现方式即可,不必过分纠结。
建议阅读:BitmapFactory.Option