ASM學習思路承接上篇舷手,對照字節(jié)碼CV asm api確實很舒服,慢慢的也能理解一些字節(jié)碼侨核,雖然不可能一下完全吃透,但是沒關系,邊學邊搜索犬缨。畢竟對于我這種菜雞來說寫代碼就是熟能生巧的過程。
擼個經(jīng)典demo監(jiān)控圖片大小棉浸,這里就以Glide為例了怀薛。源碼分析的文章很多,直接上結論:Glide
的SingleRequest
中迷郑,有一個requestListeners
枝恋,圖片加載成功后會遍歷這個集合创倔,回調RequestListener.onResourceReady()
。那么思路就很簡單了焚碌,requestListeners在SingleRequest構造方法中賦值畦攘,可以在構造方法結束時插入一個自定義的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構造方法結尾插入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
對應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構造方法結束時插入上述字節(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???報錯了,咋肥事啊杯矩,之前學習的時候搜索了一番栈虚,很多人都這么寫,咋就空指針了捏史隆,流下了沒有技術的淚水T.T魂务。
好吧,斷點調試一下Gradle 調試Transform代碼泌射,確定進入方法是SingleRequest構造方法沒錯了粘姜。那么只有一個可能,requestListeners在構造方法中傳過來時就為空了熔酷。找到問題了就好辦了孤紧,加個判空處理,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
對應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中就可以做圖片監(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;
}
}
彷佛又學到了一些钾菊,仿佛又還是很菜=帅矗。=感覺還是得抽時間系統(tǒng)的學習下字節(jié)碼,心中有底才能穩(wěn)如泰山啊煞烫。