网易首页 > 网易号 > 正文 申请入驻

从精准化测试看ASM在Android中的强势插入-Gradle插件

0
分享至

点击上方蓝字关注我,知识会给你力量

Gradle Plugin是我们在编译期修改代码的重要武器,也是我们精准化测试的核心组成部分。

官网镇楼:

https://docs.gradle.org/current/userguide/custom_plugins.html

https://developer.android.com/studio/build?hl=zh-cn#new_configurations

Gradle Plugin有三种存在形式:

  • 在构建脚本中:直接写在项目当前的build.gradle中

  • buildSrc:项目根目录下的buildSrc文件夹,是一个系统保留目录,可以直接运行插件代码而不用引用插件包

  • 独立项目:类似module,单独编译成jar使用

创建

Gradle中自带了创建模板项目的方法——gradle init,通过这个指令,可以引导我们创建一个完整的插件项目。

在相应的目录下执行该指令,如下所示。

➜ qdplugin /Users/xuyisheng/Downloads/gradle-6.5/bin/gradle init

Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 4

Select implementation language:
1: Groovy
2: Java
3: Kotlin
Enter selection (default: Java) [1..3] 1

Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Project name (default: qdplugin):
Source package (default: qdplugin):

> Task :init
Get more help with your project: https://guides.gradle.org?q=Plugin%20Development

BUILD SUCCESSFUL in 12s
2 actionable tasks: 2 executed

Gradle会自动帮我们创建好所需要的文件。

新版本的Gradle插件结构已经和之前的不太一样了,新版本的Gradle插件不再需要resources目录来申明插件的入口meta-info文件,而是直接写在了build.gradle里面,类似这样。

gradlePlugin {
// Define the plugin
plugins {
coverage {
id = 'com.yw.coverage'
implementationClass = 'com.yw.coverage.Coverage_pluginPlugin'

为了避免在编译时遇到一些奇奇怪怪的问题,这里建议大家增加指定Java8编译的指令。

sourceCompatibility = 1.8
targetCompatibility = 1.8
发布

Gradle Plugin的前两种使用方式,都不用发布插件,可以直接使用,但大部分情况下,一般先在项目根目录下创建buildSrc目录,再通过gradle init生成插件需要的文件,这样开发完后,再迁移到单独项目。

在buildSrc中,不用每次publish到App,可以直接参与编译,调试比较方便,但是等插件稳定后,通过独立的插件项目,可以让插件的集成和管理更加方便。

一般来说,我们会使用本地Maven库来调试插件,借助Gradle的maven-publish插件,我们可以和方便的发布插件到本地Maven库。

首先,引入插件:

plugins {
id 'java-gradle-plugin'
id 'java'
id 'maven-publish'
id 'groovy'
id 'maven'

❝ 使用MavenLocal,编译后publish的插件位于:/Users/用户名/.m2/repository目录下。 ❞

继续创建发布脚本,代码如下:

publishing {
publications {
coverage(MavenPublication) {
groupId = 'com.yw.coverage'
artifactId = 'coverage'
version = '0.0.1'
from components.java

其中:

  • coverage:是task name可以随意指定

  • groupId、artifactId和version:这3个东西组成了引用的id,在根目录的build.gradle中使用。

独立的插件项目,需要执行publish task,在Gradle标签卡中找到publishCoveragePublicationToMavenLocal这样一个Task,发布插件到MavenLocal,编译成功即可使用。

使用

在使用插件的项目根目录Gradle文件中,指定访问mavenLocal,同时,使用groupId、artifactId和version组成对插件的引用,如下所示。

buildscript {
ext.kotlin_version = "1.4.21"
repositories {
google()
mavenCentral()
mavenLocal()
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.yw.coverage:coverage:0.0.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

在主module中,引用插件,代码如下。

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.yw.coverage'

这里的id,就是插件的plugin配置中定义的id。

gradlePlugin {
// Define the plugin
plugins {
coverage {
id = 'com.yw.coverage'
implementationClass = 'com.yw.coverage.Coverage_pluginPlugin'

sync项目后,在task中就可以找到新建的task,这个task是在代码中注册的(默认生成的代码)。

public class Coverage_pluginPlugin implements Plugin {
public void apply(Project project) {
// Register a task
project.tasks.register("coverage") {
doLast {
println("Hello from plugin 'com.yw.coverage'")


其实很简单就创建了一个能用的Gradle插件,插件的入口就是implementationClass中申明的类,implements Plugin 并实现apply方法即可。

兼容

Gradle虽然好用,但是API的变化非常频繁,而且兼容性做的不是很好,所以大家经常在网上搜到的一些脚本,可能在你的环境下就无法执行,所以,通过官方文档查看最新的使用手册,才是最稳的方式。

Transform

Transform才是Gradle Plugin的核心。

Transform是Gradle Plugin提供的在编译过程中对class做dex打包之前的一个处理流水线。官方有很多任务,也是基于Transform实现的,自定义Gradle Plugin,配合Transform做代码的修改,是对编译过程进行干预的一般方法。

一个最简单的Transform如下所示。

package com.yw.coverage

import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager
import org.gradle.api.Project

public class CoverageInjectTransform extends Transform {
private Project project

CoverageInjectTransform(Project project) {
this.project = project
}

// 申明Transform的Name,也就是Transform的TaskName,例如当返回值为CoverageInjectTransform时,
// Sync后可以看到名为transformClassesWith[Name]ForDebug的task
@Override
String getName() {
return "Coverage"
}

// 指定Transform处理的输入类型,通常针对Class类型
@Override
Set getInputTypes() {
return TransformManager.CONTENT_CLASS
}

// 指定Transform输入文件所属的范围,通常指定为全部工程
@Override
Set getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}

// 是否支持增量编译
@Override
boolean isIncremental() {
return true
}

// 具体的执行逻辑
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
println("------------------------")
}
}

但是要注意的是,上面的Transform实际上是无法执行的,因为前面我们说了,Transform是一个处理流水线,每个Transform都是一个Gradle Task,编译器中的TaskManager将每个Transform串联起来,Transform会拿到上一个Transform编译后的class文件,以及jar和aar资源、和asset目录下的资源文件作为自己的输入,同时做好处理后,也需要将这些内容作为自己的输出内容输出给下一个Transform,直到所有的Transform执行完毕。

❝ 用户自定义的Transform,会比系统的Transform先执行 ❞

Transform有两种,即「消费型Transform」和「引用型Transform」。


  • 消费型Transform:这种Transform需要将每个jar、aar和class中间产物复制到Transform dest目录。这个目录实际上就是下一个Transform的输入目录。在复制中间产物的过程中,就是我们对产物进行修改的时机。我们可以对jar、aar、class文件的字节码做一些修改,再交给下一个Transform



  • 引用型Transform:当前Transform可以读取这些输入,但是不能修改,也不需要输出中间产物给下一个Transform


所以说,消费型Transform必须将输入的中间产物输出到下一个Transform,否则就无法继续编译了。

在Transform中有几个重要的概念:


  • TransformInput:所谓Transform就是对输入的class文件转变成目标字节码文件,TransformInput就是这些输入文件的抽象。目前它包括两部分:DirectoryInput集合与JarInput集合。



  • DirectoryInput:它代表着以源码方式参与项目编译的所有目录结构及其目录下的源码文件,可以借助于它来修改输出文件的目录结构、目标字节码文件。



  • JarInput:它代表着以jar包方式参与项目编译的所有本地jar包或远程jar包,可以借助于它来实现动态添加jar包操作。



  • TransformOutputProvider:它代表的是Transform的输出,例如可以通过它来获取输出路径。


对于TransformInput来说,Gradle通过下面两个维度来控制输入的文件。

  • Scope:过滤的是输入文件的来源

  • ContentType:过滤的是输入文件类型

Scope的取值有下面这些:

EXTERNAL_LIBRARIES:只有外部库
PROJECT:只有项目内容
PROJECT_LOCAL_DEPS:只有项目的本地依赖(本地jar)
PROVIDED_ONLY:只提供本地或远程依赖项
SUB_PROJECTS:只有子项目
SUB_PROJECTS_LOCAL_DEPS:只有子项目的本地依赖项(本地jar)
TESTED_CODE:由当前变量(包括依赖项)测试的代码

ContentType的取值有下面这些:

CONTENT_CLASS:class类型
CONTENT_JARS:jar
CONTENT_RESOURCES:asset
CONTENT_NATIVE_LIBS:native

任何消费型的Transform,都可以通过Gradle的API来获取输出目录,将中间产物Copy到输出目录:

// class
def outputDirFile = transformInvocation.outputProvider.getContentLocation(
directoryInput.name, directoryInput.contentTypes, directoryInput.scopes,
Format.DIRECTORY
FileUtils.copyDirectory(directoryInput.getFile(), outputDirFile);

// jar
def outputFile = transformInvocation.outputProvider.getContentLocation(
jarInput.name, jarInput.contentTypes, jarInput.scopes,
Format.JAR
)
FileUtils.copyFile(jarInput.getFile(), outputFile);

所有自定义的Transform中间产物,都会在build/intermediates/transforms下找到(Kotlin文件在build/tmp/kotlin-classes目录下),你可以查看这些中间产物是否符合了自己的预期。

注册

Transform需要在Plugin中进行注册才能生效,注册的方式有两种,如下所示。

//注册方式1
AppExtension appExtension = project.extensions.getByType(AppExtension) appExtension.registerTransform(new MethodTimeTransform())
//注册方式2
//project.android.registerTransform(new MethodTimeTransform())
Kotlin化

Gradle插件经历了Java、Grovvy的版本变迁,迎来了全面Kotlin化的新浪潮,新版本的官方Gradle插件,都已经全部使用Kotlin来编写,借助Kotlin,我们可以很方便的统一代码编写环境,借助不输于Grovvy的语法糖,可以很方便的来写Gradle Plugin。

在Gradle中使用Gradle需要对原有脚本做一些改造,首先,要将build.gradle脚本改为buld.gradle.kts,然后将Kotlin代码放到src/man/kotlin目录下,最后,脚本中的代码也要做相应的更新,kts脚本如下所示。

plugins {
`java-gradle-plugin`
id("org.jetbrains.kotlin.jvm") version "1.3.72"

repositories {
mavenCentral()
google()
jcenter()
}

dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("com.android.tools.build:gradle:4.1.1")
implementation("org.ow2.asm:asm:9.1")
}

gradlePlugin {
val greeting by plugins.creating {
id = "asmtest"
implementationClass = "com.yw.asm.MyPlugin"
}
}

java.sourceCompatibility = JavaVersion.VERSION_1_8
java.targetCompatibility = JavaVersion.VERSION_1_8

更简单一点,通过gradle init生成Kotlin版本的插件默认代码,Copy过去即可。

Gradle插件是我们后续做字节码修改的基础,一定要熟练掌握插件的开发和调试,这样才能避免后续在开发字节码插件的时候遇到各种插件问题而不能专心于字节码开发。

向大家推荐下我的网站 https://xuyisheng.top/ 点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
折叠屏 iPhone Ultra 保护壳遭到提前泄露!

折叠屏 iPhone Ultra 保护壳遭到提前泄露!

XCiOS俱乐部
2026-05-27 20:17:48
图赫尔疯了!35 岁老将挤掉天才进世界杯,基恩 5 年前的话应验了

图赫尔疯了!35 岁老将挤掉天才进世界杯,基恩 5 年前的话应验了

奶盖熊本熊
2026-05-23 03:30:37
新华社权威快报丨全国“三夏”大规模小麦机收全面展开

新华社权威快报丨全国“三夏”大规模小麦机收全面展开

新华社
2026-05-28 18:03:26
请把裤子穿上!娄艺潇《新龙门客栈》疯狂擦边,露胸露大白腿博眼球

请把裤子穿上!娄艺潇《新龙门客栈》疯狂擦边,露胸露大白腿博眼球

八卦王者
2026-05-26 14:52:04
悲催!浙江一女子出轨,丈夫直言婚姻本就是一场赌注,放手去爱吧

悲催!浙江一女子出轨,丈夫直言婚姻本就是一场赌注,放手去爱吧

火山詩话
2026-04-25 16:19:12
成都小米车主遇剐蹭维权遭三次殴打致骨折!小米法务称维权到底

成都小米车主遇剐蹭维权遭三次殴打致骨折!小米法务称维权到底

听心堂
2026-05-28 13:41:05
CBA总决赛大反转,上海队麻烦了

CBA总决赛大反转,上海队麻烦了

宗介说体育
2026-05-28 16:41:19
跟你借这两样东西的人,都是来害你的,会毁掉你所有的福报

跟你借这两样东西的人,都是来害你的,会毁掉你所有的福报

心理观察局
2026-05-23 07:11:05
父亲为小三抛妻弃女15年,我毕业上门要说法,推门见小三愣住

父亲为小三抛妻弃女15年,我毕业上门要说法,推门见小三愣住

墨染尘香
2026-05-25 16:17:29
大胆豪赌!皇马甩卖头号王牌!穆里尼奥豪掷 1.5 亿锁定接班人

大胆豪赌!皇马甩卖头号王牌!穆里尼奥豪掷 1.5 亿锁定接班人

奶盖熊本熊
2026-05-28 04:37:29
赖清德称台湾是“国家”,蒋万安:我是台湾人,严厉谴责大陆军演

赖清德称台湾是“国家”,蒋万安:我是台湾人,严厉谴责大陆军演

锅锅爱历史
2026-05-23 15:45:36
麦克马纳曼:穆里尼奥不适合回皇马

麦克马纳曼:穆里尼奥不适合回皇马

懂球帝
2026-05-29 03:40:09
文章新恋情实锤,相恋三年的女友身份曝光,疑似两人已经结婚生子

文章新恋情实锤,相恋三年的女友身份曝光,疑似两人已经结婚生子

花哥扒娱乐
2026-05-28 19:01:51
我飞北京动手术,想在亲姐姐家借住3天被拒,我二话不说,当晚就停了帮她还了2年的房贷

我飞北京动手术,想在亲姐姐家借住3天被拒,我二话不说,当晚就停了帮她还了2年的房贷

感觉会火
2026-04-16 18:21:45
天后带着厨子天王去双修了

天后带着厨子天王去双修了

毒舌扒姨太
2026-05-28 22:43:40
正当防卫被判死刑,枪决前6分钟被最高法紧急叫停,董伟案始末

正当防卫被判死刑,枪决前6分钟被最高法紧急叫停,董伟案始末

易玄
2026-05-25 01:45:09
暴涨700%!全球爆单!浙江有老板忙疯,一天卖了500多份

暴涨700%!全球爆单!浙江有老板忙疯,一天卖了500多份

台州交通广播
2026-05-28 08:55:33
退出对阵广厦?卢伟发文:洛夫顿进行检查治疗,另1悍将也将治疗

退出对阵广厦?卢伟发文:洛夫顿进行检查治疗,另1悍将也将治疗

阿纂看事
2026-05-28 12:53:55
第一集就全裸出镜,女神新剧冲到No.1了

第一集就全裸出镜,女神新剧冲到No.1了

来看美剧
2026-05-28 15:16:24
让女人长期保持年轻貌美,魅力十足的6个方法

让女人长期保持年轻貌美,魅力十足的6个方法

运动健身号
2026-03-31 09:20:09
2026-05-29 05:00:49
Android群英传
Android群英传
Android群英传
455文章数 921关注度
往期回顾 全部

科技要闻

利润跌27%:快手只剩“可灵”这张牌?

头条要闻

男子疑遭家暴跳楼身亡 母亲:儿媳说"你不配活在世上"

头条要闻

男子疑遭家暴跳楼身亡 母亲:儿媳说"你不配活在世上"

体育要闻

唐斯经历的一切,此刻的他与尼克斯

娱乐要闻

林俊杰七七与大哥嫂子的瓜剪不断理还乱

财经要闻

小米仍需一次创业

汽车要闻

从智驾兜底到自研4nm芯片,再到迪迪虾,比亚迪智能化战略凭什么封神?

态度原创

家居
亲子
房产
健康
军事航空

家居要闻

蜂鸟餐椅 线面交错

亲子要闻

《灸童说:中医药成语故事》悬壶济世

房产要闻

突发重磅!三亚新机场公司正式成立!

专家教你辨认“正规外泌体”!

军事要闻

美锁定伊朗打击新目标 考虑重启军事行动

无障碍浏览 进入关怀版