1. 前言
项目中遇到了一个问题 :
其中一个模块MyLibrary的assets文件夹中,需要存放很多文件(每个文件对应一个功能)。
这样导致的问题是MyLibrary打出的这个aar包体积特别大。
如果把MyLibrary严谨地拆解成若干个Module又比较费时,对于现在业务现状来说也显得没那么必要。
那么能不能在上传MyLibrary这个aar的时候,自动复制相应的文件到assets目录下,打出不同功能的aar呢 ?
这就需要自己开发一个Gradle插件来完成这个功能了。
本文环境
- Android Studio 版本 : Android Studio Hedgehog | 2023.1.1
- Gradle版本 : gradle-8.2
- AGP版本 : 8.2.0
- 项目结构 : 项目有app模块和MyLibrary模块,使用build.gradle (Groovy语言),app的assets目录下,有test1.so、test2.so、test3.so这三个文件
2. 配置上传Maven仓库
首先我们把MyLibrary配置上传Maven的插件,也就是maven-publish。
对于这部分功能不了解的同学可以先看我的这篇博客 : Android Module上传到Maven仓库 及 实现同时上传到多个Maven仓库
下面我们简单讲述一下
复制maven_upload.gradle到项目根目录下
apply plugin: 'maven-publish'
//TODO 这里填你Maven仓库的地址
def RELEASE_REPOSITORY_URL = "https://devops-maven.xxxxx.com/repository/yyyyy/"
//TODO 这里填你Maven仓库的账号
def NEXUS_USERNAME = "*********"
//TODO 这里填你Maven仓库的密码
def NEXUS_PASSWORD = "*********"
afterEvaluate {
publishing {
repositories {
maven {
name("ReleaseMaven")
url = RELEASE_REPOSITORY_URL
credentials {
username = NEXUS_USERNAME
password = NEXUS_PASSWORD
}
}
}
publications {
Production(MavenPublication) {
from components.release
groupId = rootProject.ext.GROUP
artifactId = rootProject.ext.POM_ARTIFACT_ID
version = rootProject.ext.VERSION_NAME
}
}
}
}
在MyLibrary中依赖
rootProject.ext.GROUP = "com.heiko.mytest"
rootProject.ext.POM_ARTIFACT_ID = "mylibrary"
rootProject.ext.VERSION_NAME = "1.0.0"
apply from: "${
project.rootDir}/maven_upload.gradle"
Sync一下,可以看到gradle中多了publishing这个文件夹,里面的publishProductionPublicationToReleaseMavenRepository就是用来将MyLibrary打包并上传到Maven仓库的Gradle命令了。

3. Gradle相关操作
3.1 复制文件
from是原目录,into是目标目录,include可以指定需要复制的文件,onlyIf可以用来判断是否执行复制任务。
task copyFiles(type: Copy) {
from 'src/main/assets'
into 'build/outputs/assets'
//include 'test1.txt','test2.txt' //指定文件名
include '**/*.txt' //根据*匹配符合要求的文件
onlyIf {
true
}
}
3.2 删除文件
task myDeleteFilesInDir(type: Delete) {
//delete 'src/main/assets/test1.txt' //删除test1.txt
//delete 'src/main/assets/test1.txt' //删除文件夹下所有的文件,assets文件也会被删除
delete fileTree('src/main/assets') //删除文件夹下所有的文件,assets文件不会被删除
}
3.3 dependsOn
dependsOn表示一个任务需要另一个任务先完成,可以理解为依赖于或需要先做,这意味着在执行这个任务之前,它所依赖的任务必须首先执行。
例如,如果你有一个任务叫做compile,它需要在clean任务之后执行,就可以像这样声明依赖关系:
task clearTask {
doLast {
println("执行 clear.doLast")
}
}
task compileTask {
doLast {
println("执行 compile.dolast")
}
}
compileTask.dependsOn(clearTask)
这样,每当你运行compile任务时,Gradle会首先运行clean任务。

执行compile任务的日志如下
> Task :app:clearTask
执行 clear.doLast> Task :app:compileTask
执行 compile.dolastBUILD SUCCESSFUL in 510ms
更通俗的理解 :
dependsOn就像是做饭的顺序:你首先需要准备食材,然后才能开始烹饪。同样地,如果你有一个任务依赖于另一个任务,那么你需要在开始当前任务之前先完成那个依赖任务。如果没有这种依赖关系,那么任务可能会在错误的时机执行,导致结果不正确或者出现错误。
//准备食材任务
task prepareFood(){
}
//做饭任务
task cooking(){
}
//做饭任务 依赖于 准备食材任务
cooking.dependsOn(prepareFood)
3.4 finalizedBy
finalizedBy用于指定一个任务在另一个任务完成之后执行,可以理解为在完成后执行。
在Gradle中,finalizedBy用于指定一个或多个任务,这些任务将在关联任务执行完毕后执行,无论关联任务是否成功。可以把这个理解为一种清理或收尾的工作。
举个例子,我们有一个任务A,它被finalizedBy任务B,那就意味着在任务A执行完之后,无论任务A是否成功,任务B都会被执行。
这就好比一个厨师在做完一道菜(任务A)之后,无论这道菜是否做得成功,他都需要清理厨房(任务B),那么清理厨房这个步骤就是做菜这个任务的finalizedBy。

3.5 mustRunAfter和shouldRunAfter
在Gradle中,mustRunAfter 是用来定义任务执行的顺序的。如果你有两个任务,比如说任务A和任务B,你希望无论何时,只要这两个任务都被执行,任务A都必须在任务B之后执行,那么你就可以在任务A上调用 mustRunAfter 方法并传入任务B。
举个例子,如下代码:
taskA.mustRunAfter taskB
这段代码的意思就是,如果这两个任务都在执行队列中,那么无论何时,任务A都必须在任务B之后执行。
需要注意的是,mustRunAfter 不会强制执行任务B。如果任务B没有被加入到执行队列中,那么任务A也可以独立执行。同样,如果任务A没有被加入到执行队列中,那么任务B也可以独立执行。
这与 dependsOn 不同。dependsOn 会创建一个强制的依赖关系,也就是说,任务A依赖于任务B,那么只要任务A需要执行,任务B就必须要先执行。而 mustRunAfter 只是定义了一个任务的执行顺序,并不会创建一个依赖关系。
shouldRunAfter和mustRunAfter是一样的,只不过mustRunAfter如果导致了循环依赖,Gradle 会抛出一个错误,并导致构建失败。而shouldRunAfter不会抛出异常,Gradle会忽略这个顺序要求,不会导致构建失败。
4. 新建插件
4.1 创建插件类
在MyLibrary的build.gradle中,增加如下代码,创建插件类 : MyPlugin
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "---- MyPlugin.apply ----"
project.afterEvaluate {
//在Gradle项目配置阶段完成后会被调用
//后续代码放在这里...
}
}
}
4.2 应用这个插件
在MyLibrary的build.gradle中添加这行代码,表示应用这个插件
apply plugin : MyPlugin
在Sync一下,可以看到会打印出日志,表明我们配置这个插件成功了
---- MyPlugin.apply ----
5. 实现初步的依赖
5.1 在app的assets中添加文件
在app的assets目录下,添加test1.so、test2.so、test3.so这三个文件
5.2 MyLibrary新建assets目录
在MyLibrary下新建assets文件夹
5.3 在MyPlugin中实现复制文件的Task
注意 : 后面的这些操作,代码都放在MyPlugin的apply方法的project.afterEvaluate {}里
def myCopyFiles = project.task("myPublishCopyTask", type: Copy) {
from project.fileTree('../app/src/main/assets')
include 'test1.so', 'test2.so'
into 'src/main/assets'
}
myCopyFiles.group = "publishingV2"
Sync一下项目,可以在Android Studio的Gradle Tab中,看到publishingV2文件夹,里面有myPublishCopyTask这个Task。
我们点击这个Task,可以发现,app的assets目录下的test1.so和test2.so已经被复制到MyLibrary的assets目录下了

5.4 在MyPlugin中实现删除文件的Task
//原本task名叫做myPublishDeleteTask,但是后面关联了其他Task后,这个task的名称起做publishAutoCopyTask是比较合适的
def myDeleteFilesInDir = project.task("publishAutoCopyTask", type: Delete) {
delete project.fileTree('src/main/assets')
}
myDeleteFilesInDir.group = "publishingV2"
Sync一下项目,可以在Android Studio的Gradle Tab中,看到publishingV2文件夹,里面有publishAutoCopy这个Task。
我们点击publishAutoCopy这个Task,可以发现,MyLibrary的assets目录下的test1.so和test2.so已经被删除了
5.5 关联Task
这里,我们调用packageReleaseAssets.mustRunAfter(myCopyFiles),表明调用packageReleaseAssets之前,必定会调用myCopyFiles,否则就会抛出异常。
然后调用myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask),表示运行myDeleteFilesInDir的时候,会先去执行myCopyFiles和publishTask。
def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
def publishTask = "publishProductionPublicationToReleaseMavenRepository"
packageReleaseAssets.mustRunAfter(myCopyFiles)
myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
5.6 来看一下整体的代码
这部分代码都在MyLibrary的build.gradle中
rootProject.ext.GROUP = "com.heiko.mytest"
rootProject.ext.POM_ARTIFACT_ID = "mylibrary"
rootProject.ext.VERSION_NAME = "1.0.0"
apply from: "${project.rootDir}/maven_upload.gradle"
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "---- MyPlugin.apply ----"
project.afterEvaluate {
def myCopyFiles = project.task("myPublishCopyTask", type: Copy) {
from project.fileTree('../app/src/main/assets')
include 'test1.so', 'test2.so'
into 'src/main/assets'
}
myCopyFiles.group = "publishingV2"
//原本task名叫做myPublishDeleteTask,但是后面关联了其他Task后,这个task的名称起做publishAutoCopy是比较合适的
def myDeleteFilesInDir = project.task("publishAutoCopyTask", type: Delete) {
delete project.fileTree('src/main/assets')
}
myDeleteFilesInDir.group = "publishingV2"
def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
def publishTask = "publishProductionPublicationToReleaseMavenRepository"
packageReleaseAssets.mustRunAfter(myCopyFiles)
myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
}
}
}
apply plugin: MyPlugin
5.7 调用myPublishDeleteTask
这个时候,我们在Gradle中执行myPublishDeleteTask这个任务,会发现 自动复制文件 -> 打包 -> 上传到Maven仓库 -> 删除文件这个链路,都自动执行了。
5.7.1 查看Maven仓库
我们来查看下Maven仓库,可以看到已经上传到maven仓库了
5.7.2 下载aar文件
下载这个版本的aar文件

接着拖到Android Studio中,就可以看到assets里已经有test1.so和test2.so了

6. 实现动态配置
接着我们就可以来实现动态的配置了。
要实现动态配置,就需要在app的build.gradle中配置名称和要复制的文件名称,像下面这样 :
apply plugin: MyPlugin
publishAutoCopy {
//要复制的源文件目录
sourcePath = '../app/src/main/assets'
//要复制的目标文件目录
targetPath = 'src/main/assets'
publishItem {
//名称
name = 'weather'
//要复制的文件名
sourceFiles = ['test1.so', 'test2.so']
}
publishItem {
//名称
name = 'ocr'
//要复制的文件名
sourceFiles = ['test3.so']
}
}
那么需要怎么将这些配置传递到MyPlugin插件中呢 ?
6.1 编写传参类
class PublishAutoCopyItem {
String name
ArrayList<String> sourceFiles
}
class PublishAutoCopyExtension {
private Project project
String targetPath
String sourcePath
List<PublishAutoCopyItem> items = []
PublishAutoCopyItem publishItem(Closure closure) {
PublishAutoCopyItem myItem = new PublishAutoCopyItem()
project.configure(myItem, closure)
items << myItem
return myItem
}
PublishAutoCopyExtension(Project project) {
this.project = project
}
}
6.2 在Plugin中获取传参
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def extension = project.extensions.create("publishAutoCopy", PublishAutoCopyExtension)
project.afterEvaluate {
def sourcePath = extension.sourcePath ?: ""
def targetPath = extension.targetPath ?: ""
def items = extension.items
println("sourcePath:" + sourcePath + " targetPath:" + targetPath)
for (final def item in items) {
println("item.name:" + item.name+" sourceFiles:"+item.sourceFiles)
}
//省略了之前写的代码...
}
}
}
6.3 在Build.gradle中配置
这个配置在MyLibrary的build.gradle中即可
publishAutoCopy {
//要复制的源文件目录
sourcePath = '../app/src/main/assets'
//要复制的目标文件目录
targetPath = 'src/main/assets'
publishItem {
//名称
name = 'weather'
//要复制的文件名
sourceFiles = ['test1.so', 'test2.so']
}
publishItem {
//名称
name = 'ocr'
//要复制的文件名
sourceFiles = ['test3.so']
}
//更多的配置可以在这里增加...
}
6.4 Sync下项目
然后我们Sync下项目,可以看到Gralde打印出了如下的日志
> Configure project :NcnnLibrary
sourcePath:../app/src/main/assets targetPath:src/main/assets
item.name:weather sourceFiles:[test1.so, test2.so]
item.name:ocr sourceFiles:[test3.so]
这样,我们就将配置传递给我们自定义的MyPlugin插件了
6.5 将参数传递给Task
将这些参数传递给Task,最终代码如下
project.afterEvaluate {
def names = new ArrayList<>()
def sourcePath = extension.sourcePath ?: ""
def targetPath = extension.targetPath ?: ""
for (final def item in extension.items) {
def name = item.name ?: ""
def sourceFiles = item.sourceFiles ?: [""]
if (name.isEmpty()) return
names.add(name)
def nameCapitalize = name.capitalize()
def myCopyFiles = project.task("myPublish${nameCapitalize}CopyTask", type: Copy) {
from project.fileTree(sourcePath)
include sourceFiles
into targetPath
}
def myDeleteFilesInDir = project.task("publish${nameCapitalize}AutoCopy", type: Delete) {
delete project.fileTree(targetPath)
}
myDeleteFilesInDir.group = "publishingv2"
def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
def publishTask = "publish${nameCapitalize}PublicationToReleaseMavenRepository"
packageReleaseAssets.mustRunAfter(myCopyFiles)
myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
}
project.rootProject.ext["${project.name}_NAMES"] = names
}
Sync项目后,可以发现有publishOcrAutoCopy和publishWeatherAutoCopy两个Task了

6.6 修改 maven_upload.gradle
myArtifactId现在通过rootProject.ext.POM_ARTIFACT_ID进行传递,所以我们现在需要修改maven_upload.gradle
apply plugin: 'maven-publish'
//TODO 这里填你Maven仓库的地址
def RELEASE_REPOSITORY_URL = "https://devops-maven.xxxxx.com/repository/yyyyy/"
//TODO 这里填你Maven仓库的账号
def NEXUS_USERNAME = "*********"
//TODO 这里填你Maven仓库的密码
def NEXUS_PASSWORD = "*********"
afterEvaluate {
publishing {
repositories {
maven {
name("ReleaseMaven")
url = RELEASE_REPOSITORY_URL
credentials {
username = NEXUS_USERNAME
password = NEXUS_PASSWORD
}
}
}
publications {
List<String> names
def namesKey = "${project.name}_NAMES"
if (rootProject.ext.has(namesKey)) {
names = rootProject.ext[namesKey]
} else {
names = new ArrayList()
names.add("")
}
for (final def itemName in names) {
def publicationName = itemName.capitalize()
create(publicationName, MavenPublication) {
def myArtifactId
if (name.isEmpty()) myArtifactId = rootProject.ext.POM_ARTIFACT_ID
else myArtifactId = rootProject.ext.POM_ARTIFACT_ID + "-" + itemName
from components.release
groupId = rootProject.ext.GROUP
artifactId = myArtifactId
version = rootProject.ext.VERSION_NAME
}
}
}
}
}
6.7 动态配置完成
到这里,我们就完成动态配置了 : 我们想要打包带某些文件的aar,就点击对应的Task进行打包就行了
- 想打带
test1.so和test2.so的包,那么就点击publishWeatherAutoCopy - 想打带
test3.so的包,那么就点击publishOcrAutoCopy
7. 将MyPlugin移到独立的gradle文件中
我们还可以将MyPlugin相关的代码移到一个单独的gradle文件中,比如publish_auto_copy.gradle,具体方式和maven_upload.gradle类似。
这样,我们就可以在build.gradle中,使用apply from: "${project.rootProject.rootDir}/publish_auto_copy.gradle"这一句代码,就应用MyPlugin插件了。接着,在build.gradle中配置好MyPlugin的参数就行了。
apply from: "${project.rootProject.rootDir}/publish_auto_copy.gradle"
publishAutoCopy {
//要复制的源文件目录
sourcePath = '../app/src/main/assets'
//要复制的目标文件目录
targetPath = 'src/main/assets'
publishItem {
//名称
name = 'weather'
//要复制的文件名
sourceFiles = ['test1.so', 'test2.so']
}
publishItem {
//名称
name = 'ocr'
//要复制的文件名
sourceFiles = ['test3.so']
}
//更多的配置可以在这里增加...
}
至此,我们就完成了打包自动复制文件插件的全部功能了。


















