前言:

最近在工作中接触到了字节码插桩相关的知识,所以以本文来整理并实践一下相关的知识体系。

字节码插桩:

  • AOP编程  
  • 打包流程
  • 自定义Gradle Task
  • 查看,识别Java字节码
  • ASM使用
  • Transform

AOP编程思想:

面向切面编程思想,与面向过程和面向对象编程不同,AOP可以理解s为处理项目中一些统一的事物的过程,比如统计所有log,所有的点击事件监听,某个方法的全局操作等等。

打包流程:

java → class+jar → classes.dex+aaset+res → resource.zip+dex → apk

参考:Android APK打包流程_Davis

自定义grade插件:

Gradle以Groovy作为脚本语言,Groovy类似于java的方言。

Grade由project组成,一个Project包含n个Task,Task由n个Action组成,Action即执行代码块。

构建工具会根据每个build.grade文件创建出Project实例,初始化的时候会去settings.gradle文件下拿到摇构建的项目。

settings.gradle文件:

rootProject.name = "aop_asm_study"

include ':app'

可以其中的include,告诉Gradle需要参与构建的项目。

Task是gradle执行的最小单元,构建,编译,打包,debug,test等都是执行了某一个task。

Task会按依赖关系顺序执行。

编写Gradle插件主要有三种方法:

  • build.gradle脚本中直接使用。这种方式就是直接在Android Studio app moudle的build.gradle中进行插件的编写,优点是不用再上传插件到maven或者其它地方,项目就可以直接使用;缺点也很明显,就是只能在自己的项目中使用,不能复用,这个不是我们今天要说的。

android item封装 android 封包_android item封装

可以看到,可以在工程的build.gradle中直接写task,这里输出一句话,然后在右边的Gradle列表里:Tasks->other中找到myCustionTask

android item封装 android 封包_AndroidAOP_02

双击运行:

android item封装 android 封包_Android切面编程_03

  • buildSrc中使用。这种方式需要在项目中新建一个model命名为buildSrc,这个目录就用来存放自定义插件。然后在src/main中建立两个目录,一个就是存放代码的groovy目录,一个是存放自定义插件名称的resources目录。这种定义方式也是只能在我们项目中进行使用,不好复用。
  • 独立Module中使用。这种方式就是完全独立开发一个Module,可以随便用,与第二种方式的区别是不直接依赖,采用maven仓库依赖。

单Module方式:

  1. 首先新建一个module,文件结构修改为以下图所示:

android item封装 android 封包_ASM_04

这里要注意划线部分一定要一致。

NetworkPlugin:

package com.example.jzn.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class NetworkPlugin implements Plugin<Project>{
    @Override
    void apply(Project project) {
        println "------network plugin begin-------"
        project.tasks.create("ngTestWork", NetworkTask) {
            doLast {
                println "doLast"
            }
        }
        println "------network plugin end-------"
    }
}

NetworkTask:

package com.example.jzn.plugin

import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class NetworkTask extends DefaultTask {
    @TaskAction
    def testNetwork() {
        println "------NetworkTask begin-------"
        try {
            OkHttpClient mOkHttpClient = new OkHttpClient()
            Request request = new Request.Builder().url("http://www.baidu.com/").build()
            Call call = mOkHttpClient.newCall(request)
            Response mResponse = call.execute()
            if (mResponse.isSuccessful()) {
                println mResponse.body().string()
            }
        } catch (Exception e) {
            e.printStackTrace()
            println e.toString()
        }
        println "------NetworkTask end-------"
    }
}

com.example.jzn.plugin.properties: 

implementation-class=com.example.jzn.plugin.NetworkPlugin

然后这里要注意修改该module下的build.gradle:

apply plugin: 'groovy'
//使用该插件,才能使用uploadArchives
apply plugin: 'maven'
repositories {
    jcenter()
}
dependencies {
    //使用gradle sdk
    compile gradleApi()
    //使用groovy sdk
    compile localGroovy()
    compile 'com.android.tools.build:gradle:3.0.1'
    compile 'com.squareup.okhttp3:okhttp:3.7.0'
    compile 'com.squareup.okio:okio:1.9.0'
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
uploadArchives {
    repositories.mavenDeployer {
        pom.version = '1.0.0'
        pom.artifactId = 'jznPlugin'
        pom.groupId = 'com.example.jzn.plugin'
        //repository(url: "file:///D:/repository/")
        repository(url: "file:/Users/xiaoguagua/Desktop/chajian/")
    }
}

 这里没有用网络maven,用的本地地址,是我在mac桌面创建的一个文件夹。

全部配置好之后,sync一下项目,在gradle任务下会出现这个上传的task:

android item封装 android 封包_android item封装_05

点击运行,成功之后会看到chajian文件夹里已经有了我们生成的组件:

android item封装 android 封包_Gradle_06

这个时候,我们如果要在其他工程中引用,只需要在根目录的build.gradle作如下配置即可:

android item封装 android 封包_Gradle_07

引入依赖,支持组件,然后编译一下,就会在gradle列表的Tasks->other里看到我们自定义的task:

android item封装 android 封包_Android切面编程_08

最后点击运行可以看到控制台输出的效果了。

查看字节码

首先查看class文件:

可以在app-build-intermediates-javac里看到所有的class

查看字节码:ASM Bytecode Viewer插件。

ASM的使用

主要的操作流程:

  • 需要创建一个 ClassReader 对象,将 .class 文件的内容读入到一个字节数组中
  • 然后需要一个 ClassWriter 的对象将操作之后的字节码的字节数组回写
  • 需要事件过滤器 ClassVisitor。在调用 ClassVisitor 的某些方法时会产生一个新的 XXXVisitor 对象,当我们需要修改对应的内容时只要实现自己的 XXXVisitor 并返回就可以了

Transform

在Android打包过程中的class 文件到 .dex 发文件的阶段里,Transform 是 Android 官方提供的在这一阶段用来修改 .class 文件的一套标准 API。这一应用现在主要集中在字节码查找、代码注入等。

每个 Transform 都是一个 gradle task, 将 class 文件、本地依赖的 jar, aar 和 resource 资源统一处理。 每个 Transform 在处理完之后交给下一个 Transform。

如果是用户自定义的 Transform 会插在队列的最前面。

Transform使用方式:

Transform是一个抽象类,我们可以创建一个类继承它,然后在自定义的Plugin中注册:

val android = project.extensions.getByType(AppExtension::class.java)
        android.registerTransform(LogTransform())

这里的AppExtension是啥呢?可以参考Android Gradle学习(五) Extension详解_假装你是大灰狼的专栏


参考资料:

aop编程:Android字节码插桩_Watson的博客

Android编译插桩:Android 编译插桩(二): Gradle Transform - 知乎

自定义gradle插件参考:

在AndroidStudio中自定义Gradle插件 - 简书

Gradle自定义插件_Watson的博客

Transform参考:Android 编译插桩(二): Gradle Transform - 知乎

asm参考:ASM 库的介绍和使用 - 简书

配合ASM实现AOP编程:

测试工程:aop_asm_study