目录
参考文章
Android 编译插桩(一): ASM
Android 编译插桩(二): Gradle Transform
Android 编译插桩(三):Transform + ASM
Java ASM系列:(028)修改已有的方法(删除-清空方法体)
我的代码是参考这些文章代码写的,主体是以前三篇文章的代码为基础
使用方法
1.编译使用插件
这里自定义了一个插件用来对字节码进行操作
首先我们需要找到这个Gradle任务,双击进行编译打包
打包成功后会生成如下目录
然后我们需要在项目的gradle文件中进行引用
然后在application的model下的gradle中应用插件
2.使用ASM清空特定方法体
这里在Activity中加了一个点击事件,这次是将点击事件的方法体进行清除
这里我们在插件的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文件中方法体已经清除了
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如下
辅助工具
由于字节码的插桩具有一定难度,因此我们可以通过ASM Bytecode Viewer Support Kotlin这款插件来辅助
我们安装完插件后可以在想要生成对应代码的原文件里右键选择ASM Bytecode Viewer来生成
生成的代码如下所示
然后我们可以通过选择ASMified来查看ASM插桩的代码