文章目录
-
- [0. 总览](#0. 总览)
-
- [0.1 版本锁定表](#0.1 版本锁定表)
- [0.2 目录规划](#0.2 目录规划)
- [0.3 四段依赖顺序](#0.3 四段依赖顺序)
- [1. 阶段 A · 基础运行时](#1. 阶段 A · 基础运行时)
-
- [1.1 JDK 21](#1.1 JDK 21)
- [1.2 Node.js 22 LTS](#1.2 Node.js 22 LTS)
- [1.3 Git](#1.3 Git)
- [1.4 PowerShell 脚本策略(条件性)](#1.4 PowerShell 脚本策略(条件性))
- [1.5 环境变量(用户变量)](#1.5 环境变量(用户变量))
- [1.6 验收 A](#1.6 验收 A)
- [1.7 常见坑](#1.7 常见坑)
- [2. 阶段 B · Android SDK(不装 IDE)](#2. 阶段 B · Android SDK(不装 IDE))
-
- [2.1 下载命令行工具](#2.1 下载命令行工具)
- [2.2 目录归位(整套流程最容易错的一步)](#2.2 目录归位(整套流程最容易错的一步))
- [2.3 安装 SDK 三件套(必须用 CMD)](#2.3 安装 SDK 三件套(必须用 CMD))
- [2.4 接受许可](#2.4 接受许可)
- [2.5 验收 B](#2.5 验收 B)
- [2.6 常见坑](#2.6 常见坑)
- [3. npm 配置](#3. npm 配置)
- [4. 阶段 C · demo 项目创建](#4. 阶段 C · demo 项目创建)
-
- [4.1 建项目](#4.1 建项目)
- [4.2 `vite.config` 加 `base`(防白屏的兼容性兜底)](#4.2
vite.config加base(防白屏的兼容性兜底)) - [4.3 装 Capacitor 8 三个包](#4.3 装 Capacitor 8 三个包)
- [4.4 `cap init` 与配置文件](#4.4
cap init与配置文件) - [4.5 `webDir` 对照表](#4.5
webDir对照表) - [4.6 build → add → sync](#4.6 build → add → sync)
- [5. APK 打包(debug)](#5. APK 打包(debug))
-
- [5.1 首次构建前:换 Gradle 发行版镜像(关键修复)](#5.1 首次构建前:换 Gradle 发行版镜像(关键修复))
- [5.2 构建](#5.2 构建)
- [5.3 产物](#5.3 产物)
- [5.4 构建告警(全部无害,不要为它们改文件)](#5.4 构建告警(全部无害,不要为它们改文件))
- [5.5 装机与分发(可选)](#5.5 装机与分发(可选))
- [6. 日常最小循环与禁令](#6. 日常最小循环与禁令)
- [7. release 签名包](#7. release 签名包)
-
- [7.1 两个密码是什么](#7.1 两个密码是什么)
- [7.2 生成 keystore(只做一次)](#7.2 生成 keystore(只做一次))
- [7.3 `keystore.properties`(手动新建,不会自动生成)](#7.3
keystore.properties(手动新建,不会自动生成)) - [7.4 `build.gradle` 三处改动](#7.4
build.gradle三处改动) - [7.5 `.gitignore` 追加两行](#7.5
.gitignore追加两行) - [7.6 出包](#7.6 出包)
- [7.7 验签与输出解读](#7.7 验签与输出解读)
- [7.8 签名指纹基准(用于以后核对是否同一个 keystore)](#7.8 签名指纹基准(用于以后核对是否同一个 keystore))
- [7.9 发布注意](#7.9 发布注意)
- [8. 换成自己的项目](#8. 换成自己的项目)
- [9. 排错速查总表](#9. 排错速查总表)
- [10. 附录](#10. 附录)
-
- [10.1 环境变量速查](#10.1 环境变量速查)
- [10.2 关键路径速查](#10.2 关键路径速查)
- [10.3 完整命令流水(新机复现用,按序执行)](#10.3 完整命令流水(新机复现用,按序执行))
- [10.4 已知限制](#10.4 已知限制)
用途 :全新 Windows 电脑,从零搭建混合开发(Web 转 APK)环境,覆盖环境搭建、demo 项目创建、debug / release 出包、签名配置、排错速查。
实操验证 :2026-11-10 全流程跑通,未安装 Android Studio,未连接真机或模拟器。
核心原理 :Web 构建产物 →
cap sync拷入 Android 壳工程 → Gradle / AGP 编译 → APK(或 AAB)。约定 :全程使用 CMD(命令提示符),路径全部放 D 盘、纯英文无空格。所有
npx cap在项目根目录 执行,所有gradlew.bat在android\目录执行。
0. 总览
0.1 版本锁定表
| 组件 | 锁定版本 | 说明 |
|---|---|---|
| JDK | Eclipse Temurin 21(21.0.13+9) |
Capacitor 8 用 Java 21 编译,装 17 会报「无效的源发行版: 21」 |
| Node.js | 22 LTS(v22.23.4) |
Capacitor 8 CLI 要求 node >= 22,20 直接不支持 |
| npm | 10.9.9(Node 22 自带) | 无需升级 |
| Git | 2.56.0.4 | 可选,仅用于拉代码 |
| Android SDK | Platform 36 / build-tools 36.0.0 / platform-tools 37.0.1 | compileSdk = targetSdk = 36 |
| AGP / Gradle | 8.13 / 8.14.3 | 由 cap add android 生成的 wrapper 锁定,不要全局装 Gradle |
| Capacitor | 8.x(core / android / cli) | minSdk = 24 |
| 脚手架 | create-vite 9.3.4 | engines ^20.19.0 or >=22.12.0,Node 22.23.4 兼容 |
| Android Studio | 不需要 | 只用 commandlinetools + gradlew |
0.2 目录规划
bash
D:\dev\jdk-21 JDK 21(固定目录名,不带版本号)
D:\dev\nodejs Node.js
D:\dev\Git Git
D:\dev\Android\Sdk Android SDK(ANDROID_HOME)
D:\dev\gradle-home Gradle 缓存(GRADLE_USER_HOME)
D:\dev\npm-cache npm 缓存
D:\dev\projects\hello-app 项目
D:\dev\release.jks 签名密钥库(务必异地备份)
0.3 四段依赖顺序
bash
A 运行时 JDK21 / Node22 / Git / 环境变量 / npm 配置
↓ sdkmanager 是 Java 程序,必须先有 JAVA_HOME
B SDK commandlinetools 归位 → 装三件套 → 接受 licenses
↓ Gradle 靠 ANDROID_HOME 找 SDK、靠 android-36 编译
C 项目 Vite → Capacitor 三包 → cap init → build → add → sync → assembleDebug
↓ 正式分发才需要
D 签名 keytool → keystore.properties → build.gradle → assembleRelease → apksigner
环境变量任何改动都必须关掉所有终端和 VSCode 重新打开,只对新建进程生效。
1. 阶段 A · 基础运行时
1.1 JDK 21
安装包:OpenJDK21U-jdk_x64_windows_hotspot_21.0.13_9.msi(Eclipse Temurin / adoptium.net)。
- 为什么不用 Oracle JDK:Oracle JDK 21 的 21.0.12 及更早走 NFTC 免费许可,从 21.0.13 起改回 OTN(商业生产需订阅)。Temurin 走 GPLv2 + Classpath Exception,无免费窗口、可商用、可自由打补丁。两者功能等同,版本号相同但法律性质不同。
- Custom Setup 页把安装目录改成
D:\dev\jdk-21。目录名不要带版本号 ,否则升级 JDK 后JAVA_HOME会失效。 - 装完
where java应只有一条,指向D:\dev\jdk-21\bin\java.exe。
1.2 Node.js 22 LTS
安装包:node-v22.23.4-x64.msi,路径 D:\dev\nodejs\。
Custom Setup页 5 项全部保留(Node.js runtime、corepack manager、npm package manager、Online documentation shortcuts、Add to PATH)。蓝色高亮只是「当前选中项显示说明」,不是只装这一项 ;尤其Add to PATH别动,MSI 靠它自动写 PATH。Tools for Native Modules页的Automatically install the necessary tools不要勾(会额外装 Chocolatey + Python + VS Build Tools,Capacitor 8 依赖全是纯 JS)。- MSI 会自动写 PATH,不用手动加 node 那一条。
1.3 Git
安装包:Git-2.56.0.4-64-bit.exe,路径 D:\dev\Git。
Adjusting your PATH environment选第二项Git from the command line and also from 3rd-party software。第一项会让 CMD / PowerShell 认不到 git;第三项会用 Git 自带的find/sort覆盖 Windows 系统工具。Choosing the default editor保持默认 Vim 也没关系(打包链路不调用 Git 编辑器),以后随时可改:git config --global core.editor notepad。- 行尾转换保持默认
Checkout Windows-style, commit Unix-style。 - 完成页取消
Launch Git Bash/View Release Notes。
1.4 PowerShell 脚本策略(条件性)
bash
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
仅当使用 PowerShell 或 VSCode 内置终端时需要(否则 npx.ps1 cannot be loaded)。全程用 CMD 则完全不用设 ,CMD 走的是 npx.cmd。设一次永久生效。
1.5 环境变量(用户变量)
Win+R → sysdm.cpl → 高级 → 环境变量 → 用户变量:
| 变量 | 值 |
|---|---|
JAVA_HOME |
D:\dev\jdk-21 |
ANDROID_HOME |
D:\dev\Android\Sdk |
GRADLE_USER_HOME |
D:\dev\gradle-home |
Path 追加三条(Node / Git 由 MSI 自动写入,不要再手动加):
bash
%JAVA_HOME%\bin
%ANDROID_HOME%\cmdline-tools\latest\bin
%ANDROID_HOME%\platform-tools
说明:SDK、gradle-home、cmdline-tools\latest\bin 此时还不存在也没关系,PATH 允许指向尚未创建的目录;%JAVA_HOME%\bin 建议放靠前位置,避免被其他 JDK 抢先。
1.6 验收 A
bash
java -version
node -v
npm -v
git --version
where java
echo %JAVA_HOME%
echo %ANDROID_HOME%
echo %GRADLE_USER_HOME%
| 命令 | 期望 |
|---|---|
java -version |
openjdk 21.0.13 / Temurin |
node -v |
v22.23.4 |
npm -v |
10.9.9 |
git --version |
2.56.0.4 |
where java |
只有一条 D:\dev\jdk-21\bin\java.exe |
三个 echo |
分别输出三个 D 盘路径 |
1.7 常见坑
| 现象 | 处理 |
|---|---|
echo %ANDROID_HOME% 输出字面量 %ANDROID_HOME% |
该 CMD 窗口在配置前就开着,环境变量是进程启动时的快照;关掉重开一个新窗口即可。也可用 set ANDROID 检查变量名是否拼错或带空格 |
where java 指向别的目录 |
把 %JAVA_HOME%\bin 上移到靠前位置;Oracle JDK 安装器会塞 javapath,Temurin 不会 |
npx.ps1 cannot be loaded |
见 1.4 |
2. 阶段 B · Android SDK(不装 IDE)
2.1 下载命令行工具
commandlinetools-win-15859902_latest.zip(约 155MB)
直链:https://dl.google.com/android/repository/commandlinetools-win-15859902_latest.zip
2.2 目录归位(整套流程最容易错的一步)
zip 解压后里面是一个 cmdline-tools 文件夹,要把它里面的内容 (bin、lib、source.properties、NOTICE.txt)放进 <Sdk>\cmdline-tools\latest\:
text
D:\dev\Android\Sdk\
└─ cmdline-tools\
└─ latest\
├─ bin\sdkmanager.bat ← 必须存在这个文件
├─ lib\
└─ source.properties
- 多套一层
cmdline-tools、或漏掉latest,后面全线失败。 - 若已解压到
D:\dev\cmdline-tools,先Test-Path D:\dev\cmdline-tools\bin\sdkmanager.bat确认层级,再整体剪切并改名为latest;归位后D:\dev\根目录不该再留cmdline-tools。
2.3 安装 SDK 三件套(必须用 CMD)
bash
sdkmanager --sdk_root=D:\dev\Android\Sdk "platform-tools" "platforms;android-36" "build-tools;36.0.0"
- 包名含分号,双引号不能去掉 :PowerShell 传
.bat时可能被 cmd 拆成两个参数,CMD 里双引号能保护它。 - 首次提示
Accept? (y/N)输入y。下载几百 MB,100% Unzipping...是正常解包过程,别关窗口。 sdkmanager本身是 Java 程序,依赖JAVA_HOME或 PATH 里的 java.exe,所以环境变量要先配好。- 开头的
WARNING: sdkmanager is deprecated. Use Android CLI instead可忽略 :Google 推荐新的android sdk命令,但 sdkmanager 仍完整可用,且 Gradle / AGP 只读 SDK 目录文件,与用哪个 CLI 装包无关。 - 超时就重跑同一条命令,已装好的包会跳过;
dl.google.com不通时挂代理或换镜像。
2.4 接受许可
bash
sdkmanager --sdk_root=D:\dev\Android\Sdk --licenses
一路 y(约 6--8 次),直到 All SDK package licenses accepted。漏这步 Gradle 会报 licenses not accepted。
2.5 验收 B
bash
adb version
sdkmanager --sdk_root=D:\dev\Android\Sdk --list_installed
list_installed 三项齐全(本机实测输出):
| Path | Version |
|---|---|
build-tools;36.0.0 |
36.0.0 |
platform-tools |
37.0.1 |
platforms;android-36 |
2 |
adb version 会显示 Installed as D:\dev\Android\Sdk\platform-tools\adb.exe,同时验证 PATH 生效。
2.6 常见坑
| 现象 | 处理 |
|---|---|
sdkmanager 不是内部命令 |
2.2 目录结构错,或没重开终端 |
ERROR: JAVA_HOME is not set |
补 1.5 的环境变量并重开终端 |
Could not determine SDK root |
命令补 --sdk_root=D:\dev\Android\Sdk,核对 ANDROID_HOME |
platforms is not a valid package |
换 CMD + 双引号,别用 PowerShell |
licenses not accepted |
补跑 2.4 |
3. npm 配置
bash
npm config set registry https://registry.npmmirror.com
npm config set cache D:\dev\npm-cache
npm config get registry
npm config get cache
- 不要加
--global:会写到 Node 安装目录下的全局 npmrc,可能需要管理员权限且升级 Node 会丢。上面两条写进用户级C:\Users\<你>\.npmrc。 npm config get(不带参数)列出全部生效配置,应出现; "user" config from ...\.npmrc段和cache/registry两行。prefix = C:\Users\<你>\AppData\Roaming\npm是内建默认值,只影响npm i -g;全程用本地 devDependency +npx,不会往这里写,不用改。- npmmirror 对新发版本偶有同步滞后,装
@capacitor/cli@^8报404时切回官方源:npm config set registry https://registry.npmjs.org。
明确不用做 :升级 npm、装 nvm / corepack / pnpm / yarn、配 NODE_PATH / NODE_ENV / prefix、全局装 @capacitor/cli、装 Python 或 VS Build Tools、全局装 Gradle。
4. 阶段 C · demo 项目创建
4.1 建项目
bash
cd /d D:\dev\projects
npm create vite@latest hello-app -- --template vue
cd hello-app
npm install
交互应答:Ok to proceed? (y) 输 y;若问框架选 Vue → JavaScript;若问 Install with npm and start now? 选 No 。成功标志:Scaffolding project in ... + Done. Now run:,随后 npm install 输出 added xx packages 且无 ERR!。
4.2 vite.config 加 base(防白屏的兼容性兜底)
打开 hello-app\vite.config.js(TS 模板是 .ts),在 defineConfig({ ... }) 里加一行:
js
export default defineConfig({
base: './',
plugins: [vue()],
})
base只改构建产物里<script>/<link>/ 动态 import 的 URL 前缀:/assets/x.js→./assets/x.js。不改源码、不改打包内容。- 自检:
npm run build后打开dist\index.html看src=开头是/assets/还是./assets/。 - 准确定位 :Capacitor 6+ 默认
androidScheme: https,WebView 加载https://localhost/,原生层WebViewLocalServer拦截请求并从assets/public返回字节流,所以绝对路径默认也能加载。base: './'是低成本保险,不是硬性必需 ;它真正防的是file://容器(Cordova / 自建 WebView)和网站子目录部署。 - 什么时候别加 :Vue Router / React Router 的 history 模式且有深层路由时,
./相对「当前 URL」解析,从/user/profile刷新会找/user/assets/x.js→ 404。这类项目保持base: '/'或改用createWebHashHistory()。
| 项目情况 | 建议 |
|---|---|
| 单页 / hash 路由 / 只做 APK | base: './' |
| history 路由、深层路径 | 保持 /,或换 hash 路由 |
| 同时部署到网站子目录 | 用明确的 base: '/子目录/' |
4.3 装 Capacitor 8 三个包
bash
npm i "@capacitor/core@^8" "@capacitor/android@^8"
npm i -D "@capacitor/cli@^8"
CMD 里双引号不能去掉 :^ 是 CMD 转义符,不加引号 @^8 会被吞成 @8。node_modules 是项目级依赖,换项目要重装。
4.4 cap init 与配置文件
bash
npx cap init "HelloApp" "com.example.helloapp" --web-dir=dist
- 生成
capacitor.config.json(本项目用--template vue的 JS 模板,未装 TypeScript)。只有项目里能找到typescript包时才生成capacitor.config.ts,且此时package.json需要"type": "module"(否则Parsing capacitor.config.ts failed)。别同时保留两份配置。 - 位置在项目根目录 ,与
package.json同级,不在android\也不在src\。 - 内容(83 字节):
json
{
"appId": "com.example.helloapp",
"appName": "HelloApp",
"webDir": "dist"
}
cap sync打印的Creating capacitor.config.json in android\app\src\main\assets是自动生成的运行时副本,每次 sync 覆盖,不要手改。要编辑的只有项目根那一个。appId上架后不可改。
4.5 webDir 对照表
webDir 必须等于 npm run build 实际输出、且含最终 index.html 的目录名;cap sync 会把它整体拷到 android\app\src\main\assets\public\。
| 构建工具 | 输出目录 |
|---|---|
| Vite / Vue CLI | dist |
| Create React App | build |
| Nuxt 静态导出 | .output/public |
| Angular | dist\<项目名>\browser |
不确定就先跑一次 npm run build,看哪个新目录里出现 index.html。配错或忘了 sync,结果就是启动白屏。
4.6 build → add → sync
bash
npm run build
npx cap add android
npx cap sync android
cap add android生成android\原生工程,整个项目生命周期只跑一次 ;重复执行会重建并覆盖你在android\里的改动。cap sync等价于copy+update,成功标志:✔ Copying web assets from dist to android\app\src\main\assets\public等五项全绿。- 别跑
npx cap open android,它会启动本机没装的 Android Studio。
5. APK 打包(debug)
5.1 首次构建前:换 Gradle 发行版镜像(关键修复)
首次 gradlew 会从 services.gradle.org 下载 Gradle 8.14.3,国内易触发 wrapper 默认 10 秒超时:
bash
java.io.IOException: Downloading from https://services.gradle.org/distributions/gradle-8.14.3-all.zip failed
SocketTimeoutException: Read timed out
用记事本打开 android\gradle\wrapper\gradle-wrapper.properties (注意是两层子目录;Windows 隐藏扩展名后可能只显示成 gradle-wrapper):
bash
dir /s /b gradle-wrapper.properties
notepad D:\dev\projects\hello-app\android\gradle\wrapper\gradle-wrapper.properties
把 distributionUrl 整行换成:
properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
\:是 properties 的转义写法,照原样保留 ,别改成https://;其它行一律不动。- 备用:
https\://mirrors.huaweicloud.com/gradle/gradle-8.14.3-all.zip(腾讯云 / 华为云 / 阿里云均实测 200)。 .properties注释规则:行首#或!才是注释;禁行尾注释 (会被当成 URL 的一部分)、禁中文 (按 ISO-8859-1 读会乱码);别把distributionUrl注释掉。- 换 URL 后 wrapper 按 distributionUrl 字符串算缓存目录名,会新建目录重新下载,旧的
.part/.lck残留不用管。
两类下载要分清:
| Gradle 发行版 | 项目依赖(AGP / androidx / Kotlin) | |
|---|---|---|
| 来源 | distributionUrl(腾讯云镜像) |
dl.google.com / mavenCentral() |
| 落盘 | D:\dev\gradle-home\wrapper\dists\ |
D:\dev\gradle-home\caches\modules-2\ |
| 解决方式 | 改 gradle-wrapper.properties |
在 D:\dev\gradle-home\init.gradle 配仓库镜像(阿里云 `maven.aliyun.com/repository/google |
5.2 构建
bash
cd /d D:\dev\projects\hello-app\android
gradlew.bat assembleDebug --no-daemon
assembleDebug 是构建任务;wrapper 先检查本地有无 Gradle 8.14.3,没有则自动下载解压后才开始构建。不会装到系统、不写 PATH、不需要全局 Gradle。首次约 4--15 分钟(本机实测 BUILD SUCCESSFUL in 4m 11s,93 tasks executed),别强关。
缓存目录树:
text
D:\dev\gradle-home\
├─ wrapper\dists\gradle-8.14.3-all\<hash>\
│ ├─ gradle-8.14.3-all.zip
│ ├─ gradle-8.14.3\bin\gradle.bat
│ └─ gradle-8.14.3-all.zip.ok
├─ caches\modules-2\files-2.1\
└─ daemon\
5.3 产物
bash
D:\dev\projects\hello-app\android\app\build\outputs\apk\debug\app-debug.apk
bash
dir /s /b app\build\outputs\apk\*.apk
5.4 构建告警(全部无害,不要为它们改文件)
| 输出 | 性质 |
|---|---|
Using flatDir should be avoided... |
Capacitor 模板自带的 aar 仓库写法 |
使用或覆盖了已过时的 API(中文乱码) |
javac 的 deprecation 提示,乱码只是 JDK 日志编码与 CMD 代码页不匹配 |
single-use Daemon ... will be stopped |
--no-daemon 触发的一次性进程正常退出 |
Incubating Problem report... |
Gradle 8 新特性提示 |
5.5 装机与分发(可选)
bash
adb devices
adb install -r app\build\outputs\apk\debug\app-debug.apk
- 手机需开「开发者选项 → USB 调试」,首次连接弹「允许 USB 调试」。
List of devices attached为空 +no devices/emulators found只是没连设备,与出包完全无关 :assembleDebug是纯本地 Gradle 构建,全程不调用 adb。- debug 包带自动生成的 debug 签名,可以直接把文件发给别人安装;不可上架(商店要 AAB 且拒绝 debug 签名);换一台电脑重新打包后覆盖安装会报签名不一致,需先卸载。
- 拷出分发:
copy app\build\outputs\apk\debug\app-debug.apk D:\dev\hello-app-debug.apk
6. 日常最小循环与禁令
改了前端代码后,只需这三行(第二次起 Gradle 增量构建,快很多):
bash
cd /d D:\dev\projects\hello-app
npm run build && npx cap sync android
cd /d D:\dev\projects\hello-app\android && gradlew.bat assembleDebug --no-daemon
用绝对路径 cd /d ...\android 而非相对 cd android,避免同一窗口重复执行时「已经在 android 里」导致失败。
四条禁令
| 禁止 | 原因 |
|---|---|
重复 npx cap add android |
重建原生工程,覆盖 android\ 里的改动 |
npx cap open android |
会启动本机没装的 Android Studio |
npm i -g @capacitor/cli / 全局装 Gradle |
本地 npx + 项目 gradlew 已锁版本 |
改了配置却跳过 npm run build 直接 cap sync |
拷进去的还是旧产物 |
可选命令:
| 命令 | 用途 |
|---|---|
npx cap copy android |
只拷 web 资源 |
npx cap update android |
只更新原生插件 |
npx cap ls / npx cap doctor |
列平台 / 环境体检 |
gradlew.bat clean assembleDebug --no-daemon |
缓存异常时清理重来 |
gradlew.bat help --no-daemon |
秒级只解析构建脚本,验证 build.gradle 语法 |
gradlew.bat bundleRelease --no-daemon |
上架用 AAB |
7. release 签名包
7.1 两个密码是什么
release.jks 是一个「盒子」,盒子里装着 mykey 这把私钥:
| 口令 | 保护对象 | 填错时报错 |
|---|---|---|
storePassword(密钥库口令) |
打开整个 jks 文件 | Keystore was tampered with, or password was incorrect |
keyPassword(密钥口令) |
使用 mykey 私钥签名 |
Cannot recover key / Failed to read key |
一个 keystore 可放多把 alias 的密钥,所以设计了分层口令。但 JDK 21 的 keytool 默认生成 PKCS12,该格式不支持库口令与密钥口令不同 ;交互里 输入 <mykey> 的密钥口令 直接回车 = 与库密码相同。
结论:只需设一个强密码 ,keystore.properties 两行填同一个值即可 ------ Gradle 的 signingConfig 接口要求分别给出两个字段,不代表要两个不同密码。
7.2 生成 keystore(只做一次)
bash
D:\dev\jdk-21\bin\keytool.exe -genkeypair -v -keystore D:\dev\release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias mykey
交互顺序:密钥库口令 → 再输一次 → 姓名 / 组织 / 城市 / 省市 / 国家代码(可填 CN)→ y 确认 → 问「输入 <mykey> 的密钥口令」时直接回车。
- 一路回车那些 DN 字段会得到
CN=Unknown, OU=Unknown, ...,不影响安装、商店也不校验 DN 内容。想填真实信息可在发布前重新生成。 -validity 10000约 27 年。本机实测产物 2742 字节。- keystore 一旦分发/上架就绝不能换 ,否则老用户无法覆盖升级,只能换
appId重新上架。
7.3 keystore.properties(手动新建,不会自动生成)
用 notepad 打开不存在的文件,弹「找不到文件,是否创建?」点 是 ,粘内容后 Ctrl+S:
bash
notepad D:\dev\projects\hello-app\android\keystore.properties
properties
storeFile=D:/dev/release.jks
storePassword=<你设的密码>
keyAlias=mykey
keyPassword=<同一个密码>
- 必须放在
hello-app\android\(与gradlew.bat、settings.gradle同级),因为代码里rootProject.file("keystore.properties")的 rootProject 是android\这一层。放到项目根会报FileNotFoundException。 storeFile用正斜杠 ,.properties里反斜杠会被当转义符。- 密码别用
#、!、空格开头。 - 该文件含明文密码,不进版本库。
- 记事本「另存为」时保存类型选「所有文件」,别变成
keystore.properties.txt。 - 本文档不记录真实密码,请自行在密码管理器保存。
7.4 build.gradle 三处改动
先确认是 Groovy 版并备份:
bash
dir app\build.*
copy app\build.gradle app\build.gradle.bak
notepad app\build.gradle
groovy
// ① 文件最顶部,apply plugin 之前
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(rootProject.file("keystore.properties")))
// ② android { 块内、buildTypes 之前
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
// ③ 已有 buildTypes { release { ... } } 里加一行,其余 minifyEnabled 等保持不动
signingConfig signingConfigs.release
注释规则(三类文件不要混):
| 文件 | 语言 | 规则 |
|---|---|---|
build.gradle / .kts |
Groovy / Kotlin | 支持 //、/* */、行尾注释,中文一般也行(Gradle 8 按 UTF-8 编译、JDK 21 默认 UTF-8) |
*.properties |
Java Properties | 只能行首 # / !;禁行尾注释、禁中文 |
capacitor.config.json |
JSON | 完全不支持注释 |
改完先做秒级语法自检,不用等完整构建:
bash
gradlew.bat help --no-daemon
BUILD SUCCESSFUL 即脚本语法没问题;报 Could not compile build file 就把报错按 9 章排查。若实际是 build.gradle.kts,上述写法要换 Kotlin DSL(import java.util.Properties + val keystoreProperties = Properties().apply { load(...) } + signingConfigs { create("release") { ... } })。
7.5 .gitignore 追加两行
gitignore
android/keystore.properties
*.jks
7.6 出包
bash
cd /d D:\dev\projects\hello-app\android
gradlew.bat assembleRelease --no-daemon
产物:app\build\outputs\apk\release\app-release.apk
陷阱 :没接上 signingConfig 时 assembleRelease 也会 BUILD SUCCESSFUL,但产出 app-release-unsigned.apk,装不上。所以必须做下一步验签。
7.7 验签与输出解读
bash
dir /s /b app\build\outputs\apk\release\*.apk
D:\dev\Android\Sdk\build-tools\36.0.0\apksigner.bat verify --verbose --print-certs app\build\outputs\apk\release\app-release.apk
本机实测结果(判据):
| 输出 | 判定 |
|---|---|
Verifies |
签名完整、APK 未被篡改,可安装分发 |
Verified using v2 scheme: true |
主签名方案生效,Android 7.0+ 全支持 |
v1 scheme (JAR signing): false |
预期。minSdk 24,AGP 8 默认关闭 v1(v2 已覆盖所有可装设备) |
v3 / v3.1 / v4 / SourceStamp: false |
预期 。v3 用于密钥轮换、v4 是增量安装的 .idsig、SourceStamp 是 Play 溯源标记,普通 assembleRelease 不生成 |
Number of signers: 1 + RSA 2048 + 三种摘要齐全 |
证书就是 7.2 生成的 mykey,未混入 debug 签名 |
Signer #1 certificate DN: CN=Unknown, ... |
keytool 交互回车的默认值,功能无影响 |
apksigner 能打开 app-release.apk 本身就证明文件名不是 -unsigned,也证明 7.3 / 7.4 两步都做对了(Gradle 读不到凭据或没接 signingConfig,产不出这个文件)。
7.8 签名指纹基准(用于以后核对是否同一个 keystore)
后续每次发版若指纹一致,说明签名未变、可以覆盖升级。本机首次 release 包实测:
text
SHA-256: 3e89aeb68410e7f6639cdda03a3ac25e77495490209d846db74129af3435fab1
SHA-1 : be6f7c28f08488fe1c2efa0713ffd178c515948c
MD5 : 6e61c9579cd712dc97cb5cd93e39dfeb
7.9 发布注意
- 异地备份
D:\dev\release.jks+ 密码,两者缺一不可;密码不写进代码或文档。 - 每次发版把
app\build.gradle→defaultConfig→versionCode加 1。 - debug 包与 release 包签名不同,装过旧 debug 包的手机要先卸载再装,否则报签名冲突。
- 上架商店要 AAB 而非 APK:
gradlew.bat bundleRelease --no-daemon→app\build\outputs\bundle\release\app-release.aab。 - 以后出 release 包只需第 6 章那三行(第 3 行换成
assembleRelease),7.2--7.5 不再重复。
8. 换成自己的项目
分层原则:机器级装一次所有项目共用;项目级每个新项目要重做。
| 层级 | 换项目时 |
|---|---|
| JDK / SDK / 环境变量 / Gradle 缓存 / npm 镜像 | 完全不用动 |
Capacitor 三个 npm 包(node_modules 每项目独立) |
必须重新 npm i |
cap init 的 appId / appName / webDir |
换成真实值,appId 上架后不可改 |
cap add android |
每个项目只一次 |
vite.config 的 base |
按该项目路由情况决定(见 4.2) |
keystore.properties / build.gradle 签名配置 |
新项目重新做一遍 7.3--7.5;可复用同一个 release.jks(同签名才能升级) |
完整命令(D:\dev\projects\my-app 为示例):
bash
cd /d D:\dev\projects\my-app
npm install
npm i "@capacitor/core@^8" "@capacitor/android@^8"
npm i -D "@capacitor/cli@^8"
npx cap init "真实应用名" "com.company.myapp" --web-dir=dist
npm run build
npx cap add android
npx cap sync android
cd /d D:\dev\projects\my-app\android
gradlew.bat assembleDebug --no-daemon
--web-dir=dist 按 4.5 对照表换成实际目录。项目路径保持纯英文;中文路径构建失败时可在 android\gradle.properties 加 android.overridePathCheck=true。
9. 排错速查总表
| 现象 | 处理 |
|---|---|
echo %VAR% 输出字面量 |
重开终端(环境变量快照) |
requires Java 17 / 无效的源发行版: 21 |
JAVA_HOME 未生效或指向 17,where java 核对并上移 Path |
npx.ps1 cannot be loaded |
PowerShell 里 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,或改用 CMD |
sdkmanager 不是内部命令 |
检查 cmdline-tools\latest\bin 层级 / 重开终端 |
platforms is not a valid package |
换 CMD + 双引号 |
licenses not accepted |
补 sdkmanager --licenses |
platforms;android-36 找不到 |
用最新 commandlinetools 重新归位;sdkmanager --list 查可用包 |
@^8 被吞成 @8 |
CMD 里加双引号 |
@capacitor/cli 404 |
npmmirror 同步滞后,切 registry.npmjs.org 重装 |
Parsing capacitor.config.ts failed |
老项目缺 "type": "module",或改用 .json;别同时保留两份 |
| 启动白屏 | webDir 与构建产物目录不一致 / 忘了 npm run build / 忘了 cap sync;再查 base |
Could not determine SDK root / SDK location not found |
核对 ANDROID_HOME;或在 android\local.properties 写 sdk.dir=D:/dev/Android/Sdk(正斜杠) |
Downloading ...gradle-8.14.3-all.zip failed / Read timed out |
改 distributionUrl 为腾讯云镜像(5.1) |
| Maven 依赖阶段超时 | 在 D:\dev\gradle-home\init.gradle 配仓库镜像,不要改项目 build.gradle |
FINDSTR: 无法打开 app\build.gradle |
CMD 停在项目根了;写 android\app\build.gradle 或先 cd /d ...\android |
FileNotFoundException: keystore.properties |
文件放错层(应在 android\)或存成了 .txt |
Keystore was tampered with, or password was incorrect |
storePassword 错 |
Cannot recover key |
keyPassword 错,或 keyAlias 不是 mykey |
产物是 app-release-unsigned.apk |
signingConfig 没接进 buildTypes.release |
apksigner 报 DOES NOT VERIFY |
跑了缓存旧包,gradlew.bat clean assembleRelease --no-daemon |
adb: no devices/emulators found |
只是没连设备,与出包无关,可忽略 |
| 中文路径构建失败 | android\gradle.properties 加 android.overridePathCheck=true |
| 覆盖安装报签名不一致 | debug / release 签名不同,或换了 keystore;先卸载旧版 |
10. 附录
10.1 环境变量速查
bash
JAVA_HOME = D:\dev\jdk-21
ANDROID_HOME = D:\dev\Android\Sdk
GRADLE_USER_HOME = D:\dev\gradle-home
Path 追加:
%JAVA_HOME%\bin
%ANDROID_HOME%\cmdline-tools\latest\bin
%ANDROID_HOME%\platform-tools
10.2 关键路径速查
| 用途 | 路径 |
|---|---|
| sdkmanager | D:\dev\Android\Sdk\cmdline-tools\latest\bin\sdkmanager.bat |
| apksigner | D:\dev\Android\Sdk\build-tools\36.0.0\apksigner.bat |
| keytool | D:\dev\jdk-21\bin\keytool.exe |
| Gradle wrapper 配置 | android\gradle\wrapper\gradle-wrapper.properties |
| 项目配置 | hello-app\capacitor.config.json |
| 签名参数 | hello-app\android\keystore.properties |
| debug APK | android\app\build\outputs\apk\debug\app-debug.apk |
| release APK | android\app\build\outputs\apk\release\app-release.apk |
| release AAB | android\app\build\outputs\bundle\release\app-release.aab |
10.3 完整命令流水(新机复现用,按序执行)
bash
REM ---- 1. 安装三个 msi/exe(图形向导,见 1.1--1.3) ----
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
REM ---- 2. 配 1.5 的环境变量,关掉所有终端重开 ----
java -version
node -v
git --version
where java
echo %ANDROID_HOME%
REM ---- 3. SDK(commandlinetools 归位后,CMD) ----
sdkmanager --sdk_root=D:\dev\Android\Sdk "platform-tools" "platforms;android-36" "build-tools;36.0.0"
sdkmanager --sdk_root=D:\dev\Android\Sdk --licenses
adb version
sdkmanager --sdk_root=D:\dev\Android\Sdk --list_installed
REM ---- 4. npm ----
npm config set registry https://registry.npmmirror.com
npm config set cache D:\dev\npm-cache
REM ---- 5. 项目 ----
cd /d D:\dev\projects
npm create vite@latest hello-app -- --template vue
cd hello-app
npm install
REM 手工在 vite.config.js 加 base: './',
npm i "@capacitor/core@^8" "@capacitor/android@^8"
npm i -D "@capacitor/cli@^8"
npx cap init "HelloApp" "com.example.helloapp" --web-dir=dist
npm run build
npx cap add android
npx cap sync android
REM ---- 6. 改 gradle-wrapper.properties 的 distributionUrl(见 5.1) ----
cd /d D:\dev\projects\hello-app\android
gradlew.bat assembleDebug --no-daemon
dir /s /b app\build\outputs\apk\*.apk
REM ---- 7. release 签名(见 7.2--7.5,含手工编辑) ----
gradlew.bat assembleRelease --no-daemon
D:\dev\Android\Sdk\build-tools\36.0.0\apksigner.bat verify --verbose --print-certs app\build\outputs\apk\release\app-release.apk
10.4 已知限制
- debug 包带本机 debug 签名,可直接安装、不可上架;换电脑重打包覆盖安装会签名冲突。
- release 包需 keystore +
signingConfigs,未签名产物装不上;keystore 丢失无法给老用户升级。 - 商店上架要
.aab(bundleRelease),不是 APK。 - iOS 无法在 Windows 编译,需 macOS 或云构建。
- 未连真机时无法验证实际显示效果(白屏与否),
apksigner verify只能确认签名有效。 - 项目路径须纯英文;中文路径需
android.overridePathCheck=true兜底。 - 本文档不记录任何真实密码;keystore 与密码请自行异地备份。