From 5ead63a8a3d8e38cdd2c0a03cb1dcec9403c724a Mon Sep 17 00:00:00 2001 From: WaterRain <109326062+CATMIAOZHI@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:18:39 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20AGP=209.0=20ARM64=20Proot=20release=20AP?= =?UTF-8?q?K=20=E7=BC=BA=20AndroidManifest.xml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packageRelease 任务在 ARM64 Proot 环境下丢失 manifest, 新增 fixReleaseManifest 后处理任务:从 .ap_ 提取 binary AndroidManifest.xml 注入 APK 并用 apksigner 重新签名。 仅 aarch64 环境触发,CI x86_64 不受影响。 --- app/build.gradle.kts | 91 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e1833a0..bdeb62c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 {