Skip to content

产物处理工具集

源:Gradle Copy Task

处理构建产物的常见操作:复制、重命名、压缩和分发。

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.zip

CI 集成

yaml
- name: Prepare Release
  run: ./gradlew prepareRelease
  
- name: Upload Artifacts
  uses: actions/upload-artifact@v3
  with:
    name: release-package
    path: releases/*.zip