# 用 aapt2 + javac + d8 + zipalign + apksigner 手工构建 APK(不需要 Gradle) # 用法: powershell -File android\build_apk.ps1 # # 注意:刻意不用 $ErrorActionPreference="Stop"。 # 原生工具的告警(javac 的 deprecation note 等)会写 stderr, # 在 Stop 模式下会被 PowerShell 当成致命错误中断构建。 # 改为每一步显式检查 $LASTEXITCODE。 $ErrorActionPreference = "Continue" $android = $PSScriptRoot # LanRemote/android(本脚本所在目录) $root = Split-Path -Parent $android # LanRemote $tc = Join-Path $root "..\starklink\toolchain" $jdk = Join-Path $tc "jdk17" $sdk = Join-Path $tc "android-sdk" $bt = Join-Path $sdk "build-tools\34.0.0" $androidJar = Join-Path $sdk "platforms\android-34\android.jar" $keystore = Join-Path $tc "debug.keystore" $javac = Join-Path $jdk "bin\javac.exe" $keytool = Join-Path $jdk "bin\keytool.exe" $jar = Join-Path $jdk "bin\jar.exe" $aapt2 = Join-Path $bt "aapt2.exe" $zipalign = Join-Path $bt "zipalign.exe" $apksigner= Join-Path $bt "apksigner.bat" $d8 = Join-Path $bt "d8.bat" $work = Join-Path $android "build" $outDir = Join-Path $root "dist" $apkName = "ykxmz-watch-2.1.8.apk" function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan } # 前置检查 foreach ($tool in @($javac, $aapt2, $d8, $zipalign, $apksigner)) { if (-not (Test-Path $tool)) { throw "缺少工具: $tool" } } if (-not (Test-Path $androidJar)) { throw "缺少 android.jar: $androidJar" } if (-not (Test-Path $keystore)) { throw "缺少签名用 keystore: $keystore" } $env:JAVA_HOME = $jdk $env:PATH = "$(Join-Path $jdk 'bin');$env:PATH" if (Test-Path $work) { Remove-Item -Recurse -Force $work } New-Item -ItemType Directory -Force -Path $work | Out-Null New-Item -ItemType Directory -Force -Path $outDir | Out-Null # ---------------- 1. 编译资源 ---------------- Step "1/6 aapt2 compile 资源" $compiled = Join-Path $work "res.zip" & $aapt2 compile --dir (Join-Path $android "res") -o $compiled if ($LASTEXITCODE -ne 0) { throw "aapt2 compile 失败" } # ---------------- 2. 链接资源 + 生成 R.java ---------------- Step "2/6 aapt2 link 生成资源表与 R.java" $genDir = Join-Path $work "gen" New-Item -ItemType Directory -Force -Path $genDir | Out-Null $unsigned = Join-Path $work "app-unsigned.apk" & $aapt2 link ` -o $unsigned ` -I $androidJar ` --manifest (Join-Path $android "AndroidManifest.xml") ` -R $compiled ` --java $genDir ` --min-sdk-version 24 ` --target-sdk-version 34 ` --version-code 5 ` --version-name "2.1.8" ` --auto-add-overlay if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" } # ---------------- 3. 编译 Java ---------------- Step "3/6 javac 编译 Java 源码" $classesDir = Join-Path $work "classes" New-Item -ItemType Directory -Force -Path $classesDir | Out-Null $javaSources = @() $javaSources += Get-ChildItem -Recurse -Filter *.java -Path (Join-Path $android "src") | ForEach-Object { $_.FullName } $javaSources += Get-ChildItem -Recurse -Filter R.java -Path $genDir -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } if ($javaSources.Count -eq 0) { throw "没有找到 Java 源文件" } Write-Host "源文件 $($javaSources.Count) 个" & $javac -source 8 -target 8 -nowarn -Xlint:none -encoding UTF-8 ` -bootclasspath $androidJar ` -d $classesDir ` $javaSources 2>&1 | Out-String | Write-Host if ($LASTEXITCODE -ne 0) { throw "javac 编译失败" } # ---------------- 4. d8 转 dex ---------------- Step "4/6 d8 转换为 classes.dex" $classesJar = Join-Path $work "classes.jar" & $jar cf $classesJar -C $classesDir . if ($LASTEXITCODE -ne 0) { throw "打包 classes.jar 失败" } $dexDir = Join-Path $work "dex" New-Item -ItemType Directory -Force -Path $dexDir | Out-Null & $d8 --lib $androidJar --min-api 24 --output $dexDir $classesJar 2>&1 | Out-String | Write-Host if ($LASTEXITCODE -ne 0) { throw "d8 失败" } $dex = Join-Path $dexDir "classes.dex" if (-not (Test-Path $dex)) { throw "未生成 classes.dex" } Write-Host ("classes.dex 大小 {0:N1} KB" -f ((Get-Item $dex).Length / 1KB)) # ---------------- 5. 组装 APK ---------------- Step "5/6 组装 APK" & python (Join-Path $root "_build\pack_apk.py") $unsigned $dex if ($LASTEXITCODE -ne 0) { throw "写入 classes.dex 失败" } $aligned = Join-Path $work "app-aligned.apk" & $zipalign -f 4 $unsigned $aligned if ($LASTEXITCODE -ne 0) { throw "zipalign 失败" } # ---------------- 6. 签名 ---------------- Step "6/6 apksigner 签名" $final = Join-Path $outDir $apkName if (Test-Path $final) { Remove-Item $final -Force } & $apksigner sign ` --ks $keystore ` --ks-pass pass:android ` --key-pass pass:android ` --ks-key-alias androiddebugkey ` --v1-signing-enabled true ` --v2-signing-enabled true ` --out $final ` $aligned if ($LASTEXITCODE -ne 0) { throw "签名失败" } Step "验证签名" & $apksigner verify --print-certs $final if ($LASTEXITCODE -ne 0) { throw "签名校验失败" } Step "构建完成" Write-Host ("APK: {0}" -f $final) -ForegroundColor Green Write-Host ("大小: {0:N2} MB" -f ((Get-Item $final).Length / 1MB)) -ForegroundColor Green