AGP7 适配 Transform

AGP7 适配 Transform

环境:

Gradle 版本:

distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip

AGP版本:

buildscript {  
ext.kotlin_version = "1.6.20"
dependencies {
classpath "com.android.tools.build:gradle:7.2.2"
}
}

plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}

创建插件

1、插件配置

gradlePlugin {  
val tinker by plugins.creating {
// 插件id
id = "tinker-plugin"
// 插件的实现类
implementationClass = "com.lyy.TinkerPlugin"
}
}

2、插件

class TinkerPlugin: Plugin<Project> {  

private val TAG = "TinkerPlugin"

override fun apply(target: Project) {
val androidComponentsExtension =
target.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponentsExtension.onVariants { variant ->
log("variant: ${variant.name}")
variant.instrumentation.apply {
// 处理直接码的类
transformClassesWith(TinkerTransform::class.java, InstrumentationScope.ALL) {}
setAsmFramesComputationMode(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
}
}
}

private fun log(msg: String) {
println("[$TAG]: $msg")
}
}

AndroidComponentsExtension
是Android Gradle插件(AGP)提供的一个扩展点,它允许开发者在构建过程中访问Android应用程序组件(如Application、Library、Test等)。该扩展点提供了许多有用的信息,例如组件的名称、依赖项、构建类型、Flavor等。通过使用AndroidComponentsExtension,插件可以更加智能地操作应用程序组件。

transformClassesWith
相当于transform,作用是将class转换为dex

  • InstrumentationScope.ALL 范围是所有的类,包括工程中的源代码和第三方依赖库的jar包,也就是说,我们不需要再手动处理jar的class了
  • InstrumentationScope.PROJECT 范围是当前工程中所有的类,不包括第三方依赖库jar包中的class

处理字节码

1、创建ClassVisitor

用于匹配需要修改的类

abstract class TinkerTransform : AsmClassVisitorFactory<InstrumentationParameters.None> {  

override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return object : ClassVisitor(Opcodes.ASM5, nextClassVisitor) {
val className = classContext.currentClassData.className
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val oldMethodVisitor =
super.visitMethod(access, name, descriptor, signature, exceptions)
if (className == "com.tencent.tinker.loader.SystemClassLoaderAdder" && name == "checkDexInstall" && descriptor == "(Ljava/lang/ClassLoader;)Z") {
return TinkerVisitor(className, oldMethodVisitor, access, name, descriptor)
}
return oldMethodVisitor
}
}
}

override fun isInstrumentable(classData: ClassData): Boolean {
return true
}
}

2、创建AdviceAdapter

修改方法的字节码

class TinkerVisitor(  
private val className: String,
nextMethodVisitor: MethodVisitor,
access: Int,
name: String?,
descriptor: String?
) : AdviceAdapter(Opcodes.ASM5, nextMethodVisitor, access, name, descriptor) {
override fun visitInsn(opcode: Int) {
// super.visitInsn(opcode)
println("visitInsn, name = ${name}, opcodeAndSource = ${opcode}")
if (opcode == IRETURN){
println("开始修改返回")
mv.visitInsn(ICONST_1)
mv.visitInsn(IRETURN)
return
}
super.visitInsn(opcode)
}
}
  • visitMethodInsn是ASM的MethodVisitor类中的一个方法,用于访问方法调用指令。当ASM访问到字节码中的方法调用指令时,就会回调visitMethodInsn方法。
public void visitMethodInsn(
int opcode, // 方法调用指令的操作码,如INVOKEVIRTUAL、INVOKESTATIC等
String owner, // 调用方法的类的全限定名
String name, // 调用方法的方法名
String descriptor, // 调用方法的描述符,如"(Ljava/lang/String;)V"
boolean isInterface // 调用方法是否是接口方法
);

visitMethodInsn 中修改method的指令,改方法无法修改方法的返回,如果要修改方法的返回,需要在visitInsn中操作

public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { 
if (opcode == Opcodes.INVOKESTATIC
&& owner.equals("java/lang/Math")
&& name.equals("random"))
{
// 修改静态方法调用指令
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
super.visitInsn(Opcodes.DUP); super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(D)V", false);
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
  • visitInsn 是ASM的MethodVisitor类中的一个方法,用于访问无操作数指令。当ASM访问到字节码中的无操作数指令时,就会回调visitInsn方法

    比如修改方法的返回

override fun visitInsn(opcode: Int) {  
// super.visitInsn(opcode)
println("visitInsn, name = ${name}, opcodeAndSource = ${opcode}")
if (opcode == IRETURN){
println("开始修改返回")
mv.visitInsn(ICONST_1) // 将方法修改为true
mv.visitInsn(IRETURN)
return
}
super.visitInsn(opcode)
}

获取asm字节码

1、创建一个类,写一个测试方法

public fun test():Boolean{  
return true
}

2、使用as的插件直接看,右键
image.png

3、在右侧的插件那就可以看到需要的字节码了
image.png

效果

原来的:
image.png

修改字节码后的:
image.png

   Vector Landscape Vectors by Vecteezy
作者

AriaLyy

发布于

2023-04-19

许可协议

CC BY-NC-SA 4.0

评论