Asm+Gradle8修改aar

329 阅读2分钟

Asm+Gradle8修改aar

前景提要

  • 最近遇到一个需求,需要修改第三方aar中的内容,

    通过百度搜索获得的大部分内容都是,使用重打包的方式修改aar,

    该方法确实可行,不过在学习了Asm了解原理后,觉得也可以用来修改aar中的字节码

  • 不过网上关于Asm修改aar的实践文章几乎与没有,

    在自我折腾了很久以后,决定把踩坑内容,以及过程写出来

基本概念

  • apk的编译流程大概可以这么理解

    .java->.classes->.dex

  • Asm则是可以直接修改.classes文件的工具

    如下图所示

image-20240509214912660.png

开始修改aar

创建以下的目录,

  1. 一个是用来构建需要修改的aar
  2. 一个用来制作修改aar的插件

image-20240509221507154.png

使用as编译出一个 能够修改的aar

image-20240509221805002.png

image-20240509221836302.png ·

编写一个要修改的类

package com.onBit.lib_base.base
​
object HelloUtils {
    fun greet() {
        println("Hello world")
    }
}

编写插件

build.gradle

plugins {
    id("java-gradle-plugin")
    //gradle 支持Kotlin
    alias(libs.plugins.org.jetbrains.kotlin.jvm)
    id("maven-publish")
}
​
/**
 * 定义发布 每次更新后 执行才能更新
 */
gradlePlugin {
    plugins {
        create("onepixelPlugin") {
            group = "com.onepixel.plugin"
            version = "1.0.0"
            //插件的唯一标识,使用插件的时候就是这个id
            id = "com.onepixel.plugin"
            //PageAnalysisPlugin的全类名 取代resources声明
            implementationClass = "com.onepixel.plugin.HelloPlug"
        }
    }
}
​
dependencies {
    // 使得 gradle 提供的 api
    implementation gradleApi()
    implementation localGroovy()
​
    // 最新版本的 asm 库是 9.0
    implementation 'com.android.tools.build:gradle:8.0.0'
    implementation 'com.android.tools.build:gradle-api:8.0.0'
    implementation 'org.ow2.asm:asm:9.6'
    implementation 'org.ow2.asm:asm-util:9.2'
    implementation 'org.ow2.asm:asm-commons:9.2'
}
​
publishing {
    repositories {
        maven {
            url = uri("../plugin") //本地maven地址
        }
    }
}
​
java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

plung

package com.onepixel.plugin
​
import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import com.onepixel.transform.HelloTransform
import org.gradle.api.Plugin
import org.gradle.api.Project
​
class HelloPlug : Plugin<Project> {
    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        //在compileDebugJavaWithJavac 后执行
        androidComponents.onVariants(androidComponents.selector().all()) { variant ->
            variant.instrumentation.transformClassesWith(HelloTransform::class.java, InstrumentationScope.ALL,){
​
            }
​
            variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
        }
    }
}
​

使用Asm ByteCode Viewer 生成代码

image-20240510000708891.png

制作修改代码ClassVisitor

package com.onepixel.transform
​
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.AdviceAdapter
​
class HelloClassVisitor(api: Int, classVisitor: ClassVisitor?) :
    ClassVisitor(api, classVisitor) {
​
    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        println("Class Name>>$name")
        super.visit(version, access, name, signature, superName, interfaces)
    }
​
    override fun visitMethod(
        access: Int,
        name: String,
        desc: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        println("   Method >>$name")
        val methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions)
​
        if (name == "greet" && desc == "()V") {
            // 只修改 greet 方法
            return object : AdviceAdapter(Opcodes.ASM9, methodVisitor, access, name, desc) {
                override fun onMethodEnter() {
                    super.onMethodEnter()
                    methodVisitor.visitCode();
                    methodVisitor.visitLdcInsn("Hello 112341235235");
                    methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                    methodVisitor.visitInsn(SWAP);
                    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
                    methodVisitor.visitInsn(RETURN);
                    methodVisitor.visitMaxs(2, 1);
                    methodVisitor.visitEnd();
                }
            }
        }
        return methodVisitor
    }
​
​
}
​

检查结果

进行编译后检查生成结果,

使用jadx检查

image-20240510001027399.png

大功告成,aar也是同样的方式

坑点

  • 当时 我用debug包测试的时候 print方法是可以正常打印的

  • 但是缓存release后 print 不会正常打印类名,导致我一直以为没有正常编译,耽误了我好几天,

    后面输出才发现以及改了代码,

  • 缺点:

    • 因为是aar,提供给项目使用,在使用的时候会是修改前的代码如何要修改或者添加参数的话,在写代码的过程中是编译不通过的

      只能在编译后才能看见是否修改完,可以使用反射的方式,不过不够直观

  • 结论:

    可以使用Asm修改aar,因为是在classes转dex文件时候拦截,但是实际开发中不方便使用,所以还是推荐使用重打包方式,

    Asm,还是应该使用在插装等代码增强上面