Android ASM实现大图监控

821 阅读1分钟

前言

图片加载不当导致的OOM在安卓中十分场景 , 有没有什么方法可以检测图片的加载大小并且是无侵入的

当然是有的 , go on

至于怎么在项目中使用asm这里就不说了 , 网上很多爽文 , 我们直接进入正题

1. 新建HookImageView 继承自ImageView

因为所有图片加载最终都会调用ImageView的setImageDrawable , setXXX 方法进行设置图片 , 我们可以在这里获取图片的大小

abstract class HookImageView @JvmOverloads constructor(
    context: Context,
    attrset: AttributeSet? = null,
    attr: Int = 0
) :
    ImageView(context, attrset, attr), MessageQueue.IdleHandler {


    companion object{

        val MAX_ALARM_MULTIPLE =2
        val MAX_ALARM_IMAGE_SIZE =20*1024
        val TAG ="HookImageView"
    }
    override fun setImageDrawable(drawable: Drawable?) {
        super.setImageDrawable(drawable)
        println("$TAG setImageDrawable(drawable: Drawable?)")
        printStack()
        addImageLegalMonitor()

    }

    private fun addImageLegalMonitor() {
        Looper.myQueue().removeIdleHandler(this);
        Looper.myQueue().addIdleHandler(this);
    }

    override fun queueIdle(): Boolean {
        val drawable = drawable
        val background = background
        if (drawable != null) {
            checkIsLegal(drawable, "图片")
        }
        try {
            if (background != null) {
                checkIsLegal(background, "背景")
            }
        } catch ( e :Exception) {
            e.printStackTrace()
        }

        return false
    }

    private  fun checkIsLegal(drawable: Drawable, tag: String) {
        val viewWidth = measuredWidth
        val viewHeight = measuredHeight
        val drawableWidth = drawable.intrinsicWidth
        val drawableHeight = drawable.intrinsicHeight
        // 大小告警判断
        val imageSize: Int = calculateImageSize(drawable)
        if (imageSize > MAX_ALARM_IMAGE_SIZE) {
            Log.e(TAG, "图片加载不合法," + tag + "大小 -> " + imageSize)
            dealWarning(drawableWidth, drawableHeight, imageSize, drawable)
        }
        // 宽高告警判断
        if (MAX_ALARM_MULTIPLE * viewWidth < drawableWidth) {
            Log.e(TAG, "图片加载不合法, 控件宽度 -> " + viewWidth + " , " + tag + "宽度 -> " + drawableWidth)
            dealWarning(drawableWidth, drawableHeight, imageSize, drawable)
        }
        if (MAX_ALARM_MULTIPLE * viewHeight < drawableHeight) {
            Log.e(TAG, "图片加载不合法, 控件高度 -> " + viewHeight + " , " + tag + "高度 -> " + drawableHeight)
            dealWarning(drawableWidth, drawableHeight, imageSize, drawable)
        }
    }

    private fun printStack() {
        val stringBuilder = StringBuilder()
        for (stackTraceElement in Thread.currentThread().getStackTrace()) {
            stringBuilder
                .append(stackTraceElement.toString())
                .append("\n")
        }
        Log.e(TAG, " 方法调用 -> ${stringBuilder.toString()}")
    }

    private  fun calculateImageSize(drawable: Drawable): Int {
        if (drawable is BitmapDrawable) {
            val bitmap: Bitmap = (drawable as BitmapDrawable).getBitmap()
            return bitmap.byteCount
        }
        val pixelSize = if (drawable.opacity !== PixelFormat.OPAQUE) 4 else 2
        return pixelSize * drawable.intrinsicWidth * drawable.intrinsicHeight
    }
}

这里方便测试 , 把MAX_ALARM_IMAGE_SIZE 弄为20kb , 即图片大小超过20kb就报警

接着就是用asm把AppCompatImageView 的父类ImageView替换成HookImageView了

2.替换AppCompatImageView 父类ImageView

替换AppCompatImageView 父类ImageView成HookImageView的Asm关键代码很少 , 只有两行

  if (classNode.name == "androidx/appcompat/widget/AppCompatImageView") {
      classNode.superName = "com/jansir/androidasmplugin/HookImageView"
  }

运行打印如下

参考文章

juejin.cn/post/691899…