阅读 289

自定义Gradle插件+ASM 实战

实战:在每一个BaseActivity的子类的onCreate方法开头添加一些判断逻辑。

使用自定义插件的Transform利用ASM去修改代码后,查看反编译的字节码是

实现步骤:

1. 编写自定义插件

因为本插件只给本工程用,就不用upload Task,直接创建一个buildSrc的java的module.

  在App的build.gradle里面去应用插件 (apply plugin:'com.docwei.plugin') 即可。你们很疑惑这个插件名是哪里的插件名,其实插件名就是:com.docwei.plugin.properties这个properties文件的文件名。

2. 拆解自定义插件的transform

  • 1. 自定义插件需要创建一个类(比如:MyPlugin)去实现Plugin接口,重写里面的apply方法,其参数project即为应用了这个插件的project,这里是app的build.gradle应用了。
  • 2. 字节码的处理:使用google提供的transform获取所有的字节码,修改字节码当然是使用ASM啦。 在Transform里面的输入类型有两种,一种是jarInput:包含本地和远端的jar,一种是directoryInput包含我们自己编写的代码,这里我们的MainActivity、SecondeAct,ThirdAct就是在这种输入类型里的。谨记:一旦注册了transform,就要处理输入和输出(默认实现是没有处理的),否则编译失败。

3. ASM的处理 (重点)

  • a. 先获取BaseActivity的所有子类

   ClassReader就是利用io流读取字节码的包装类
   Set<String> superClazzs = new HashSet<>();
   superClazzs.add("com/docwei/transformwithasm/activity/BaseActivity");
   directoryInput.file.eachFileRecurse { File file ->
        if (file.isFile() && shouldProcessClassName(file.name)) {
            FileInputStream fis = new FileInputStream(file);
            ClassReader cr = new ClassReader(fis);
            //接下来要过滤出BaseActivity的子类,然后去处理
            if (hasImplSpecifiedInterfaces(cr, superNames)) {
                byte[] bytes = scanClass(cr)
                FileOutputStream fos = new FileOutputStream(file.parentFile.absolutePath + File.separator + file.name)
                fos.write(bytes)
                fos.close()
            }
            fis.close()
        }
    } 
  static boolean hasImplSpecifiedInterfaces(ClassReader reader, Set<String> superClazzs) {
        if ("java/lang/Object".equals(reader.getClassName())) {
            return false;
        }
        try {
            if (superClazzs.contains(reader.getSuperName())) {
                return true;
            } else {
                ClassReader parent = new ClassReader(reader.getSuperName());
                return hasImplSpecifiedInterfaces(parent, superClazzs);
            }
        } catch (IOException e) {
            return false;
        }
    }
复制代码
  • b. 定位到子类的onCreate方法

static class ScanClassVisitor extends ClassVisitor {
    ScanClassVisitor(int api, ClassVisitor cv) {
        super(api, cv)
    }
    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (name == 'onCreate' && desc == '(Landroid/os/Bundle;)V') {
            // 先获取原始的方法
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions)
            //再修改方法
            return new ScanMethodVisitor(Opcodes.ASM6, mv)
        }
        return super.visitMethod(access, name, desc, signature, exceptions)
    }
  }
复制代码
  • c. 在onCreate方法里面添加ASM字节码操作。

     你可能觉得自己不会写Asm,不懂它的Api,不要紧,有工具,请速速安装:

  1. 先把MainActivity里要添加的ASM提示代码在onCreate里面加上,

  2. 右键 show ByteCode outline 查看这几行红框里面的ASM的字节码操作

  添加的提示代码对应的ASM的操作代码对比:
  3. ASM操作的代码都在这里了,直接在MyTransform类的ScanClassVisitor的visitCode方法里复制ASM的操作代码, 导包,处理报错,把行号vistiLineNumber删了,只保留:如下的

  4. 删除MainActivity里面添加的ASM提示代码,build一下,可以在/Users/xxx/Desktop/TransformWithASM/app/build/intermediates/transforms/docweiTransform/debug/26/com/docwei/transformwithasm/activity/MainActivity.class查看MainActivity的字节码是否修改成功。

最后:感谢在实现这个功能时抓头挠耳搜索到的所有相关博客,太多了就不贴链接了。

github项目链接   star 个呗。