美文网首页android基础知识Android性能「性能优化」
「性能优化4.0」运行期间检测不合理的图片

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

作者: 未见哥哥 | 来源:发表于2019-04-20 23:42 被阅读18次

    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号

    相关文章

      网友评论

        本文标题:「性能优化4.0」运行期间检测不合理的图片

        本文链接:https://www.haomeiwen.com/subject/qjwqgqtx.html