产物处理工具集
处理构建产物的常见操作:复制、重命名、压缩和分发。
APK/AAB 重命名
基本重命名
使用 Variant Output API:
kotlin
android {
applicationVariants.all {
outputs.all {
val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variant = this@all
output.outputFileName = "MyApp-${variant.name}-${variant.versionName}.apk"
}
}
}输出示例:
MyApp-debug-1.0.0.apk
MyApp-release-1.0.0.apk包含更多信息
kotlin
android {
applicationVariants.all {
outputs.all {
val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variant = this@all
val gitHash = providers.exec {
commandLine("git", "rev-parse", "--short", "HEAD")
}.standardOutput.asText.get().trim()
val date = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
output.outputFileName = buildString {
append("MyApp")
append("-${variant.name}")
append("-${variant.versionName}")
append("-${gitHash}")
append("-${date}")
append(".apk")
}
}
}
}输出:
MyApp-release-1.0.0-abc1234-20260126.apk复制产物
Copy 任务
基本复制:
kotlin
tasks.register<Copy>("copyApk") {
from("${layout.buildDirectory.get()}/outputs/apk/release")
into("${rootProject.projectDir}/releases")
include("*.apk")
}自动触发:
kotlin
tasks.named("assembleRelease") {
finalizedBy("copyApk")
}复制到多个位置
kotlin
tasks.register<Copy>("distributeApk") {
from("${layout.buildDirectory.get()}/outputs/apk/release")
into("${rootProject.projectDir}/releases")
into("D:/Releases")
into("\\\\server\\shared\\releases")
include("*.apk")
}复制并重命名
kotlin
tasks.register<Copy>("copyAndRename") {
from("${layout.buildDirectory.get()}/outputs/apk/release") {
include("*.apk")
rename { "MyApp-${project.version}.apk" }
}
into("${rootProject.projectDir}/releases")
}压缩打包
创建 ZIP
kotlin
tasks.register<Zip>("packageRelease") {
from("${layout.buildDirectory.get()}/outputs/apk/release") {
include("*.apk")
}
from("${layout.buildDirectory.get()}/outputs/mapping/release") {
include("mapping.txt")
}
archiveFileName.set("MyApp-${project.version}.zip")
destinationDirectory.set(layout.buildDirectory.dir("distributions"))
}包含多个文件
kotlin
tasks.register<Zip>("packageAll") {
from("${layout.buildDirectory.get()}/outputs/apk/release") {
include("*.apk")
into("apk")
}
from("${layout.buildDirectory.get()}/outputs/bundle/release") {
include("*.aab")
into("bundle")
}
from("${layout.buildDirectory.get()}/outputs/mapping/release") {
include("mapping.txt")
into("mapping")
}
from("${projectDir}/CHANGELOG.md")
archiveFileName.set("MyApp-${project.version}-full.zip")
destinationDirectory.set(file("${rootProject.projectDir}/releases"))
}清理旧产物
删除旧文件
kotlin
tasks.register<Delete>("cleanOldReleases") {
delete(fileTree("${rootProject.projectDir}/releases") {
include("*.apk")
exclude("*${project.version}*")
})
}保留最新N个
kotlin
tasks.register("cleanOldApks") {
doLast {
val releasesDir = file("${rootProject.projectDir}/releases")
val apks = releasesDir.listFiles { file ->
file.extension == "apk"
}?.sortedByDescending { it.lastModified() } ?: emptyList()
apks.drop(5).forEach { it.delete() }
}
}上传到服务器
FTP 上传
kotlin
tasks.register("uploadToFtp") {
doLast {
val ftpClient = org.apache.commons.net.ftp.FTPClient()
ftpClient.connect("ftp.example.com")
ftpClient.login("user", "password")
val apkFile = file("${layout.buildDirectory.get()}/outputs/apk/release/app-release.apk")
apkFile.inputStream().use { input ->
ftpClient.storeFile("MyApp-${project.version}.apk", input)
}
ftpClient.disconnect()
}
}HTTP 上传
kotlin
tasks.register("uploadToServer") {
doLast {
val apkFile = file("${layout.buildDirectory.get()}/outputs/apk/release/app-release.apk")
val client = java.net.http.HttpClient.newHttpClient()
val request = java.net.http.HttpRequest.newBuilder()
.uri(URI("https://server.example.com/upload"))
.header("Content-Type", "application/octet-stream")
.POST(java.net.http.HttpRequest.BodyPublishers.ofFile(apkFile.toPath()))
.build()
client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString())
}
}生成变更日志
从 Git Commits
kotlin
tasks.register("generateChangelog") {
val changelogFile = file("${layout.buildDirectory.get()}/CHANGELOG.md")
doLast {
val commits = providers.exec {
commandLine("git", "log", "--oneline", "--since", "1 week ago")
}.standardOutput.asText.get()
changelogFile.writeText("""
# Changelog
## Version ${project.version}
$commits
""".trimIndent())
}
}实战案例
案例1:完整发布流程
kotlin
tasks.register("prepareRelease") {
group = "release"
description = "准备发布包"
dependsOn("clean", "assembleRelease", "bundleRelease")
doLast {
val releaseDir = file("${rootProject.projectDir}/releases/${project.version}")
releaseDir.mkdirs()
// 复制 APK
copy {
from("${layout.buildDirectory.get()}/outputs/apk/release")
into("${releaseDir}/apk")
include("*.apk")
}
// 复制 AAB
copy {
from("${layout.buildDirectory.get()}/outputs/bundle/release")
into("${releaseDir}/bundle")
include("*.aab")
}
// 复制 Mapping
copy {
from("${layout.buildDirectory.get()}/outputs/mapping/release")
into("${releaseDir}/mapping")
}
// 生成压缩包
val zipTask = tasks.register<Zip>("zipRelease") {
from(releaseDir)
archiveFileName.set("MyApp-${project.version}.zip")
destinationDirectory.set(file("${rootProject.projectDir}/releases"))
}
zipTask.get().zip()
}
}案例2:自动分发
kotlin
android {
applicationVariants.all {
val variant = this
// 创建分发任务
tasks.register("distribute${variant.name.capitalize()}") {
dependsOn("assemble${variant.name.capitalize()}")
doLast {
val apkFile = variant.outputs.first().outputFile
// 复制到共享文件夹
copy {
from(apkFile)
into("\\\\server\\shared\\android-builds")
rename { "${variant.name}-${variant.versionName}.apk" }
}
// 通知
println("✅ APK 已分发到共享文件夹")
}
}
}
}最佳实践
自动化流程:
kotlin
tasks.named("assembleRelease") {
finalizedBy("copyApk", "generateChangelog")
}版本号命名:
kotlin
"MyApp-${variant.name}-${variant.versionName}-${gitHash}.apk"清理策略:
- 定期清理旧产物
- 保留最新几个版本
- 备份重要版本
目录结构:
releases/
├── 1.0.0/
│ ├── apk/
│ ├── bundle/
│ └── mapping/
├── 1.0.1/
└── MyApp-1.0.1.zipCI 集成:
yaml
- name: Prepare Release
run: ./gradlew prepareRelease
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: release-package
path: releases/*.zip