目录:
- ByteX 介绍
- 开发插件
- Method-Trace插件
- Jitpack发布
公众号: https://mp.weixin.qq.com/s/sq1AdQA9_DbhJDE2IKuVeQ
项目代码:https://github.com/gongshijier/ByteX
gradle专栏地址: https://gongshijier.github.io/categories/Android%E9%A1%B9%E7%9B%AE%E6%9E%84%E5%BB%BAGradle%E4%B8%93%E6%A0%8F/
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
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中配置
// 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']
}
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
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 查看构件产物即可
代码发布后可以使用如下方式来开源给其他开发者使用
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
apply plugin: 'com.github.dcendents.android-maven'
group='com.github.gongshijier'
致此,完成了插件从开发到发布的过程 !
这些便是该插件的效果:
该插件可以直接通过下面方式使用:
- 添加依赖
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"
}
- 使用插件
// 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']
}