ASM學(xué)習(xí)思路承接上篇,對照字節(jié)碼CV asm api確實很舒服,慢慢的也能理解一些字節(jié)碼,雖然不可能一下完全吃透,但是沒關(guān)系,邊學(xué)邊搜索。畢竟對于我這種菜雞來說寫代碼就是熟能生巧的過程。
擼個經(jīng)典demo監(jiān)控圖片大小,這里就以Glide為例了。源碼分析的文章很多,直接上結(jié)論:Glide的SingleRequest中,有一個requestListeners,圖片加載成功后會遍歷這個集合,回調(diào)RequestListener.onResourceReady()。那么思路就很簡單了,requestListeners在SingleRequest構(gòu)造方法中賦值,可以在構(gòu)造方法結(jié)束時插入一個自定義的RequestListener做圖片監(jiān)控。
先看源碼SingleRequest
private SingleRequest(
Context context,
GlideContext glideContext,
@NonNull Object requestLock,
@Nullable Object model,
Class<R> transcodeClass,
BaseRequestOptions<?> requestOptions,
int overrideWidth,
int overrideHeight,
Priority priority,
Target<R> target,
@Nullable RequestListener<R> targetListener,
@Nullable List<RequestListener<R>> requestListeners,
RequestCoordinator requestCoordinator,
Engine engine,
TransitionFactory<? super R> animationFactory,
Executor callbackExecutor) {
...
this.requestListeners = requestListeners;
...
//插入邏輯
}
老樣子,寫個工具類GlideUtil
public class GlideUtil {
public static List addListener(List<RequestListener> requestListeners) {
requestListeners.add(new RequestListener() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Object resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
Log.d("chenxuan----->", "GlideHook");
return false;
}
});
return requestListeners;
}
}
只需在SingleRequest構(gòu)造方法結(jié)尾插入GlideUtil.addListener(requestListeners)
說實話,我還是不熟練手寫,先借助插件ASM Bytecode Viewer生成一下吧。寫個工具類HookUtil->build->右鍵class文件使用插件。
public class HookUtil {
private List<RequestListener> requestListeners;
private void hook() {
GlideUtil.addListener(requestListeners);
}
}

先看字節(jié)碼,精簡一下。
private hook()V
L0
ALOAD 0
GETFIELD com/chenxuan/hook/HookUtil.requestListeners : Ljava/util/List;
INVOKESTATIC com/chenxuan/hook/GlideUtil.addListener (Ljava/util/List;)Ljava/util/List;
POP
對應(yīng)asm api
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/chenxuan/hook/HookUtil", "requestListeners", "Ljava/util/List;");
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/chenxuan/hook/GlideUtil", "addListener", "(Ljava/util/List;)Ljava/util/List;", false);
methodVisitor.visitInsn(POP);
話不多說,CV到MethodVisitor。
ImageClassVisitor在visitMethod中判斷是否是SingleRequest的構(gòu)造方法<init>。
class ImageClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, classVisitor) {
private var target: String? = null
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
target = name
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
if (target == "com/bumptech/glide/request/SingleRequest" && name == "<init>" && descriptor != null) {
return ImageMethodVisitor(mv, access, name, descriptor)
}
return mv
}
}
ImageMethodVisitor在SingleRequest構(gòu)造方法結(jié)束時插入上述字節(jié)碼。
object ImageMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun onMethodExit(opcode: Int) {
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(
GETFIELD,
"com/bumptech/glide/request/SingleRequest",
"requestListeners",
"Ljava/util/List;"
)
mv.visitMethodInsn(
INVOKESTATIC,
"com/chenxuan/hook/GlideUtil",
"addListener",
"(Ljava/util/List;)Ljava/util/List;",
false
)
mv.visitInsn(POP)
super.onMethodExit(opcode)
}
}
}
}
跑個測試用例MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Glide
.with(this)
.load("https://pic.3gbizhi.com/2014/0430/20140430043839656.jpg")
.into(findViewById(R.id.ivAvatar))
}
}
wtf???報錯了,咋肥事啊,之前學(xué)習(xí)的時候搜索了一番,很多人都這么寫,咋就空指針了捏,流下了沒有技術(shù)的淚水T.T。

好吧,斷點調(diào)試一下Gradle 調(diào)試Transform代碼,確定進(jìn)入方法是SingleRequest構(gòu)造方法沒錯了。那么只有一個可能,requestListeners在構(gòu)造方法中傳過來時就為空了。找到問題了就好辦了,加個判空處理,requestListeners為空時,初始化一個數(shù)組,然后再addListener。
修改HookUtil
public class HookUtil {
private List<RequestListener> requestListeners;
private void hook() {
if (requestListeners == null) requestListeners = new ArrayList<>();
GlideUtil.addListener(requestListeners);
}
}
再看一下插件生成的字節(jié)碼
private hook()V
L0
ALOAD 0
GETFIELD com/chenxuan/hook/HookUtil.requestListeners : Ljava/util/List;
IFNONNULL L1
ALOAD 0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
PUTFIELD com/chenxuan/hook/HookUtil.requestListeners : Ljava/util/List;
L1
FRAME SAME
ALOAD 0
GETFIELD com/chenxuan/hook/HookUtil.requestListeners : Ljava/util/List;
INVOKESTATIC com/chenxuan/hook/GlideUtil.addListener (Ljava/util/List;)Ljava/util/List;
POP
對應(yīng)asm api
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/chenxuan/hook/HookUtil", "requestListeners", "Ljava/util/List;");
Label label1 = new Label();
methodVisitor.visitJumpInsn(IFNONNULL, label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
methodVisitor.visitFieldInsn(PUTFIELD, "com/chenxuan/hook/HookUtil", "requestListeners", "Ljava/util/List;");
methodVisitor.visitLabel(label1);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/chenxuan/hook/HookUtil", "requestListeners", "Ljava/util/List;");
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/chenxuan/hook/GlideUtil", "addListener", "(Ljava/util/List;)Ljava/util/List;", false);
methodVisitor.visitInsn(POP);
CV到ImageMethodVisitor
object ImageMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun onMethodExit(opcode: Int) {
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(
GETFIELD,
"com/bumptech/glide/request/SingleRequest",
"requestListeners",
"Ljava/util/List;"
)
val label1 = Label()
mv.visitJumpInsn(IFNONNULL, label1)
mv.visitVarInsn(ALOAD, 0)
mv.visitTypeInsn(NEW, "java/util/ArrayList")
mv.visitInsn(DUP)
mv.visitMethodInsn(
INVOKESPECIAL,
"java/util/ArrayList",
"<init>",
"()V",
false
)
mv.visitFieldInsn(
PUTFIELD,
"com/bumptech/glide/request/SingleRequest",
"requestListeners",
"Ljava/util/List;"
)
mv.visitLabel(label1)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(
GETFIELD,
"com/bumptech/glide/request/SingleRequest",
"requestListeners",
"Ljava/util/List;"
)
mv.visitMethodInsn(
INVOKESTATIC,
"com/chenxuan/hook/GlideUtil",
"addListener",
"(Ljava/util/List;)Ljava/util/List;",
false
)
mv.visitInsn(POP)
super.onMethodExit(opcode)
}
}
}
}
保險起見測試用例也設(shè)置個requestListeners回調(diào),防止插入if語句錯誤覆蓋用戶設(shè)置的回調(diào)。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Glide
.with(this)
.load("https://pic.3gbizhi.com/2014/0430/20140430043839656.jpg")
.listener(object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
Log.d("chenxuan----->", "onResourceReady")
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(findViewById(R.id.ivAvatar))
}
run起來,查看log

好起來了,兩個回調(diào)都走了,插樁成功。接下來在GlideUtil自定義的RequestListener中就可以做圖片監(jiān)控了。
public class GlideUtil {
public static List addListener(List<RequestListener> requestListeners) {
requestListeners.add(new RequestListener() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Object resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
if (resource instanceof BitmapDrawable) {
((BitmapDrawable) resource).getBitmap();
}
return false;
}
});
return requestListeners;
}
}
彷佛又學(xué)到了一些,仿佛又還是很菜=。=感覺還是得抽時間系統(tǒng)的學(xué)習(xí)下字節(jié)碼,心中有底才能穩(wěn)如泰山啊。