04.Gradle三方插件
好用的 Gradle Task
Gradle Task 之获取 xml 中的所有 view
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import groovy.util.slurpersupport.GPathResult
import groovy.util.slurpersupport.NodeChild
import groovy.util.slurpersupport.Node
/**
* 收集App中自定义view
*/
project.afterEvaluate {
println("project.afterEvaluate : ${this.name}, android:${this.android}, applicationVariants:${this.android.applicationVariants}")
this.android.applicationVariants.all { variant ->
def buildType = variant.buildType.name.capitalize()
def mergeResourcesTask = project.tasks.findByName("merge${buildType}Resources")
println("=========project.afterEvaluate find task buildType=${buildType},$mergeResourcesTask: $mergeResourcesTask")
if (mergeResourcesTask != null) {
def resParseTask = project.tasks.create("resParse${buildType}Task", ResParseTask.class)
resParseTask.buildType = buildType
mergeResourcesTask.finalizedBy(resParseTask)
}
}
}
class ResParseTask extends DefaultTask {
String buildType
File viewNameListFile
HashSet<String> viewSet = new HashSet<>()
// 自己根据输出几个添加
List<String> ignoreViewNameList = Arrays.asList("include", "fragment", "merge", "view", "DateTimeView")
ResParseTask() {
setGroup("resParse")
setDescription("收集App中xml中的所有View")
}
@TaskAction
void doTask() {
File distDir = new File(project.buildDir, "tmp_custom_views")
if (!distDir.exists()) {
distDir.mkdirs()
}
viewNameListFile = new File(distDir, "custom_view_final.txt")
if (viewNameListFile.exists()) {
viewNameListFile.delete()
}
viewNameListFile.createNewFile()
viewSet.clear()
viewSet.addAll(ignoreViewNameList)
try {
File resMergeFile = new File(project.buildDir, "/intermediates/incremental/merge${buildType}Resources/merger.xml")
println("------>>>>>> resMergeFile: ${resMergeFile.getAbsolutePath()} === ${resMergeFile.exists()}")
if (!resMergeFile.exists()) {
println("------>>>>>> resMergeFile not exists: ${resMergeFile.getAbsolutePath()} === ${resMergeFile.exists()}")
return
}
XmlSlurper slurper = new XmlSlurper()
GPathResult result = slurper.parse(resMergeFile)
if (result.children() != null) {
println("------>>>>>> resMergeFile parse: ${resMergeFile.getAbsolutePath()}, children=${result.childNodes().size()}")
result.childNodes().forEachRemaining({ o ->
println("------>>>>>> parseNode: ${o.name()}, ${o.getClass().getCanonicalName()} ${o instanceof NodeChild}")
if (o instanceof Node) {
parseNode(o)
}
})
}
} catch (Throwable e) {
e.printStackTrace()
}
}
void parseNode(Node node) {
if (node == null) {
return
}
println("------>>>>>> parseNode, name=${node.name()}, type=${node.attributes.get("type")}, path=${node.attributes.get("path")}")
if (node.name() == "file" && node.attributes.get("type") == "layout") {
String layoutPath = node.attributes.get("path")
println("------>>>>>> parseNode: $layoutPath")
try {
XmlSlurper slurper = new XmlSlurper()
GPathResult result = slurper.parse(layoutPath)
String viewName = result.name()
println("------>>>>>> parseNode viewName=$viewName")
if (viewSet.add(viewName)) {
viewNameListFile.append("${viewName}\n")
}
if (result.children() != null) {
result.childNodes().forEachRemaining({ o ->
if (o instanceof Node) {
parseLayoutNode(o)
}
})
}
} catch (Throwable e) {
e.printStackTrace()
}
} else {
node.childNodes().forEachRemaining({ o ->
if (o instanceof Node) {
parseNode(o)
}
})
}
}
void parseLayoutNode(Node node) {
if (node == null) {
return
}
String viewName = node.name()
println("------>>>>>> parseLayoutNode viewName=$viewName")
if (viewSet.add(viewName)) {
viewNameListFile.append("${viewName}\n")
}
if (node.childNodes().size() <= 0) {
return
}
node.childNodes().forEachRemaining({ o ->
if (o instanceof Node) {
parseLayoutNode(o)
}
})
}
}
在 app/build.gradle 中引用
1
apply from: "$rootDir/config/gradle/view_opt.gradle"
官方插件
JVM Plugins
The Java Library Plugin
https://docs.gradle.org/nightly/userguide/java_library_plugin.html#java_library_plugin
The Groovy Plugin
https://docs.gradle.org/nightly/userguide/groovy_plugin.html
java-gradle-plugin
java-gradle-plugin 介绍
使用 java-gradle-plugin
插件发布 jar 省略写 resource/META-INF
官方文档:
Gradle Plugin Development Plugin
之前的写法:
- 创建
main/resources/META-INF/gradle-plugins
文件夹,并在该文件夹下新建first-plugin.properties
文件 (这里的 first-plugin 就是插件的 id), - 在
first-plugin.properties
文件中声明实现插件的类implementation-class=me.hacket.plugin.FirstPlugin
java-gradle-plugin
插件可用来帮助我们开发 Gradle 插件
- Gradle 6.4 后自动 apply 了 [[04.Gradle三方插件#The Java Library Plugin]]
java-library
插件 - 自动在在
api
configuration 添加了gradleApi()
依赖 - 在
jar
task 执行时,校验 plugin metadata 信息 - 集成了 TestKit,可用来测试 plugin 代码;对于 ` testImplementation ` 自动添加了
gradleTestKit()
依赖;通过GradleRunner
自动生成了 plugin classpath,具体可见 Automatic classpath injection with the Plugin Development Plugin
引入
kotlin 引入:
1
2
3
4
// build.gradle.kts
plugins {
`java-gradle-plugin`
}
groovy 引入:
1
2
3
4
// build.gradle
plugins {
id 'java-gradle-plugin'
}
作用
作用一:可用来替代需要写 resource/META-INF
- groovy
1
2
3
4
5
6
7
8
gradlePlugin {
plugins {
simplePlugin {
id = 'first-plugin'
implementationClass = 'me.hacket.plugin.FirstPlugin'
}
}
}
- kotlin
1
2
3
4
5
6
7
8
gradlePlugin {
plugins {
create("simplePlugin") {
id = "org.gradle.sample.simple-plugin"
implementationClass = "org.gradle.sample.SimplePlugin"
}
}
}
- 之前的写法
创建main/resources/META-INF/gradle-plugins
文件夹,并在该文件夹下新建first-plugin.properties
文件 (这里的 first-plugin 就是插件的 id),在该文件中声明实现插件的类implementation-class=me.hacket.plugin.FirstPlugin
可在 gradlePlugin{}
block 中定义多个插件
作用二:为每个 plugin 生成 Plugin Marker Artifact 信息 Plugin Marker Artifact 用来标记插件库的真实路径,用于 plugins 找到插件库用的
作用三:Publish each plugin to the Gradle Plugin Portal (see Publishing Plugins to Gradle Plugin Portal for details), but only if the Plugin Publishing Plugin has also been applied.
Plugin Marker Artifacts
自从可以通过 plugins{}
DSL 来全局声明插件的 id
和 version
属性,Gradle 需要一种方式来找到该插件的实现 artifact(简单说就是需要一种方法把 plugins 方式声明的和已有的 GAV 映射起来)。
Gradle 会寻找 Plugin Marker Artifact
找到 marker:plugin.id:plugin.id.gradle.plugin:plugin.version
,该 marker 有具体的依赖实现,该 marker 的 publish 可通过自动 java-gradle-plugin
发布
和其他插件的配合
maven-publish
java-gradle-plugin
检测到 maven-publish
插件,会自动配置 MavenPublications:
1
2
3
4
5
6
7
publishing {
publications {
myPublicationName(MavenPublication) {
// Configure the publication here
}
}
}
默认添加以下属性:
groupId
-project.group
artifactId
-project.name
version
-project.version
一般自定义最好。
### Plugin Publish Plugin
Starting from version 1.0.0, the Plugin Publish Plugin always auto-applies the Java Gradle Plugin (java-gradle-plugin
) and the Maven Publish Plugin (maven-publish
).
三方插件
maven 发布相关插件
[[Gradle 和 Maven]]
Java Gradle 插件
Java Gradle 插件的使用
1
2
// java 是Java Gradle插件的plugin id
apply plugin:'java'
使用 Java 插件之后会为当前工程添加默认设置和约定,如源代码的位置、单元测试代码的位置、资源文件的位置等,一般使用默认设置即可。
Java 插件约定的项目结构
Java 插件设置一些默认的设置和约定,下面来看一看 Java 项目的默认工程目录,目录结构基本如下:
1
2
3
4
5
6
7
8
JavaGradle
└─src
├─main
│ ├─java
│ └─resources
└─test
├─java
└─resources
- src/main/java 默认是源代码存放目录
- src/main/resources 是资源文件、配置文件等目录
- src/test 下面的目录当然是与其相对应的单元测试相关文件的存储目录
- main 和 test 是 Java Gradle 插件内置的两个源代码集合,当然除此之外可以自己定义其他源代码集合
1
2
3
4
5
6
7
apply plugin : 'java'
sourceSets{
// 指定新的源代码集合
vip{
// ...
}
}
上述目录结构都是 Java Gradle 插件默认实现的,当然还可以修改具体目录的位置,配置方式如下:
1
2
3
4
5
6
7
8
9
10
11
sourceSets {
// 修改默认目录,下面还是和默认位置一样,如需配置其他目录修改即可
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
SourceSet 源集
SourceSet 是 Java Gradle 插件用来描述和管理源代码及其资源的一个抽象概念,是一个 Java 源代码文件和资源文件的集合。可以通过源代码集合配置源代码文件的位置、设置源代码集合的属性等,源集可以针对不同的业务将源代码分组管理,如 Java Gradle 插件默认提供的 main 和 test 源代码目录,一个用于业务代码,另一个用于单元测试,非常方便。
Java Gradle 插件在 Project 下提供一个 sourceSet
属性以及 sourceSet{}
闭包来访问和配置源集,sourceSet 是一个 SourceSetContainer, 源集的常用属性如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 比如main、test等表示源集的名称
name(String)
// 表示源集编译后的class文件目录
output.classDir(File)
// 表示编译后生成的资源文件目录
output.resourcesDir(File)
// 表示编译后源集所需的classpath
compileClasspath(FileCollection)
// 表示该源集的Java源文件
java(SourceDirectorySet)
// 表示该源集的Java源文件所在目录
java.srcDirs(Set)
// 表示源集的资源文件
resources(SourceDirectorySet)
// 表示该源集的资源文件所在目录
resources.srcDirs(Set)
Java 插件常用 Task
Task 名称(默认源集通用任务) | 类型 | 描述 |
---|---|---|
compileJava | JavaCompile | 表示使用 javac 编译 java 源文件 |
processResources | Copy | 表示把资源文件复制到生成的资源文件目录中 |
classes | Task | 表示组装产生的类和资源文件目录 |
compileTestJava | JavaCompile | 表示使用 javac 编译测试 java 源文件 |
processTestResources | Copy | 表示把资源文件复制到生成的资源文件目录中 |
testClasses | Task | 表示组装产生的测试类和相关资源文件 |
jar | Jar | 表示组装 jar 文件 |
javadoc | Javadoc | 表示使用 javadoc 生成 Java API 文档 |
uploadArchives | Upload | 表示上传包含 Jar 的构建,使用 archives{}闭包进行配置 |
clean | Delete | 表示清理构建生成的目录文件 |
cleanTaskName | Delete | 表示删除指定任务生成的文件,如 cleanJar 是删除 jar 任务生成的文件 |
Task 名称(自定义源集任务,SourceSet 是具体的源集名称) | 类型 | 描述 |
---|---|---|
compileSourceSetJava | JavaCompile | 表示使用 javac 编译指定源集的源代码 |
processSouceSetResources | Copy | 表示把指定源集的资源文件复制到生成文件中的资源目录中 |
sourcesSetClasses | Task | 表示组装给定源集的类和资源文件目录 |
Gradle 插件之蒲公英 pgyer
蒲公英应用上传插件
https://github.com/dodocat/pgyer
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
31
32
33
34
35
36
37
38
apply plugin: 'org.quanqi.pgyer'
// ==================== 蒲公英 ====================
pgyer {
_api_key = "70885395bcdfd5ebdb72a5856c95596c"
uKey = "d670780e218b698082c7dd096078659c"
}
def findReleaseApk() {
String uploadFilePath
File file = file("${rootDir}/build/apk/release")
if (file.exists()) {
file.eachFileMatch(~/.*release.apk/) { it ->
uploadFilePath = it.absolutePath
}
}
return uploadFilePath
}
def findDebugApk() {
String uploadFilePath
File file = file("${rootDir}/build/apk/debug")
if (file.exists()) {
file.eachFileMatch(~/.*debug.apk/) { it ->
uploadFilePath = it.absolutePath
}
}
return uploadFilePath
}
apks {
release {
// sourceFile = file(findReleaseApk())
sourceFile = file("${findDebugApk()}")
println("开始上传apk到蒲公英:" + findDebugApk())
}
}
Gradle 插件 webp 转换
https://zhuanlan.zhihu.com/p/307222777
https://github.com/smallSohoSolo/McImage
Gradle 插件之 walle
什么是 walle?
https://github.com/Meituan-Dianping/walle
Walle 是 Android Signature V2 Scheme 签名下的新一代渠道包打包神器:通过在 Apk 中的 APK Signature Block 区块添加自定义的渠道信息来生成渠道包,从而提高了渠道包生成效率,可以作为单机工具来使用,也可以部署在 HTTP 服务器上来实时处理渠道包 Apk 的升级网络请求。
使用
配置依赖
在位于项目的根目录 build.gradle 文件中添加 Walle Gradle 插件的依赖, 如下:
1
2
3
4
5
6
root build.gradle
buildscript {
dependencies {
classpath 'com.meituan.android.walle:plugin:1.1.7'
}
}
引入 walle 插件
1
2
3
4
5
6
// app build.gradle
apply plugin: 'walle'
dependencies {
compile 'com.meituan.android.walle:library:1.1.7'
}
配置 walle 插件
1
2
3
4
5
6
7
8
walle {
// 指定渠道包的输出路径
apkOutputFolder = new File("${project.buildDir}/outputs/channels");
// 定制渠道包的APK的文件名称
apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
// 渠道配置文件
channelFile = new File("${project.getProjectDir()}/channel")
}
apkOutputFolder
指定渠道包的输出路径, 默认值为new File("${project.buildDir}/outputs/apk")
apkFileNameFormat
:定制渠道包的 APK 的文件名称, 默认值为 ‘appName-{buildType}-${channel}.apk
‘
可使用以下变量:
1
2
3
4
5
6
7
8
9
10
projectName - 项目名字
appName - App模块名字
packageName - applicationId (App包名packageName)
buildType - buildType (release/debug等)
channel - channel名称 (对应渠道打包中的渠道名字)
versionName - versionName (显示用的版本号)
versionCode - versionCode (内部版本号)
buildTime - buildTime (编译构建日期时间)
fileSHA1 - fileSHA1 (最终APK文件的SHA1哈希值)
flavorName - 编译构建 productFlavors 名
- channelFile :包含渠道配置信息的文件路径。具体内容格式详见:github 渠道配置文件示例,支持使用#号添加注释
如何获取渠道信息
1
String channel = WalleChannelReader.getChannel(this.getApplicationContext());
如何生成渠道包
生成渠道包的方式是和 assemble${variantName}Channels 指令结合,渠道包的生成目录默认存放在 build/outputs/apk/,也可以通过 walle 闭包中的 apkOutputFolder
参数来指定输出目录
1
2
3
4
// 生成渠道包
./gradlew clean assembleReleaseChannels
// 支持 productFlavors
./gradlew clean assembleMeituanReleaseChannels
walle 原理
https://tech.meituan.com/2017/01/13/android-apk-v2-signature-scheme.html
开源 Gradle 插件
TinyPngPlugin
https://github.com/waynell/TinyPngPlugin
TinyPng gradle plugin for android
ApiInspect
https://github.com/Sunzxyong/ApiInspect
An api compatibility inspect gradle plugin.(一个 Api 兼容性检测的 Gradle 插件)
ScratchPaper
https://github.com/2BAB/ScratchPaper
ScatchPaper 可以在你的 App icon 上加一个蒙层用以区分出各个 BuildType 的 App,并且承载了版本信息等附加文字。
- 支持 常规 和 圆形 的图标
- 支持 adaptive-icon
- 支持 AAPT2
类似项目:
https://github.com/akonior/icon-version
AabResGuard
https://github.com/bytedance/AabResGuard
About
The tool of obfuscated aab resources.(Android app bundle 资源混淆工具)
McImage
Android Gradle Plugin – Auto Check big image and compress image in building.
https://github.com/smallSohoSolo/McImage
android-snapshot-publisher
https://github.com/xmartlabs/android-snapshot-publisher
Gradle plugin to deploy Android Snapshot Versions
AndroidLocalizePlugin
https://github.com/Airsaid/AndroidLocalizePlugin
Android/IDEA 本地化插件。 支持多种语言,无需申请 KEY。
1
2
3
4
支持 104 种语言。
无需申请 KEY。
一键生成所有翻译文件。
支持对指定的文本不参与翻译。