一、背景
Booster 是一款专门为移动应用设计的易用、轻量级且可扩展的质量优化框架,其目标主要是为了解决随着 APP 复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。 Booster 提供了性能检测、多线程优化、资源索引内联、资源去冗余、资源压缩、系统 Bug 修复等一系列功能模块,可以使得稳定性能够提升 15% ~ 25%,包体积可以减小 1MB ~ 10MB。
github地址:github.com/didi/booste...
Booster是一款设计方面非常广的优化框架,研究Booster之前需要先了解Gradle插件、字节码、ASM。
二、gradle插件开发
开始之前先查看版本兼容性(重要!重要!重要!)
我使用的gradle-plugin版本是4.0.0;gradle版本是6.1.1
developer.android.google.cn/studio/rele...
研究Booster源码首先需要了解如何开发一个gradle插件,下面是快速上手版本
2.1 打开android studio创建新的android工程
2.2 创建moudle
2.3 配置moudle的build.gradle
php
apply plugin: 'maven'
apply plugin: 'org.jetbrains.kotlin.jvm'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation gradleApi()
implementation 'com.android.tools.build:gradle:4.0.0'
implementation 'org.ow2.asm:asm:7.1'
implementation 'org.ow2.asm:asm-commons:7.1'
}
group='com.tt.plugin'
version='1.0.0'
uploadArchives {
repositories {
mavenDeployer {
group(group)
version(version)
//本地的Maven地址设置
repository(url: uri('../maven_repo'))
}
}
}
2.4 实现
在main/resources/META_INF/gradle-plugins文件下创建如下文件
创建插件类
kotlin
class DemoPlugin : Plugin<Project> {
companion object {
private const val TAG = "kotlin-DemoPlugin: "
}
override fun apply(project: Project) {
println(TAG + project.name + ";num = 0")
printDependencies(project)
}
private fun printDependencies(project: Project) {
val dependExtension = project.extensions.create("printDepend", DenpeExtension::class.java)
project.afterEvaluate {
// build.gradle配置执行完成
if (dependExtension.enable?.get() == true) {
val configuration = project.configurations.getByName("debugCompileClasspath")
val allDependencies = configuration.allDependencies
allDependencies.forEach { denp ->
println("${TAG}group = ${denp.group};name = ${denp.name};version = ${denp.version}")
}
}
}
}
}
kotlin
internal interface DenpeExtension {
val enable: Property<Boolean?>?
}
2.5 发布
在2.3中我们已经配置好发布信息,执行uploadArchives命令即可
执行发布命令后,在项目根目录下出现maven-repo,说明成功了
2.6 使用
在项目的build.gradle中声明仓库地址和依赖
typescript
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
maven {
url('./maven_repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.tt.plugin:plugin:1.0.0'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
在app的build.gradle使用插件,到这里你的插件就已经运行起来了
bash
apply plugin: 'com.tt.plugin'
printDepend {
enable = true
}
2.7 debug调试
打开Edit Configurations,创建Remote JVM Debug
修改项目的gradle.properties,增加如下配置,如果出现错误,将原来的值注释
ini
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
-Dorg.gradle.debug=true
断点调试,在代码处打断点,点击debug甲壳虫就可以了,如下
参考文章
如果对gradle不熟悉可以参考以下文章,再进行demo开发
1、入门gradle
2、一文搞懂gradle配置
3、gradle中的DSL、Groovy、Kotlin
4、gradle的生命周期
5、gradle常用命令与参数
6、gradle依赖管理和版本决议
7、gradle构建核心之Task指南
8、gradle插件发布指南
9、gradle plugin插件开发及其断点
三、字节码
字节码文件本质上就是一张表,如下
字节码主要由以下七部分组成:
- 魔数与Class文件版本
- 常量池
- 访问标志
- 类索引、父类索引、接口索引
- 字段表集合
- 方法表集合
- 属性表集合
创建Demo.java类,执行javac命令,得到Demo.class字节码文件
typescript
public class Demo {
public static void main(String args[]){
System.out.println("Hello World.");
}
}
使用文本编辑器打开Demo.class,字段含义如下(最好手动解析加深印象)
yaml
cafe babe 魔数
0000 次版本号
0034 主版本号(jdk1.8)
001d 常量池(16进制1d转换为十进制是29,常量数量是29-1=28)
0a (1)Methodref_info
0006 常量池第6个常量所表示class_info信息 java/lang/Object
000f 常量池第15个常量所表示name_and_type信息 <init>()V
09 (2)Fieldref_info
0010 常量池第16个常量所表示class_info信息 java/lang/System
0011 常量池第17个常量所表示name_and_type信息 out Ljava/io/PrintStream
08 (3)String_info
0012 常量池第18个常量索引 Hello World.
0a (4)Methodref_info
0013 常量池第19个常量所表示class_info信息 java/io/PrintStream
0014 常量池第20个常量所表示name_and_type信息 println (Ljava/lang/String;)V
07 (5)Class_info
0015 常量池第21个常量索引 Demo
07 (6)Class_info
0016 常量池第22个常量索引 java/lang/Object
01 (7)Utf8_info
0006 字符串长度为6个字节
3c696e69743e 字符串的值 <init>
01 (8)Utf8_info
0003 字符串长度为3个字节
282956 ()V
01 (9)Utf8_info
0004 字符串长度为4个字节
436f6465 Code
01 (10)Utf8_info
000f 字符串长度为15个字节
4c696e654e756d6265725461626c65 LineNumberTable
01 (11)Utf8_info
0004 字符串长度为4个字节
6d61696e main
01 (12)Utf8_info
0016 字符串长度为22个字节
285b4c6a6176612f6c616e672f537472696e673b2956 ([Ljava/lang/String;)V
01 (13)Utf8_info
000a 字符串长度为10个字节
536f7572636546696c65 SourceFile
01 (14)Utf8_info
0009 字符串长度为9个字节
44656d6f2e6a617661 Demo.java
0c (15)NameAndType_info
0007 常量池第7个常量所表示的信息 <init>
0008 常量池第8个常量所表示的信息 ()V
07 (16)Class_info
0017 常量池第23个常量所表示的信息 java/lang/System
0c (17)NameAndType_info
0018 常量池第24个常量所表示的信息 out
0019 常量池第25个常量所表示的信息 Ljava/io/PrintStream;
01 (18)Utf8_info
000c 字符串长度为12个字节
48656c6c6f20576f726c642e Hello World.
07 (19)Class_info
001a 常量池第26个常量所表示的信息 java/io/PrintStream
0c (20)NameAndType_info
001b 常量池第27个常量所表示的信息 println
001c 常量池第28个常量所表示的信息 (Ljava/lang/String;)V
01 (21)Utf8_info
0004 字符串长度为4个字节
44656d6f Demo
01 (22)Utf8_info
0010 字符串长度为16个字节
6a6176612f6c616e672f4f626a656374 java/lang/Object
01 (23)Utf8_info
0010 字符串长度为16个字节
6a6176612f6c616e672f53797374656d java/lang/System
01 (24)Utf8_info
0003 字符串长度为3个字节
6f7574 out
01 (25)Utf8_info
0015 字符串长度为21个字节
4c6a6176612f696f2f5072696e7453747265616d3b Ljava/io/PrintStream;
01 (26)Utf8_info
0013 字符串长度为19个字节
6a6176612f696f2f5072696e7453747265616d java/io/PrintStream
01 (27)Utf8_info
0007 字符串长度为7个字节
7072696e746c6e println
01 (28)Utf8_info
0015 字符串长度为21个字节
284c6a6176612f6c616e672f537472696e673b2956 (Ljava/lang/String;)V
0021 访问标志,0001或0020,public 并且允许使用invokespecial字节码指令的新语义
0005 类索引,常量池第5个常量所表示的信息 Demo
0006 父类索引,常量池第6个常量所表示的信息 java/lang/Object
0000 接口索引,没有实现接口,所以是0,所以没有后面的接口内容
0000 字段表数量,没有声明类级或实例级变量,所以没有后面的字段表
0002 方法表方法数量,每个方法都用method_info表示
0001 方法访问标识, ACC_PUBLIC
0007 方法名称索引项,指向常量池第7个常量所表示的信息 <init>
0008 方法描述符索引项,指向常量池第8个常量所表示的信息 ()V
0001 属性计表器
0009 指向常量池第9个常量所表示的信息 Code
0000001d 属性长度29
0001 max_stack,操作数栈深度最大值
0001 max_locals,局部变量所需的存储空间
00000005 字节码指令长度
2a 对应的指令为aload_0
b7 指令为invokespecial
0001 这是invokespecial的参数
b1 对应的指令为return
0000 异常表数据
0001 属性表长度
000a 指向常量池第10个常量所表示的信息 LineNumberTable
00000006 属性长度6个字节
0001 line_number_info长度
0000 start_pc 表示的字节码行号为第 0 行
0001 line_number 表示 Java 源码行号为第 1 行
0009 方法访问标识, public static void
000b 常量池第11个常量所表示的信息 main
000c 常量池第12个常量所表示的信息 ([Ljava/lang/String;)V
0001 属性表长度
0009 指向常量池第9个常量所表示的信息 Code
00000025 属性长度为37
0002 max_stack,操作数栈深度最大值2,说明有2个局部变量
0001 max_locals,局部变量所需的存储空间
00000009 字节码指令长度
b2
0002
1203
b600
04b1
0000 异常表数据
0001 属性表长度
000a 常量池第10个常量所表示的信息 LineNumberTable
0000000a 属性长度10个字节
0002 line_number_info长度
0000 start_pc 表示的字节码行号为第 0 行
0003 line_number 表示 Java 源码行号为第 3 行
0008 start_pc 表示的字节码行号为第 8 行
0004 line_number 表示 Java 源码行号为第 4 行
0001 1个属性
000d 常量池第13个常量所表示的信息 SourceFile
00000002 长度2个字节
000e 常量池第14个常量所表示的信息 Demo.java
使用javap -verbose Demo.class命令自动解析字节码,和我们手动解析的进行对比,是一致的
less
public class Demo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello World.
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Demo
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Demo.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello World.
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Demo
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public Demo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World.
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
字节码指令官网docs.oracle.com/javase/spec...
加载指令load
load类指令是将局部变量表中指定位置的变量加载到操作数栈中,比如 iload_0 将局部变量表中下标(slot)为 0 的 int 型变量加载到操作数栈上。
存储指令store
store 类指令是将操作数栈栈顶的数据存储到局部变量表中,比如 istore_0 将操作数栈顶的元素存储到局部变量表中下标为 0 的位置,这个位置的元素类型为 int,store 指令和 load 指令用法类似
访问 Filed
getFiled:比如 getfield #2 ,会获取常量池中的 #2 字段压入栈顶,同时将 this 出栈
putFiled:设置字段的值
访问方法
invokestatic:用于调用静态方法
invokespecial:用于调用私有实例方法、构造器方法以及使用super关键字调用父类的实例方法等
invokevirtual:用于调用非私有实例方法
invokeinterface:用于调用接口方法
invokedynamic:用于调用动态方法,比如 lambda
栈帧
JVM 是一个基于栈的虚拟机,每个线程都有一个虚拟机栈用来存储栈帧( stack frame ),栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,栈帧随着方法调用而创建,随着方法结束而销毁。 每个栈帧可以简单的认为由三部分组成:
局部变量表的大小在编译期间就已经确定,对应 Code 属性中的 locals 字段 局部变量区一般用来缓存一些临时数据,比如计算的结果。实际上,JVM 会把局部变量区当成一个 数组,里面会依次缓存 this 指针(非静态方法)、参数、局部变量。
实战分析
arduino
public class Demo {
public static int add() {
int a = 1;
int b = 2;
return a + b;
}
}
public class Demo
{
public static int add();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: iconst_1 // 常量1入栈
1: istore_0 // 将栈顶元素出栈并且存到局部变量表slot0处
2: iconst_2 // 常量2入栈
3: istore_1 // 将栈顶元素出栈并且存到局部变量表slot1处
4: iload_0 // 加载局部变量表slot0处的元素到栈顶
5: iload_1 // 加载局部变量表slot1处的元素到栈顶
6: iadd // 将操作数栈内的两个元素出栈,相加后将结果入栈
7: ireturn // 返回栈顶元素,方法结束
LineNumberTable:
line 6: 0
line 7: 2
line 8: 4
}
SourceFile: "Demo.java"
参考文章:
字节码文件结构:
www.cnblogs.com/chanshuyi/p...
字节码增强技术探索:
tech.meituan.com/2019/09/05/...
ASM基础知识:
四、ASM
访问者模式主要用于修改或操作一些数据结构比较稳定的数据,字节码文件的结构是由JVM固定的,所以很适合利用访问者模式对字节码文件进行修改,ASM对字节码操作使用的就是访问者模式,访问者模式代码如下:
typescript
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Wheel implements CarElement {
private final String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Body implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Car implements CarElement {
private final List<CarElement> elements;
public Car() {
elements = new ArrayList<>();
elements.add(new Wheel("front left"));
elements.add(new Wheel("front right"));
elements.add(new Wheel("back left"));
elements.add(new Wheel("back right"));
elements.add(new Body());
elements.add(new Engine());
}
@Override
public void accept(CarElementVisitor visitor) {
for (CarElement element : elements) {
element.accept(visitor);
}
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Moving my body");
}
@Override
public void visit(Car car) {
System.out.println("Starting my car");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
@Override
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Visiting body");
}
@Override
public void visit(Car car) {
System.out.println("Visiting car");
}
@Override
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
Android打包过程:
Transform官网:tools.android.com/tech-docs/n...
Transform详解: www.jianshu.com/p/37a5e0588...
自定义Transform: juejin.cn/post/715984...
根据官网介绍,Transform API允许第三方的plugin在生成dex文件前操作class文件,一次app打包可能会经历多次Transform,Transform将输入进行处理,然后写到指定的目录作为下一个Transform输入源,如下
自定义Transform
封装一套通用的模版代码
kotlin
package com.tt.plugin.base
import com.android.SdkConstants
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.builder.utils.isValidZipEntryName
import com.android.utils.FileUtils
import com.google.common.io.Files
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
abstract class BaseCustomTransform(private val enableLog: Boolean) : Transform() {
//线程池,可提升 80% 的执行速度
// private var waitableExecutor: WaitableExecutor = WaitableExecutor.useGlobalSharedThreadPool()
/**
* 此方法提供给上层进行字节码插桩
*/
abstract fun provideFunction(): ((InputStream, OutputStream) -> Unit)?
/**
* 上层可重写该方法进行文件过滤
*/
open fun classFilter(className: String) = className.endsWith(SdkConstants.DOT_CLASS)
/**
* 默认:获取输入的字节码文件
*/
override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
return TransformManager.CONTENT_CLASS
}
/**
* 默认:检索整个项目的内容
*/
override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
return TransformManager.SCOPE_FULL_PROJECT
}
/**
* 默认开启增量编译
*/
override fun isIncremental(): Boolean {
return true
}
/**
* 对输入的数据做检索操作:
* 1、处理增量编译
* 2、处理并发逻辑
*/
override fun transform(transformInvocation: TransformInvocation) {
super.transform(transformInvocation)
log("Transform start...")
//输入内容
val inputProvider = transformInvocation.inputs
//输出内容
val outputProvider = transformInvocation.outputProvider
// 1. 子类实现字节码插桩操作
val function = provideFunction()
// 2. 不是增量编译,删除所有旧的输出内容
if (!transformInvocation.isIncremental) {
outputProvider.deleteAll()
}
for (input in inputProvider) {
// 3. Jar 包处理
log("Transform jarInputs start.")
for (jarInput in input.jarInputs) {
val inputJar = jarInput.file
val outputJar = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
if (transformInvocation.isIncremental) {
// 3.1. 增量编译中处理 Jar 包逻辑
when (jarInput.status ?: Status.NOTCHANGED) {
Status.NOTCHANGED -> {
// Do nothing.
}
Status.ADDED, Status.CHANGED -> {
// Do transform.
// waitableExecutor.execute {
// doTransformJar(inputJar, outputJar, function)
// }
doTransformJar(inputJar, outputJar, function)
}
Status.REMOVED -> {
// Delete.
FileUtils.delete(outputJar)
}
}
} else {
// 3.2 非增量编译中处理 Jar 包逻辑
// waitableExecutor.execute {
// doTransformJar(inputJar, outputJar, function)
// }
doTransformJar(inputJar, outputJar, function)
}
}
// 4. 文件夹处理
log("Transform dirInput start.")
for (dirInput in input.directoryInputs) {
val inputDir = dirInput.file
val outputDir = outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)
if (transformInvocation.isIncremental) {
// 4.1. 增量编译中处理文件夹逻辑
for ((inputFile, status) in dirInput.changedFiles) {
val outputFile = concatOutputFilePath(outputDir, inputFile)
when (status ?: Status.NOTCHANGED) {
Status.NOTCHANGED -> {
// Do nothing.
}
Status.ADDED, Status.CHANGED -> {
// Do transform.
// waitableExecutor.execute {
// doTransformFile(inputFile, outputFile, function)
// }
doTransformFile(inputFile, outputFile, function)
}
Status.REMOVED -> {
// Delete
FileUtils.delete(outputFile)
}
}
}
} else {
// 4.2. 非增量编译中处理文件夹逻辑
// Traversal fileTree (depthFirstPreOrder).
for (inputFile in FileUtils.getAllFiles(inputDir)) {
// waitableExecutor.execute {
// val outputFile = concatOutputFilePath(outputDir, inputFile)
// if (classFilter(inputFile.name)) {
// doTransformFile(inputFile, outputFile, function)
// } else {
// // Copy.
// Files.createParentDirs(outputFile)
// FileUtils.copyFile(inputFile, outputFile)
// }
// }
val outputFile = concatOutputFilePath(outputDir, inputFile)
if (classFilter(inputFile.name)) {
doTransformFile(inputFile, outputFile, function)
} else {
// Copy.
Files.createParentDirs(outputFile)
FileUtils.copyFile(inputFile, outputFile)
}
}
}
}
}
// waitableExecutor.waitForTasksWithQuickFail<Any>(true)
log("Transform end...")
}
/**
* Do transform Jar.
*/
private fun doTransformJar(inputJar: File, outputJar: File, function: ((InputStream, OutputStream) -> Unit)?) {
// Create parent directories to hold outputJar file.
Files.createParentDirs(outputJar)
// Unzip.
FileInputStream(inputJar).use { fis ->
ZipInputStream(fis).use { zis ->
// Zip.
FileOutputStream(outputJar).use { fos ->
ZipOutputStream(fos).use { zos ->
var entry = zis.nextEntry
while (entry != null && isValidZipEntryName(entry)) {
if (!entry.isDirectory) {
zos.putNextEntry(ZipEntry(entry.name))
if (classFilter(entry.name)) {
// Apply transform function.
applyFunction(zis, zos, function)
} else {
// Copy.
zis.copyTo(zos)
}
}
entry = zis.nextEntry
}
}
}
}
}
}
/**
* Do transform file.
*/
private fun doTransformFile(inputFile: File, outputFile: File, function: ((InputStream, OutputStream) -> Unit)?) {
// Create parent directories to hold outputFile file.
Files.createParentDirs(outputFile)
FileInputStream(inputFile).use { fis ->
FileOutputStream(outputFile).use { fos ->
// Apply transform function.
applyFunction(fis, fos, function)
}
}
}
private fun applyFunction(input: InputStream, output: OutputStream, function: ((InputStream, OutputStream) -> Unit)?) {
try {
if (null != function) {
function.invoke(input, output)
} else {
// Copy
input.copyTo(output)
}
} catch (e: UncheckedIOException) {
throw e.cause!!
}
}
/**
* 创建输出的文件
*/
private fun concatOutputFilePath(outputDir: File, inputFile: File) = File(outputDir, inputFile.name)
/**
* log 打印
*/
private fun log(logStr: String) {
if (enableLog) {
println("$name - $logStr")
}
}
}
通用模版实现类
kotlin
package com.tt.plugin
import com.tt.plugin.base.BaseCustomTransform
import java.io.InputStream
import java.io.OutputStream
class DemoTransform(val enableLog: Boolean): BaseCustomTransform(enableLog) {
/**
* 此方法提供给上层进行字节码插桩
*/
override fun provideFunction() = { inputStream: InputStream, outputStream: OutputStream ->
outputStream.write(inputStream.readBytes())
}
/**
* Returns the unique name of the transform.
*
*
* This is associated with the type of work that the transform does. It does not have to be
* unique per variant.
*/
override fun getName(): String {
return "DemoTransform"
}
}
注册Transform,写在plugin中
kotlin
private fun registerTransform(project: Project) {
val appExtension = project.extensions.getByType(AppExtension::class.java)
appExtension.registerTransform(DemoTransform(true))
}
构建app后,app/build/intermediates/transforms文件下出现DemoTransform文件夹,里面有很多jar包
ASM统计方法耗时
kotlin
package com.tt.plugin
import com.tt.plugin.asm_visitor.CostTimeClassVisitor
import com.tt.plugin.base.BaseCustomTransform
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import java.io.InputStream
import java.io.OutputStream
class DemoTransform(val enableLog: Boolean): BaseCustomTransform(enableLog) {
companion object {
const val TAG = "DemoTransform: "
}
override fun classFilter(className: String): Boolean {
return className.endsWith("Activity.class")
|| className.endsWith("Test.class")
}
/**
* 此方法提供给上层进行字节码插桩
*/
override fun provideFunction() = { inputStream: InputStream, outputStream: OutputStream ->
// 使用input输入流构建ClassReader
val classReader = ClassReader(inputStream)
// 使用ClassReader和flags构建ClassWriter
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES)
// 使用 ClassWriter 构建我们自定义的 ClassVisitor
val visitor = CostTimeClassVisitor(classWriter)
// 最后通过 ClassReader 的 accept 将每一条字节码指令传递给 ClassVisitor
classReader.accept(visitor, ClassReader.EXPAND_FRAMES)
//将修改后的字节码文件转换成字节数组
val byteArray = classWriter.toByteArray()
//最后通过输出流修改文件,这样就实现了字节码的插桩
outputStream.write(byteArray)
}
/**
* Returns the unique name of the transform.
* This is associated with the type of work that the transform does. It does not have to be
* unique per variant.
*/
override fun getName(): String {
return "DemoTransform"
}
}
kotlin
package com.tt.plugin.asm_visitor
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.AdviceAdapter
class CostTimeClassVisitor(val nextVisitor: ClassVisitor) :
ClassVisitor(Opcodes.ASM6, nextVisitor) {
private var className: String? = null
companion object {
val TAG = "CostTimeClassVisitor: "
}
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
className = name
this.cv.visitField(Opcodes.ACC_PRIVATE, "time", "J", null, 0L)
println("${TAG} visit: version = ${version}; access = $access; name = $name; superName = $superName; signature = $signature")
}
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val visitor = super.visitMethod(access, name, descriptor, signature, exceptions)
println("${TAG} visitMethod: access = $access; name = $access; descriptor = $descriptor; signature = $signature")
return object : AdviceAdapter(Opcodes.ASM6, visitor, access, name, descriptor) {
var ishook = false
/**
* 开始扫描
*/
override fun visitCode() {
super.visitCode()
println("${TAG}name = ${name} visitCode; ishook = $ishook")
}
/**
* 访问注解
*/
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
if ("Lcom/tt/pluginappthird/CostTime;" == descriptor) {
ishook = true
}
println("${TAG}name = ${name} visitAnnotation: descriptor = $descriptor; ishook = $ishook")
return super.visitAnnotation(descriptor, visible)
}
/**
* 方法开始
*/
override fun onMethodEnter() {
println("${TAG}name = ${name} onMethodEnter; ishook = $ishook")
if (ishook) {
visitVarInsn(ALOAD, 0)
visitVarInsn(ALOAD, 0)
visitFieldInsn(GETFIELD, className, "time", "J")
visitMethodInsn(
INVOKESTATIC,
"android/os/SystemClock",
"uptimeMillis",
"()J",
false
)
visitInsn(LSUB)
visitFieldInsn(PUTFIELD, className, "time", "J")
}
}
/**
* 方法结束
*/
override fun onMethodExit(opcode: Int) {
println("${TAG}name = ${name} onMethodExit; ishook = $ishook")
if (ishook) {
visitVarInsn(ALOAD, 0)
visitVarInsn(ALOAD, 0)
visitFieldInsn(GETFIELD, className, "time", "J")
visitMethodInsn(
INVOKESTATIC,
"android/os/SystemClock",
"uptimeMillis",
"()J",
false
)
visitInsn(LADD)
visitMethodInsn(
PUTFIELD,
className,
"time",
"J",
false
)
visitLdcInsn("MainActivity")
visitLdcInsn("$name: time = ")
visitVarInsn(ALOAD, 0)
visitFieldInsn(GETFIELD, className, "time", "J")
visitMethodInsn(
INVOKESTATIC,
"java/lang/Long",
"valueOf",
"(J)Ljava/lang/Long;",
false
)
visitMethodInsn(
INVOKESTATIC,
"kotlin/jvm/internal/Intrinsics",
"stringPlus",
"(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;",
false
)
visitMethodInsn(INVOKESTATIC,
## "android/util/Log",
"d",
"(Ljava/lang/String;Ljava/lang/String;)I",
false)
visitInsn(POP)
}
}
/**
* 访问结束
*/
override fun visitEnd() {
super.visitEnd()
println("${TAG}name = ${name} visitEnd; ishook = $ishook")
}
}
}
}
studio安装ASMPlugin插件后,可以将.java文件转换为字节码格式,ASM的代码照着敲就可以了,遇到不清楚的去官网查一下就可以了,如下:
五、Booster
Booster地址:github.com/didi/booste...
从booster-transform-thread分析booster
项目的build.gradlet添加如下代码:
bash
buildscript {
ext.kotlin_version = '1.6.10'
ext.booster_version = '4.14.2'
repositories {
google()
mavenCentral()
maven {
url('./maven_repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.tt.plugin:plugin:1.0.0'
classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version"
classpath "com.didiglobal.booster:booster-transform-thread:$booster_version"
}
}
app的build.gradle添加
arduino
apply plugin: 'com.didiglobal.booster'
测试代码,在下面的代码中打印子线程的名字,我们在创建线程时并没有设置线程名字,运行后看下效果
kotlin
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
}
@CostTime
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val t1 = Thread {
Log.d(TAG, "t1 name = ${Thread.currentThread().name}")
}
t1.start()
}
日志输入结果如下,可以看到线程被赋予了名字
ini
com.tt.pluginappthird D/MainActivity: t1 name = com.tt.pluginappthird.MainActivity
booster是怎么做的呢?从源码可知插件入口是BoosterPlugin,那我们继续看看BoosterPlugin做了什么
kotlin
class BoosterPlugin : Plugin<Project> {
override fun apply(project: Project) {
// android项目
project.extensions.findByName("android") ?: throw GradleException("$project is not an Android project")
// 插件版本小于3.6 添加监听
if (!GTE_V3_6) {
project.gradle.addListener(BoosterTransformTaskExecutionListener(project))
}
// 加载处理器
val processors = loadVariantProcessors(project)
// gradle生命周期初始化、配置、执行,state.executed配置完成(可以拿到依赖哪些三方库)
if (project.state.executed) {
project.setup(processors)
} else {
project.afterEvaluate {
project.setup(processors)
}
}
// 注册transform
project.getAndroid<BaseExtension>().registerTransform(BoosterTransform.newInstance(project))
}
}
kotlin
// 内联函数
@Throws(ServiceConfigurationError::class)
internal fun loadVariantProcessors(project: Project): List<VariantProcessor> {
// 创建ServiceLoader,传参classloder和project的class文件
// 加载
return newServiceLoader<VariantProcessor>(project.buildscript.classLoader, Project::class.java).load(project)
}
internal inline fun <reified T> newServiceLoader(classLoader: ClassLoader, vararg types: Class<*>): ServiceLoader<T> {
// 返回ServiceLoaderFactory,T是VariantProcessor,*types是Project::class.java
return ServiceLoaderFactory(classLoader, T::class.java).newServiceLoader(*types)
}
// 内联类
internal class ServiceLoaderFactory<T>(private val classLoader: ClassLoader, private val service: Class<T>) {
// ServiceLoader的实现类是ServiceLoaderImpl
fun newServiceLoader(vararg types: Class<*>): ServiceLoader<T> = ServiceLoaderImpl(classLoader, service, *types)
}
由以上代码得知newServiceLoader().load(project)最后调用的是ServiceLoaderImpl.load(),然后看看ServiceLoaderImpl.load()做了什么
kotlin
override fun load(vararg args: Any): List<T> {
// args是传递project
// 调用了lookup,看下做了什么
return lookup<T>().map { provider ->
// provider是加载的类文件
try {
try {
// *types是Project::class.java,T是VariantProcessor
// 利用反射创建META-INF/services/${VariantProcessor::class.java.name}下的类对象
provider.getConstructor(*types).newInstance(*args) as T
} catch (e: NoSuchMethodException) {
provider.getDeclaredConstructor().newInstance() as T
}
} catch (e: Throwable) {
throw ServiceConfigurationError("Provider $provider not found")
}
}
}
kotlin
fun <T> lookup(): Set<Class<T>> {
// name = META-INF/services/${service.name}
// sevice是传进来的VariantProcessor的class文件
// 加载META-INF/services/${VariantProcessor::class.java.name}文件
return classLoader.getResources(name)?.asSequence()?.map(::parse)?.flatten()?.toSet()?.mapNotNull { provider ->
try {
// classloader加载文件
val providerClass = Class.forName(provider, false, classLoader)
if (!service.isAssignableFrom(providerClass)) {
throw ServiceConfigurationError("Provider $provider not a subtype")
}
providerClass as Class<T>
} catch (e: Throwable) {
null
}
}?.toSet() ?: emptySet()
}
META-INF/services/${VariantProcessor::class.java.name},这个又是什么?其实这是spi机制,就是将实现类注册到META-INF/services中,然后使用serviceLoader去加载;具体请参考下面的链接 juejin.cn/post/684490...
到这里也知道了loadVariantProcessors(project)其实就是利用@AutoService加载VariantProcessor实现类,booster-transform-thread中的是ThreadVariantProcessor,获取到实现类后调用了setup函数
kotlin
private fun Project.setup(processors: List<VariantProcessor>) {
val android = project.getAndroid<BaseExtension>()
when (android) {
is AppExtension -> android.applicationVariants
is LibraryExtension -> android.libraryVariants
else -> emptyList<BaseVariant>()
}.takeIf<Collection<BaseVariant>>(Collection<BaseVariant>::isNotEmpty)?.let { variants ->
variants.forEach { variant ->
processors.forEach { processor ->
// 执行了每个实现类的process函数
processor.process(variant)
}
}
}
}
kotlin
@AutoService(VariantProcessor::class)
class ThreadVariantProcessor : VariantProcessor {
override fun process(variant: BaseVariant) {
if (variant !is LibraryVariant && !variant.isDynamicFeature) {
// 给项目添加依赖:booster-android-instrument-thread
variant.project.dependencies.add("implementation", "${Build.GROUP}:booster-android-instrument-thread:${Build.VERSION}")
}
}
}
booster-android-instrument-thread项目代码如下,主要是和线程相关,到这里我们的demo项目已经引用了booster-android-instrument-thread
接下来看下booster如何给线程添加名字,在BoosterPlugin的apply函数中注册了BoosterTransform
kotlin
// 注册transform
project.getAndroid<BaseExtension>().registerTransform(BoosterTransform.newInstance(project))
// newInstance函数,在不同版本创建不同的transform
companion object {
fun newInstance(project: Project, name: String = "booster"): BoosterTransform {
// 创建TransformParameter
val parameter = project.newTransformParameter(name)
return when {
GTE_V3_4 -> BoosterTransformV34(parameter)
else -> BoosterTransform(parameter)
}
}
}
// 执行transform函数
final override fun transform(invocation: TransformInvocation) {
BoosterTransformInvocation(invocation, this).apply {
if (isIncremental) {
// 增量编译
doIncrementalTransform()
} else {
// 非增量编译
outputProvider?.deleteAll()
doFullTransform()
}
}
}
internal fun doIncrementalTransform() = doTransform(this::transformIncrementally)
private fun doTransform(block: (ExecutorService, Set<File>) -> Iterable<Future<*>>) {
this.outputs.clear()
this.collectors.clear()
val executor = Executors.newFixedThreadPool(NCPU)
// transform执行之前
this.onPreTransform()
// Look ahead to determine which input need to be transformed even incremental build
val outOfDate = this.lookAhead(executor).onEach {
project.logger.info("✨ ${it.canonicalPath} OUT-OF-DATE ")
}
try {
// 执行transformIncrementally函数
block(executor, outOfDate).forEach {
it.get()
}
} finally {
executor.shutdown()
executor.awaitTermination(1, TimeUnit.HOURS)
}
// transform执行之后
this.onPostTransform()
if (transform.verifyEnabled) {
this.doVerify()
}
}
private fun transformIncrementally(executor: ExecutorService, outOfDate: Set<File>) = this.inputs.map { input ->
input.jarInputs.filter {
it.status != NOTCHANGED || outOfDate.contains(it.file)
}.map { jarInput ->
// 处理jar包
executor.submit {
doIncrementalTransform(jarInput)
}
} + input.directoryInputs.filter {
it.changedFiles.isNotEmpty() || outOfDate.contains(it.file)
}.map { dirInput ->
executor.submit {
// 处理非jar包
doIncrementalTransform(dirInput, dirInput.file.toURI())
}
}
}.flatten()
// 无论是jar包还是非jar处理,最后会执行到这里
private fun ByteArray.transform(): ByteArray {
// 遍历transformers执行transformer.transform
return transformers.fold(this) { bytes, transformer ->
transformer.transform(this@BoosterTransformInvocation, bytes)
}
}
从上面代码可以分析出transform()最后执行的是transformers: List中的transformer,接下来看看transformers: List是什么
kotlin
// 构建TransformParameter
fun Project.newTransformParameter(name: String): TransformParameter {
return TransformParameter(name, buildscript, plugins, properties, lookupTransformers(buildscript.classLoader))
}
// parameter就是在创建BoosterTransform时传进来的TransformParameter
// parameter.transformers就是lookupTransformers(buildscript.classLoader)的返回值
private val transformers: List<Transformer> = transform.parameter.transformers.map {
try {
it.getConstructor(ClassLoader::class.java).newInstance(transform.parameter.buildscript.classLoader)
} catch (e1: Throwable) {
try {
it.getConstructor().newInstance()
} catch (e2: Throwable) {
throw e2.apply {
addSuppressed(e1)
}
}
}
}
// 这套代码和上面的使用Autoservice加载VariantProcessor基本一样,不过在这里加载的是Transformer实现类
@Throws(ServiceConfigurationError::class)
internal fun lookupTransformers(classLoader: ClassLoader): Set<Class<Transformer>> {
return ServiceLoaderImpl(classLoader, Transformer::class.java, ClassLoader::class.java).lookup()
}
到这里其实已经知道BoosterTransform.transform()利用spi技术执行到Transformer实现类,在 AsmTransformer是其对应的实现类,而AsmTransformer加载的是ClassTransformer
css
constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : this(ServiceLoader.load(ClassTransformer::class.java, classLoader).sortedBy {
it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0
}, classLoader)
在booster-transform-thread中ClassTransformer实现类是ThreadTransformer,最后会执行 transform()::ThreadTransformer;代码如下
kotlin
@AutoService(ClassTransformer::class)
class ThreadTransformer : ClassTransformer {
// ...省略代码
// 执行transform
override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
// ...省略代码
klass.methods?.forEach { method ->
method.instructions?.iterator()?.asIterable()?.forEach {
when (it.opcode) {
Opcodes.INVOKEVIRTUAL -> (it as MethodInsnNode).transformInvokeVirtual(context, klass, method)
Opcodes.INVOKESTATIC -> (it as MethodInsnNode).transformInvokeStatic(context, klass, method)
Opcodes.INVOKESPECIAL -> (it as MethodInsnNode).transformInvokeSpecial(context, klass, method)
// 创建线程,传名字
Opcodes.NEW -> (it as TypeInsnNode).transform(context, klass, method)
Opcodes.ARETURN -> if (method.desc == "L$THREAD;") {
method.instructions.insertBefore(it, LdcInsnNode(makeThreadName(klass.className)))
method.instructions.insertBefore(it, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "setThreadName", "(Ljava/lang/Thread;Ljava/lang/String;)Ljava/lang/Thread;", false))
logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;): ${klass.name}.${method.name}${method.desc}")
}
}
}
}
return klass
}
private fun TypeInsnNode.transform(context: TransformContext, klass: ClassNode, method: MethodNode) {
when (this.desc) {
/*-----------*/ HANDLER_THREAD -> this.transformNew(context, klass, method, SHADOW_HANDLER_THREAD)
// new Thread
/*-------------------*/ THREAD -> this.transformNew(context, klass, method, SHADOW_THREAD)
/*-----*/ THREAD_POOL_EXECUTOR -> this.transformNew(context, klass, method, SHADOW_THREAD_POOL_EXECUTOR, true)
SCHEDULED_THREAD_POOL_EXECUTOR -> this.transformNew(context, klass, method, SHADOW_SCHEDULED_THREAD_POOL_EXECUTOR, true)
/*--------------------*/ TIMER -> this.transformNew(context, klass, method, SHADOW_TIMER)
}
}
private fun TypeInsnNode.transformNew(@Suppress("UNUSED_PARAMETER") context: TransformContext, klass: ClassNode, method: MethodNode, type: String, optimizable: Boolean = false) {
this.find {
(it.opcode == Opcodes.INVOKESPECIAL) &&
(it is MethodInsnNode) &&
(this.desc == it.owner && "<init>" == it.name)
}?.isInstanceOf { init: MethodInsnNode ->
// 替换desc
// 例如 android/os/HandlerThread => com/didiglobal/booster/instrument/ShadowHandlerThread
this.desc = type
// 替换构造函数
// 例如 android/os/HandlerThread(Ljava/lang/String;) => com/didiglobal/booster/instrument/ShadowHandlerThread(Ljava/lang/String;Ljava/lang/String;)
val rp = init.desc.lastIndexOf(')')
init.apply {
owner = type
desc = "${desc.substring(0, rp)}Ljava/lang/String;${if (optimizable) "Z" else ""}${desc.substring(rp)}"
}
// 使用ASM进行替换
method.instructions.insertBefore(init, LdcInsnNode(makeThreadName(klass.className)))
if (optimizable) {
method.instructions.insertBefore(init, LdcInsnNode(optimizationEnabled))
}
} ?: throw TransformException("`invokespecial $desc` not found: ${klass.name}.${method.name}${method.desc}")
}
}
ThreadTransformer就是利用将demo项目中使用线程的地方,通过asm替换为booster-android-instrument-thread中封装的线程类,并且加上线程的名字及优化等
这边文章涉及到gradle、字节码、ASM、Booster较多内容,技术细节写的比较宽松!!!