Bitmap 內(nèi)存模型
在 API10 之前,Bitmap 對象本身存在 Dalvik Heap 中,像素是存在 native 中,這樣像素并不會占用 Heap 空間,也就不會造成 Heap 內(nèi)存溢出。但是缺點是Bitmap 對象被回收了,但是 native 層像素回收的時機可能跟 Heap 中 Bitmap 的對象回收時機不對應(yīng)。
API10之后,像素也放在 Dalvik Heap
API26 像素又放在 native,在 bitmap 被回收時,通過某一種機制能迅速地通知到 native 層回收對應(yīng)的像素
如何計算 bitmap 占用內(nèi)存
- 通過 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();
}

- 給定一張圖片,計算加載到內(nèi)存中的大小
size= width x height x (一個像素占用內(nèi)存) x 壓縮比例
檢測不合理圖片
在實際開發(fā)中,如果服務(wù)傳給客戶端的圖片的實際寬高是遠大于 ImageView 的寬高,那么這種情況是會浪費內(nèi)存,那么如何去檢測這種不合理的情況呢?
通用方式
給每一個 ImageView 設(shè)置一個 PreDrawListener ,在回調(diào)中計算 Bitmap 的大小和 ImageView 的大小。
這種方案是可以實現(xiàn)檢測功能,但是缺點也很明顯,不通用,侵入性比較大。
Hook 方式
Hook 意思就是掛鉤,鉤住原有方法,修改執(zhí)行邏輯,這種 Hook 方法是在運行時插樁。
下面介紹一個 Hook 框架 :Epic
該框架 Hook 的最小粒度為方法層面,該框架支持 4.0-9.0 機型
引入epic框架
- 添加依賴
compile 'me.weishu:epic:0.3.6'
- 創(chuàng)建 XC_MethodHook 的子類,在內(nèi)部實現(xiàn) hook 邏輯
使用 epic 來檢測不合理圖片
- 找到我們的 hook 點,在給一個 ImageView 設(shè)置一個 Bitmap 時一般都會調(diào)用 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 檢測結(jié)果

總結(jié)
本文使用的介紹了兩種方式來檢測不合理圖片:
通用的方法:
- 侵入性大
- 通用性差
- 沒有兼容性問題
而 hook 的方式的優(yōu)缺點如下::
- 無侵入性
- 通用性強
- 兼容性問題大,開源方案不能帶到線程環(huán)境
參考
本文是筆者學習之后的總結(jié),方便日后查看學習,有任何不對的地方請指正。
記錄于 2019年4月16號