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

目录:

    1. ByteX 介绍
    1. 开发插件
    1. Method-Trace插件
    1. 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() {
    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 whiteList = new ArrayList<>();


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

    public List getWhiteList() {
        return whiteList;
    }

    public void setWhiteList(List 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'

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

这些便是该插件的效果:

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

    1. 添加依赖
    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. 使用插件
// 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']
}

你可能感兴趣的:(Android项目构建Gradle专栏——Bytex插件开发实战)