一个基于 Android ASM 字节码插桩的 Gradle 插件,用于在开发阶段自动检测应用中加载的大图(图片尺寸远大于显示尺寸),帮助开发者定位和优化图片内存使用问题。
- 无侵入式集成: 无需修改任何业务代码或 XML 布局,通过 Gradle 插件自动实现。
- 编译时插桩: 利用 ASM 在编译期间对字节码进行操作,对运行时性能影响极小。
- 覆盖广泛: 能够监控所有通过
ImageView.setImageBitmap()
和ImageView.setImageDrawable()
设置图片的场景,包括 Glide、Picasso 等主流图片加载库的最终调用。 - 环境隔离: 默认只在
debug
构建模式下生效,对release
包无任何影响。 - 清晰的告警: 通过 Logcat 输出详细的警告信息,包括图片原始尺寸、View 显示尺寸,方便快速定位问题。
本插件的核心原理是编译时字节码插桩(Compile-time Bytecode Instrumentation)。
- Gradle 插件机制: 通过自定义 Gradle 插件,我们可以接入 Android Gradle Plugin (AGP) 的构建流程。
- ASM 字节码操作: 利用强大的 ASM 库,插件在
.class
文件转换为.dex
文件的过程中,对字节码进行扫描和修改。 - 方法拦截: 插件会遍历所有方法,并找到所有调用
android.widget.ImageView
的setImageBitmap()
和setImageDrawable()
方法的地方。 - 代码织入 (Weaving): 在这些方法调用点之前,插件会动态地插入一段我们自己的代码。这段代码会:
- 复制栈顶的
ImageView
实例和要设置的Bitmap
或Drawable
对象。 - 调用一个预先写好的运行时辅助类(
BigImageDetector.check()
)并传入这些对象。
- 复制栈顶的
- 运行时检测: 当应用在 Debug 模式下运行时,每次有图片被设置到
ImageView
,我们注入的代码就会被触发。BigImageDetector
会在一个安全的时机(imageView.post()
)获取ImageView
的实际显示尺寸和Bitmap
的原始尺寸。- 如果
Bitmap
尺寸显著大于ImageView
的显示尺寸(默认阈值为 2 倍),就在 Logcat 中打印一条警告日志。
这个插件设计为通过 buildSrc
目录集成到你的 Android 项目中。
在你的 Android app
模块的源代码中(例如 app/src/main/java/com/yourpackage/performance/
),创建 BigImageDetector.kt
文件。
// app/src/main/java/com/yourpackage/performance/BigImageDetector.kt
package com.yourpackage.performance
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import android.widget.ImageView
object BigImageDetector {
private const val TAG = "BigImageDetector"
private const val SIZE_THRESHOLD = 2.0 // 告警阈值
@JvmStatic
fun check(view: View?, drawable: Drawable?) {
if (view !is ImageView || drawable == null) return
if (drawable !is BitmapDrawable) return
val bitmap = drawable.bitmap ?: return
checkBitmap(view, bitmap)
}
@JvmStatic
fun check(view: View?, bitmap: Bitmap?) {
if (view == null || bitmap == null) return
checkBitmap(view as ImageView, bitmap)
}
private fun checkBitmap(imageView: ImageView, bitmap: Bitmap) {
imageView.post {
val viewWidth = imageView.width
val viewHeight = imageView.height
if (viewWidth <= 0 || viewHeight <= 0) return@post
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
if (bitmapWidth > viewWidth * SIZE_THRESHOLD &&
bitmapHeight > viewHeight * SIZE_THRESHOLD
) {
val warningMessage = "大图警告: " +
"图片尺寸 ($bitmapWidth x $bitmapHeight) " +
"远大于显示尺寸 ($viewWidth x $viewHeight)"
Log.w(TAG, warningMessage)
}
}
}
}
在你的项目根目录下,创建 buildSrc
目录,并配置以下文件:
buildSrc/build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
repositories {
google()
mavenCentral()
}
plugins {
`kotlin-dsl`
}
dependencies {
implementation("com.android.tools.build:gradle:7.0.4") // 替换为你项目的 AGP 版本
implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-commons:9.2")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
buildSrc/src/main/resources/META-INF/gradle-plugins/com.yourcompany.big-image-detector.properties
implementation-class=com.yourcompany.plugin.BigImageDetectorPlugin
buildSrc/src/main/kotlin/com/yourcompany/plugin/BigImageDetectorPlugin.kt
(以及其他插件类)
将之前提供的四个插件 Kotlin 文件 (BigImageDetectorPlugin.kt
, BigImageDetectorClassVisitorFactory.kt
, BigImageDetectorClassAdapter.kt
, BigImageDetectorMethodAdapter.kt
) 放置到 buildSrc/src/main/kotlin/com/yourcompany/plugin/
目录下。
确保
BigImageDetectorPlugin.kt
包含只在 debug 模式下生效的逻辑。
在你的 app/build.gradle.kts
文件中,通过插件 ID 应用插件。
// android/app/build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
// 应用我们自定义的插件
id("com.yourcompany.big-image-detector")
}
// ...
- 修改
buildSrc
后,Android Studio 会提示你同步 Gradle。点击 Sync Now。 - 以 Debug 模式运行你的应用。
- 操作应用,加载图片。
- 打开 Logcat,并使用
BigImageDetector
作为 TAG 进行过滤。如果存在大图加载,你将看到类似以下的警告信息:
W/BigImageDetector: 大图警告: 图片尺寸 (2048x1536) 远大于显示尺寸 (400x300)
- 仅限 Debug: 本插件设计为仅在
debug
构建下工作,不会对release
包产生任何影响。 - 性能: 虽然插件本身经过优化,但字节码插桩会略微增加编译时间。
cacheWidth
/cacheHeight
: 发现大图后,最佳的优化方式通常是使用Image
Widget(如 Glide、Coil、Image.network
)提供的cacheWidth
/cacheHeight
或类似的缩放参数,让图片库在解码时就将图片缩放到合适的尺寸。