前言
图片加载不当导致的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"
}
运行打印如下
参考文章