目录
![](https://img.haomeiwen.com/i8850933/d2c8fb9a704b4684.png)
参考文章
Android 编译插桩(一): ASM
Android 编译插桩(二): Gradle Transform
Android 编译插桩(三):Transform + ASM
Java ASM系列:(028)修改已有的方法(删除-清空方法体)
我的代码是参考这些文章代码写的,主体是以前三篇文章的代码为基础
使用方法
1.编译使用插件
这里自定义了一个插件用来对字节码进行操作
![](https://img.haomeiwen.com/i8850933/56498191dcba5c3b.png)
首先我们需要找到这个Gradle任务,双击进行编译打包
![](https://img.haomeiwen.com/i8850933/10798f4924b320d5.png)
打包成功后会生成如下目录
![](https://img.haomeiwen.com/i8850933/7be50f71d88fb73e.png)
然后我们需要在项目的gradle文件中进行引用
![](https://img.haomeiwen.com/i8850933/d1734d7a246ee249.png)
然后在application的model下的gradle中应用插件
![](https://img.haomeiwen.com/i8850933/bdb20913a54f3118.png)
2.使用ASM清空特定方法体
这里在Activity中加了一个点击事件,这次是将点击事件的方法体进行清除
![](https://img.haomeiwen.com/i8850933/6724c5e16eb6e192.png)
这里我们在插件的MethodEmptyBodyVisitor中修改
首先在visitMethod函数中找到OnClickListener的onClick方法(通过判断函数签名,函数名等找到特定函数)
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
String mInterfaceStr = "";
if(mInterface != null && mInterface.length > 0){
for(int i = 0 ; i < mInterface.length ; i++){
mInterfaceStr += mInterface[i];
}
}
if (mv != null && name.contains("onClick") && mInterfaceStr.contains("android/view/View$OnClickListener") && descriptor.contains("(Landroid/view/View;)V")) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
generateNewBody(mv, owner, access, name, descriptor,signature,exceptions);
return null;
}
}
return mv;
}
然后我们在generateNewBody中进行处理
protected void generateNewBody(MethodVisitor mv, String owner, int methodAccess, String methodName, String methodDesc,String signature, String[] exceptions) {
// (1) method argument types and return type
Type t = Type.getType(methodDesc);
Type[] argumentTypes = t.getArgumentTypes();
Type returnType = t.getReturnType();
// (2) compute the size of local variable and operand stack
boolean isStaticMethod = ((methodAccess & Opcodes.ACC_STATIC) != 0);
int localSize = isStaticMethod ? 0 : 1;
for (Type argType : argumentTypes) {
localSize += argType.getSize();
}
int stackSize = returnType.getSize();
// (3) method body
mv.visitCode();
if (returnType.getSort() == Type.VOID) {
mv.visitInsn(RETURN);
}
else if (returnType.getSort() >= Type.BOOLEAN && returnType.getSort() <= Type.INT) {
mv.visitInsn(ICONST_1);
mv.visitInsn(IRETURN);
}
else if (returnType.getSort() == Type.LONG) {
mv.visitInsn(LCONST_0);
mv.visitInsn(LRETURN);
}
else if (returnType.getSort() == Type.FLOAT) {
mv.visitInsn(FCONST_0);
mv.visitInsn(FRETURN);
}
else if (returnType.getSort() == Type.DOUBLE) {
mv.visitInsn(DCONST_0);
mv.visitInsn(DRETURN);
}
else {
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
}
mv.visitMaxs(stackSize, localSize);
mv.visitEnd();
}
可以看到编译后的class文件中方法体已经清除了
![](https://img.haomeiwen.com/i8850933/e4fa984e97a06e44.png)
3.使用ASM替换特定方法体
我们修改generateNewBody方法为
protected void generateNewBody(MethodVisitor mv, String owner, int methodAccess, String methodName, String methodDesc,String signature, String[] exceptions) {
// (1) method argument types and return type
Type t = Type.getType(methodDesc);
Type[] argumentTypes = t.getArgumentTypes();
Type returnType = t.getReturnType();
// (2) compute the size of local variable and operand stack
boolean isStaticMethod = ((methodAccess & Opcodes.ACC_STATIC) != 0);
int localSize = isStaticMethod ? 0 : 1;
for (Type argType : argumentTypes) {
localSize += argType.getSize();
}
int stackSize = returnType.getSize();
// (3) method body
mv.visitCode();
String mInterfaceStr = owner;
if(exceptions != null && exceptions.length > 0){
for(int i = 0 ; i < exceptions.length ; i++){
mInterfaceStr += exceptions[i];
}
}
//插入替换代码
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "com/yxhuang/asm/MainActivity$1", "this$0", "Lcom/yxhuang/asm/MainActivity;");
mv.visitMethodInsn(INVOKESTATIC, "com/yxhuang/asm/TTT", "test", "(Landroid/content/Context;)V", false);
if (returnType.getSort() == Type.VOID) {
mv.visitInsn(RETURN);
}
else if (returnType.getSort() >= Type.BOOLEAN && returnType.getSort() <= Type.INT) {
mv.visitInsn(ICONST_1);
mv.visitInsn(IRETURN);
}
else if (returnType.getSort() == Type.LONG) {
mv.visitInsn(LCONST_0);
mv.visitInsn(LRETURN);
}
else if (returnType.getSort() == Type.FLOAT) {
mv.visitInsn(FCONST_0);
mv.visitInsn(FRETURN);
}
else if (returnType.getSort() == Type.DOUBLE) {
mv.visitInsn(DCONST_0);
mv.visitInsn(DRETURN);
}
else {
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
}
mv.visitMaxs(stackSize, localSize);
mv.visitEnd();
}
这是一段跳转其他Activity的代码,原代码如下
public class TTT {
//跳转A页面
public static void test(Context context){
context.startActivity(new Intent(context,A.class));
}
}
未被替换的代码如下
mTvHello.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"aaa",Toast.LENGTH_SHORT).show();
}
});
替换后的class如下
![](https://img.haomeiwen.com/i8850933/5af42a6d78881b18.png)
辅助工具
由于字节码的插桩具有一定难度,因此我们可以通过ASM Bytecode Viewer Support Kotlin这款插件来辅助
![](https://img.haomeiwen.com/i8850933/9018397dbe3e2c27.png)
我们安装完插件后可以在想要生成对应代码的原文件里右键选择ASM Bytecode Viewer来生成
![](https://img.haomeiwen.com/i8850933/f69d372489c8b861.png)
生成的代码如下所示
![](https://img.haomeiwen.com/i8850933/df1bf718a9b652cb.png)
然后我们可以通过选择ASMified来查看ASM插桩的代码
![](https://img.haomeiwen.com/i8850933/6921295c47b25c6b.png)
网友评论