ASM学习思路承接上篇,对照字节码CV asm api确实很舒服,慢慢的也能理解一些字节码,虽然不可能一下完全吃透,但是没关系,边学边搜索。毕竟对于我这种菜鸡来说写代码就是熟能生巧的过程。
撸个经典demo监控图片大小,这里就以Glide为例了。源码分析的文章很多,直接上结论:Glide
的SingleRequest
中,有一个requestListeners
,图片加载成功后会遍历这个集合,回调RequestListener.onResourceReady()
。那么思路就很简单了,requestListeners在SingleRequest构造方法中赋值,可以在构造方法结束时插入一个自定义的RequestListener做图片监控。
先看源码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构造方法结尾插入GlideUtil.addListener(requestListeners)
说实话,我还是不熟练手写,先借助插件ASM Bytecode Viewer
生成一下吧。写个工具类HookUtil
->build->右键class文件使用插件。
public class HookUtil {
private List<RequestListener> requestListeners;
private void hook() {
GlideUtil.addListener(requestListeners);
}
}
先看字节码,精简一下。
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
对应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的构造方法<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构造方法结束时插入上述字节码。
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???报错了,咋肥事啊,之前学习的时候搜索了一番,很多人都这么写,咋就空指针了捏,流下了没有技术的泪水T.T。
好吧,断点调试一下Gradle 调试Transform代码,确定进入方法是SingleRequest构造方法没错了。那么只有一个可能,requestListeners在构造方法中传过来时就为空了。找到问题了就好办了,加个判空处理,requestListeners为空时,初始化一个数组,然后再addListener。
修改HookUtil
public class HookUtil {
private List<RequestListener> requestListeners;
private void hook() {
if (requestListeners == null) requestListeners = new ArrayList<>();
GlideUtil.addListener(requestListeners);
}
}
再看一下插件生成的字节码
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
对应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)
}
}
}
}
保险起见测试用例也设置个requestListeners回调,防止插入if语句错误覆盖用户设置的回调。
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
好起来了,两个回调都走了,插桩成功。接下来在GlideUtil自定义的RequestListener中就可以做图片监控了。
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;
}
}
彷佛又学到了一些,仿佛又还是很菜=。=感觉还是得抽时间系统的学习下字节码,心中有底才能稳如泰山啊。