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<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'

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

这些便是该插件的效果:

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

    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']
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容