0%

Android项目构建Gradle专栏——Bytex插件开发实战

公众号地址: https://mp.weixin.qq.com/s/sq1AdQA9_DbhJDE2IKuVeQ
项目代码:https://github.com/gongshijier/ByteX


1. ByteX介绍

项目地址: https://github.com/bytedance/ByteX

如果每一个功能都需要引入一个插件,插件的编译处理耗时会线性增加
而且每一个插件的transform可能都会涉及到所有class文件的遍历 时间复杂度为n
可以将所有的插件聚合在一起,在一个transform遍历就完成所有插件需要处理的逻辑 时间复杂度为1
进而达到优化插件编译耗时的目的

可以理解为很多个 plugin,插在一个公共插座上
在公共插件的基础上集成其他feature的插件

2. 基于ByteX开发插件

之前在上一章中,介绍了如何开发一个 gradle plugin
这里我们将基于 ByteX 来进行插件的开发

ByteX可以快速方便开发者开发插件:

  • 配置化能力 Extension: 将build.gradle插件的配置读取到 Extension 中进行插件个性化操作
  • 插件日志输出: 提供日志库,方便开发日志需求
  • 开发逻辑简单,只需要在自定义插件中重写transform完成所需操作即可

其中开发者可以参考 ByteX 提供的 developer API 进行开发:
https://github.com/bytedance/ByteX/blob/master/wiki/ByteX-Developer-API-zh.md

注意TIPS:(防坑)

  • 包名尽量保持一致
  • META-INF文件别漏
  • gradle.properties为groupID 和 artifactName
  • 应用插件时候,先apply宿主再apply自定义插件
  • 分清classpath依赖和implementation依赖的区别
  • 如果不确定插件的执行状态可以通过打日志的方法
  • 插件运行来但是没有得到想要的插桩结果——可能是混淆的问题,关闭混淆
  • ASM字节码操作部分,可以使用 intelliJ idea插件 ASM ByteCode Outline 来查看ASM代码

3. Method-Trace插件

gradle 插件一个常用的用途用来插桩方法进行Trace
这样可以统计函数的运行状态,方便进行性能优化分析
搭配 systrace 工具 和 Perfetto 来使用

在函数出入口调用 Trace.beginSection 和 end 就可采集 Trace 数据事后使用 perfetto进行分析,效果如下

如图是插桩后 systrace 统计的图,perfetto工具查看到的效果

如果不会使用 systrace 可以查看文章https://mp.weixin.qq.com/s/9dexhnWuWIopdhdU_aKkZw

这里避免 代码中手动每个函数调用 Trace.beginSection 采用字节码插桩来在 gradle plugin 中批处理添加插桩代码
下面是method-trace 插件的具体开发过程:

目录架构:

MethodTracePlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.ss.android.ugc.bytex.method_trace

import com.android.build.gradle.AppExtension
import com.ss.android.ugc.bytex.common.CommonPlugin
import com.ss.android.ugc.bytex.common.flow.main.Process
import com.ss.android.ugc.bytex.common.visitor.ClassVisitorChain
import com.ss.android.ugc.bytex.pluginconfig.anno.PluginConfig
import org.gradle.api.Project
import org.objectweb.asm.ClassReader


@PluginConfig("bytex.method-trace")
class MethodTracePlugin : CommonPlugin<MethodTraceExtension, MethodTraceContext>() {
override fun getContext(
project: Project,
android: AppExtension,
extension: MethodTraceExtension
): MethodTraceContext {
return MethodTraceContext(project, android, extension)
}

override fun transform(relativePath: String, chain: ClassVisitorChain): Boolean {
chain.connect(MethodTraceClassVisitor(context, extension))
return super.transform(relativePath, chain)
}

override fun flagForClassReader(process: Process?): Int {
return ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES or ClassReader.EXPAND_FRAMES
}
}

MethodTraceExtension可读取如下build.gradle中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// apply ByteX宿主
apply plugin: 'bytex'
ByteX {
enable pluginEnable
enableInDebug pluginEnableInDebug
logLevel pluginLogLevel
}

apply plugin: 'bytex.method-trace'

MethodTracePlugin {
enable pluginEnable
enableInDebug pluginEnableInDebug
whiteList = ['com/gongshijie']
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.ss.android.ugc.bytex.method_trace;

import com.ss.android.ugc.bytex.common.BaseExtension;

import java.util.ArrayList;
import java.util.List;

public class MethodTraceExtension extends BaseExtension {

private List<String> whiteList = new ArrayList<>();


@Override
public String getName() {
return "MethodTracePlugin";
}

public List<String> getWhiteList() {
return whiteList;
}

public void setWhiteList(List<String> whiteList) {
this.whiteList = whiteList;
}
}

TraceMethodVisitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.ss.android.ugc.bytex.method_trace

import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.commons.AdviceAdapter

class TraceMethodVisitor(private var context: MethodTraceContext,
private var className: String, api: Int, mv: MethodVisitor?,
access: Int, var methodName: String?, desc: String?
) : AdviceAdapter(api, mv, access, methodName, desc) {


override fun onMethodEnter() {
super.onMethodEnter()

context.logger.i("TraceMethodVisitor", "----插桩----className: $className methodName: ${methodName}------")

if (methodName != null) {
mv.visitLdcInsn("$className#$methodName");
mv.visitMethodInsn(INVOKESTATIC, "com/ss/android/ugc/bytex/method_trace_lib/MyTrace", "beginSection", "(Ljava/lang/String;)V", false);
}
}

override fun onMethodExit(opcode: Int) {
super.onMethodExit(opcode)
mv.visitMethodInsn(INVOKESTATIC, "com/ss/android/ugc/bytex/method_trace_lib/MyTrace", "endSection", "()V", false);

}
}

4. Jitpack发布

jitpack是一个代码发布平台,可以方便的将github上的代码发布到jitpack

如何使用 jitpack 发布代码
阅读文档: https://jitpack.io/docs/
发布的步骤:

  • Android library 或 java library publish打包代码到本地maven
  • github 创建一个 release
  • jitpack 查看构件产物即可

代码发布后可以使用如下方式来开源给其他开发者使用

1
2
3
4
5
6
7
8
9
10
11
12
 allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
dependencies {
classpath "com.github.gongshijier.ByteX:method-trace:1.4"
}
dependencies {
implementation "com.github.gongshijier.ByteX:method-trace-lib:1.4"
}

这里以 method-trace 这个库举例
发布代码
build.gradle

1
2
apply plugin: 'com.github.dcendents.android-maven'
group='com.github.gongshijier'

致此,完成了插件从开发到发布的过程 !

这些便是该插件的效果:

该插件可以直接通过下面方式使用:

    1. 添加依赖
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
          allprojects {
      repositories {
      jcenter()
      maven { url "https://jitpack.io" }
      }
      }
      // buildscript 中的依赖
      dependencies {
      classpath "com.github.gongshijier.ByteX:method-trace:1.4"
      }

      dependencies {
      implementation "com.github.gongshijier.ByteX:method-trace-lib:1.4"
      }
    1. 使用插件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // apply ByteX宿主
      apply plugin: 'bytex'
      ByteX {
      enable pluginEnable
      enableInDebug pluginEnableInDebug
      logLevel pluginLogLevel
      }

      apply plugin: 'bytex.method-trace'

      MethodTracePlugin {
      enable pluginEnable
      enableInDebug pluginEnableInDebug
      whiteList = ['com/gongshijie']
      }

微信扫码关注公众号,追踪更多博文