fix: AGP 9.0 ARM64 Proot release APK 缺 AndroidManifest.xml

packageRelease 任务在 ARM64 Proot 环境下丢失 manifest,
新增 fixReleaseManifest 后处理任务:从 .ap_ 提取 binary
AndroidManifest.xml 注入 APK 并用 apksigner 重新签名。
仅 aarch64 环境触发,CI x86_64 不受影响。
This commit is contained in:
WaterRain 2026-07-17 23:18:39 +00:00
parent ea1cae597d
commit 5ead63a8a3
No known key found for this signature in database

View File

@ -1,3 +1,5 @@
import java.io.File
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
@ -45,6 +47,10 @@ android {
)
}
}
androidResources {
// AGP 9.0 ARM64 Proot: noCompress workaround (manifest still needs post-process)
noCompress += "xml"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@ -59,6 +65,91 @@ android {
}
}
// ═══════════════════════════════════════════════════════
// AGP 9.0 ARM64 Proot Workaround: packageRelease 丢失
// AndroidManifest.xml
//
// 修复:assembleRelease 后检查 APK 是否含 manifest,
// 缺失则注入并重新签名。仅 aarch64 环境触发。
// ═══════════════════════════════════════════════════════
if (System.getProperty("os.arch") == "aarch64") {
tasks.register("fixReleaseManifest") {
dependsOn("assembleRelease")
doLast {
val apk = layout.buildDirectory.file("outputs/apk/release/app-release.apk").get().asFile
val manifest = layout.buildDirectory
.file("intermediates/linked_resources_binary_format/release/processReleaseResources/linked-resources-binary-format-release.ap_")
.get().asFile
if (!apk.exists() || !manifest.exists()) return@doLast
// 检查 APK 是否已包含 manifest
val checkResult = providers.exec {
commandLine("unzip", "-l", apk.absolutePath)
isIgnoreExitValue = true
}.standardOutput.asText.get()
if (!checkResult.contains("AndroidManifest.xml")) {
logger.lifecycle("FixManifestTask: injecting AndroidManifest.xml into release APK")
// 从 .ap_ 提取 binary AndroidManifest.xml 到临时文件
val tmpManifest = File(apk.parentFile, "AndroidManifest.xml")
val extractProcess = Runtime.getRuntime().exec(
arrayOf("unzip", "-o", manifest.absolutePath, "AndroidManifest.xml", "-d", apk.parentFile!!.absolutePath)
)
extractProcess.waitFor()
logger.lifecycle("FixManifestTask: extract from .ap_ exit code = ${extractProcess.exitValue()}")
// 用 zip 命令注入 binary manifest
val zipProcess = Runtime.getRuntime().exec(arrayOf("zip", "-j", apk.absolutePath, tmpManifest.absolutePath))
zipProcess.waitFor()
logger.lifecycle("FixManifestTask: zip exit code = ${zipProcess.exitValue()}")
// 找 apksigner 并重新签名
val androidHome = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
var signerFile: File? = null
if (androidHome != null) {
val btDir = File(androidHome, "build-tools")
val latest = btDir.listFiles()?.filter { it.isDirectory }?.maxByOrNull { it.name }
if (latest != null) {
val signer = File(latest, "apksigner")
if (signer.exists()) signerFile = signer
}
}
val sf = signerFile
if (sf != null && sf.exists()) {
// 从 android extension 拿签名配置
val androidExt = project.extensions.findByName("android")
val signingConfig = (androidExt as? com.android.build.api.dsl.ApplicationExtension)
?.signingConfigs?.findByName("release")
val ksFile = signingConfig?.storeFile?.absolutePath ?: ""
val ksPass = signingConfig?.storePassword ?: ""
val ksAlias = signingConfig?.keyAlias ?: ""
val ksKeyPass = signingConfig?.keyPassword ?: ""
val signCmd = arrayOf(
sf.absolutePath, "sign",
"--ks", ksFile,
"--ks-pass", "pass:$ksPass",
"--ks-key-alias", ksAlias,
"--key-pass", "pass:$ksKeyPass",
"--v2-signing-enabled", "true",
"--v3-signing-enabled", "false",
"--min-sdk-version", "31",
apk.absolutePath
)
val signProcess = Runtime.getRuntime().exec(signCmd)
val errorText = signProcess.errorStream.bufferedReader().readText()
signProcess.waitFor()
logger.lifecycle("FixManifestTask: apksigner exit code = ${signProcess.exitValue()}")
if (errorText.isNotBlank()) logger.lifecycle("FixManifestTask: apksigner stderr = $errorText")
logger.lifecycle("FixManifestTask: APK re-signed successfully")
} else {
logger.warn("FixManifestTask: apksigner not found, APK not re-signed")
}
}
}
}
project.tasks.matching { it.name == "assembleRelease" }.configureEach {
finalizedBy("fixReleaseManifest")
}
}
// Force ARM64 AAPT2 in Proot environment (local only; GitHub Actions x86_64 uses default)
if (System.getProperty("os.arch") == "aarch64") {
configurations.all {