Bitmap 内存模型
-
在 API10 之前,Bitmap 对象本身存在 Dalvik Heap 中,像素是存在 native 中,这样像素并不会占用 Heap 空间,也就不会造成 Heap 内存溢出。但是缺点是Bitmap 对象被回收了,但是 native 层像素回收的时机可能跟 Heap 中 Bitmap 的对象回收时机不对应。
-
API10之后,像素也放在 Dalvik Heap
-
API26 像素又放在 native,在 bitmap 被回收时,通过某一种机制能迅速地通知到 native 层回收对应的像素
如何计算 bitmap 占用内存
- 通过 Bitmap API 在运行时计算
- getRowBytes:Since API Level 1
- getByteCount:Since API Level 12
- getAllocationByteCount:Since API Level 19
public static int getBitmapSize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { //Since API 12
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
getAllocationByteCount
- 给定一张图片,计算加载到内存中的大小
size= width x height x (一个像素占用内存) x 压缩比例
检测不合理图片
在实际开发中,如果服务传给客户端的图片的实际宽高是远大于 ImageView 的宽高,那么这种情况是会浪费内存,那么如何去检测这种不合理的情况呢?
通用方式
给每一个 ImageView 设置一个 PreDrawListener ,在回调中计算 Bitmap 的大小和 ImageView 的大小。
这种方案是可以实现检测功能,但是缺点也很明显,不通用,侵入性比较大。
Hook 方式
Hook 意思就是挂钩
,钩住原有方法,修改执行逻辑,这种 Hook 方法是在运行时插桩。
下面介绍一个 Hook 框架 :Epic
该框架 Hook 的最小粒度为方法层面,该框架支持 4.0-9.0
机型
引入epic框架
- 添加依赖
compile 'me.weishu:epic:0.3.6'
- 创建 XC_MethodHook 的子类,在内部实现 hook 逻辑
使用 epic 来检测不合理图片
- 找到我们的 hook 点,在给一个 ImageView 设置一个 Bitmap 时一般都会调用 setImageBitmap,因此该方法就是 hook 点
public void setImageBitmap(Bitmap bm) {
...
}
- 2.hook setImageBitmap 方法
DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
if (param.thisObject instanceof ImageView) {
final ImageView imageView = (ImageView) param.thisObject;
if (imageView.getDrawable() instanceof BitmapDrawable) {
BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
final Bitmap bitmap = drawable.getBitmap();
if (bitmap != null) {
//bitmap 宽高
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//视图宽高
int viewWidth = imageView.getWidth();
int viewHeight = imageView.getHeight();
if (viewHeight > 0 && viewWidth > 0) {//view 有宽高
//当图片宽高都大于视图宽高的2倍时就报出警告
if (bitmapWidth >= viewWidth << 2 && bitmapHeight >= viewHeight << 2) {
warn(bitmapWidth, bitmapHeight, viewWidth, viewHeight);
}
} else {
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
int viewWidth = imageView.getWidth();
int viewHeight = imageView.getHeight();
if (bitmapWidth >= viewWidth << 2 && bitmapHeight >= viewHeight << 2) {
warn(bitmapWidth, bitmapHeight, viewWidth, viewHeight);
}
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
}
}
}
}
}
});
- 3.显示警告信息:检测出图片大小不合理
private void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
StringBuffer msg = new StringBuffer();
msg.append("图片大小不合理:")
.append("bitmapWidth=").append(bitmapWidth)
.append(",bitmapHeight=").append(bitmapHeight)
.append(",viewWidth=").append(viewWidth)
.append(",viewHeight=").append(viewHeight);
//不合理
Log.e(TAG, Log.getStackTraceString(new Throwable(msg.toString())));
}
- 4.log 检测结果
总结
本文使用的介绍了两种方式来检测不合理图片:
通用的方法:
- 侵入性大
- 通用性差
- 没有兼容性问题
而 hook 的方式的优缺点如下::
- 无侵入性
- 通用性强
- 兼容性问题大,开源方案不能带到线程环境
参考
本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。
记录于 2019年4月16号
网友评论