「性能优化4.0」运行期间检测不合理的图片

0.879字数 679阅读 542

Bitmap 内存模型

  • 在 API10 之前,Bitmap 对象本身存在 Dalvik Heap 中,像素是存在 native 中,这样像素并不会占用 Heap 空间,也就不会造成 Heap 内存溢出。但是缺点是Bitmap 对象被回收了,但是 native 层像素回收的时机可能跟 Heap 中 Bitmap 的对象回收时机不对应。

  • API10之后,像素也放在 Dalvik Heap

  • API26 像素又放在 native,在 bitmap 被回收时,通过某一种机制能迅速地通知到 native 层回收对应的像素

如何计算 bitmap 占用内存

  • 通过 Bitmap API 在运行时计算
  1. getRowBytes:Since API Level 1
  2. getByteCount:Since API Level 12
  3. 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 来检测不合理图片

    1. 找到我们的 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 检测结果
log

总结

本文使用的介绍了两种方式来检测不合理图片:

通用的方法:

  • 侵入性大
  • 通用性差
  • 没有兼容性问题

而 hook 的方式的优缺点如下::

  • 无侵入性
  • 通用性强
  • 兼容性问题大,开源方案不能带到线程环境

参考

本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。

记录于 2019年4月16号