不依赖 Android Studio,在 WSL 命令行环境下,从零安装依赖到生成 100% 离线可用 APK 的完整过程,含全部报错与核心架构解决方案。
适用栈: Next.js (App Router) + Cloudflare Workers + Cloudflare D1 + Electron + Capacitor + Dexie (IndexedDB)
背景与架构演进
Electron 只支持 Windows / macOS / Linux 桌面,无法打包安卓。为了适配安卓,我们选用了 Capacitor。
- 旧方案(远程 URL 套壳): APK 只是一个 WebView,加载
https://note.yysresume.work。缺点是断网直接打不开。 - 新方案(完美本地离线): 将 Cloudflare 构建的静态产物(
.vercel/output/static)直接打入 APK。数据全存在手机本地 IndexedDB (Dexie),断网随意读写,联网手动触发增量同步。彻底实现全平台一套代码完全离线可用,且不影响 Electron 的打包逻辑。
核心避坑:为什么不用 output: export?为什么会报 React Error #418?
- 为什么不用
output: export?
项目有 API Route(如/api/login),Next.js 静态导出不支持动态路由和 API。所以我们直接利用@cloudflare/next-on-pages生成的静态目录.vercel/output/static作为安卓的本地根目录。 - 致命的 React Error #418 (水合不匹配):
在 Next.js App Router 中,如果你使用物理路径(如/settings或/notebook/[id]),本地离线的 Capacitor 找不到这个文件夹,会触发 fallback 返回首页的index.html。这会导致浏览器地址栏是/settings,但内容是首页,React 状态瞬间错乱崩溃卡死。 - 终极解法:纯单页(SPA)参数路由:
将所有<Link href="/settings">改为<Link href="/?page=settings">。所有路由流转全部通过 URL 查询参数(?notebookId=xxx¬eId=yyy)实现。这样 Capacitor 永远只加载index.html,彻底解决 418 报错!
第一步:修改 Next.js 路由架构(实现纯前端 SPA)
在打包安卓前,必须将业务代码中的路径跳转改为参数跳转。
以 MainLayout.tsx 和 page.tsx 为例:
- 获取路由参数:
将useParams和usePathname全部替换为useSearchParams。const searchParams = useSearchParams() const noteId = searchParams.get('noteId') ?? undefined const notebookId = searchParams.get('notebookId') ?? undefined const page = searchParams.get('page') - 修改页面跳转:
// 错误做法(导致本地离线崩溃): // router.push(`/notebook/${nb.id}`) // 正确做法(完美单页路由): router.push(`/?notebookId=${nb.id}`) - 设置路由调度中心: 在
app/page.tsx中根据searchParams渲染不同的页面组件。
第二步:安装与初始化 Capacitor
需要安装三个包,缺一不可:
npm install @capacitor/core @capacitor/android
npm install @capacitor/cli --save-dev
(常见错误:直接 npx cap init 报 could not determine executable to run,是因为 cli 没有单独安装。)
初始化:
npx cap init
- Name:
MyCloudNote - Package ID:
work.yysresume.note(与 electron-builder 的 appId 保持一致)
第三步:配置纯本地静态模式
修改生成的 capacitor.config.ts,指向 Cloudflare 的打包产物目录:
import { CapacitorConfig } from '@capacitor/cli'
const config: CapacitorConfig = {
appId: 'work.yysresume.note'
appName: 'MyCloudNote'
// ✅ 1. 指向 Cloudflare 的构建输出目录
webDir: '.vercel/output/static'
server: {
// ✅ 2. 启用本地协议
androidScheme: 'http'
// ✅ 3. 防止 SPA 路由刷新白屏的兜底策略
errorPath: 'index.html'
}
}
export default config
第四步:添加安卓平台并同步
先执行网页构建,生成静态文件,然后再同步给安卓项目:
npm run build:web
npx cap add android
npx cap sync android
cap sync 成功输出:
✔ copy android in 29.98ms
✔ update android in 73.66ms
[info] Sync finished in 0.156s
(建议将这三步集成到 package.json 的 build:android 脚本中)。
第五步:安装 Android SDK(WSL 命令行工具)
- 安装 unzip:
WSL 最小安装不包含 unzip:sudo apt install unzip -y - 下载并解压命令行工具:
cd ~ wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip unzip commandlinetools-linux-11076708_latest.zip -d android-sdk - 整理目录结构(极其重要):
解压后需手动移到正确子目录,否则sdkmanager会报找不到自身:mkdir -p android-sdk/cmdline-tools/latest mv android-sdk/cmdline-tools/bin android-sdk/cmdline-tools/latest/ mv android-sdk/cmdline-tools/lib android-sdk/cmdline-tools/latest/ mv android-sdk/cmdline-tools/NOTICE.txt android-sdk/cmdline-tools/latest/ 2>/dev/null || true mv android-sdk/cmdline-tools/source.properties android-sdk/cmdline-tools/latest/ 2>/dev/null || true - 安装 SDK 组件:
运行 sdkmanager,过程中输入y同意许可协议:~/android-sdk/cmdline-tools/latest/bin/sdkmanager "platformsandroid-34" "build-tools34.0.0" - 写入 SDK 路径给项目:
echo "sdk.dir=/home/yys/android-sdk" > /home/yys/note/android/local.properties
第六步:安装 JDK 21
Capacitor 最新版要求 Java 21,JDK 17 或更低版本会报 invalid source release: 21。
sudo apt install openjdk-21-jdk -y
# 如果系统有多个 JDK,切换到 21
sudo update-alternatives --config java
# 验证
java -version # 应输出: openjdk version "21.x.x"
第七步:解决 Gradle 下载超时(替换镜像)
国内网络从 services.gradle.org 下载 Gradle 极易超时。
注意:直接用 sed 替换会因 \: 转义静默失败,必须用 Python 脚本安全替换!
cd /home/yys/note/android
python3 -c "
content = open('gradle/wrapper/gradle-wrapper.properties').read()
content = content.replace(
'https://services.gradle.org/distributions/gradle-8.14.3-all.zip'
'https://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip'
)
open('gradle/wrapper/gradle-wrapper.properties' 'w').write(content)
print('Gradle mirror updated!')
"
验证: cat gradle/wrapper/gradle-wrapper.properties,确认地址已变为 mirrors.cloud.tencent.com。
第八步:构建纯离线 APK
cd /home/yys/note/android
./gradlew assembleDebug
首次构建会自动下载缺失的组件,成功输出:
BUILD SUCCESSFUL in 57s
93 actionable tasks: 56 executed 37 up-to-date
找到并提取 APK:
# APK 路径
ls app/build/outputs/apk/debug/app-debug.apk
# 复制到 Windows 桌面方便传输
cp app/build/outputs/apk/debug/app-debug.apk /mnt/c/Users/yys/Desktop/
终极问题排查汇总表
| 报错现象 | 原因分析 | 解决方案 |
|---|---|---|
| Minified React Error #418 (点菜单跳回首页/白屏卡死) | Android 没有 Node.js,Capacitor 无法处理 Next.js 的物理动态路由 (/settings),导致前后端 HTML 结构不匹配 (Hydration 失败)。 |
将路由全面改为参数查询模式 (/?page=settings),让应用成为纯正的 SPA 单页应用。 |
| could not determine executable to run | 全局缺少 @capacitor/cli |
npm install @capacitor/cli --save-dev |
| SDK location not found | Android 目录缺少 local.properties 指明 SDK 位置 |
echo "sdk.dir=/home/yys/android-sdk" > android/local.properties |
| invalid source release: 21 | 系统的 JDK 版本低于 21 | sudo apt install openjdk-21-jdk -y 并切换 |
| Gradle 下载卡死 / 超时 | 国内访问 gradle.org 被墙或限速 |
使用 Python 脚本替换 gradle-wrapper.properties 为腾讯云镜像,避开 sed 的转义 bug。 |
| unzip: command not found | WSL 纯净版未预装 unzip 工具 | sudo apt install unzip -y |
进阶篇:个性化定制 —— 修改 App 名称版本号与图标生成
在完成了“让代码跑起来”的硬核攻关后,最后一步就是让它看起来像一个真正的“产品”。默认的 Capacitor 图标和 "MyCloudNote" 显然不够个性,我们需要换上那张酷炫的非主流风格的“小杨云笔记”专属图标。
第九步:生成全套安卓图标(Asset 自动化)
由于安卓系统碎片化严重,需要多套不同分辨率的图片。手动裁剪是极低效的,我们直接使用 Capacitor 官方的资产生成工具,它能完美处理你那张 1254x1254 的高清大图。
-
准备原始素材:
在项目根目录创建assets文件夹,将你的高清图标放入其中,命名为icon.png。mkdir -p assets # 将 1254x1254 的图片放入 assets/icon.png -
安装资产工具:
npm install @capacitor/assets --save-dev -
一键生成安卓资源:
该工具会自动将你的icon.png缩放裁剪并分发到安卓工程的所有mipmap文件夹中:npx capacitor-assets generate --android注:即便原图是 1254x1254,工具也会自动处理。由于原图带有黑色留白,生成的图标在桌面会有天然的“安全区域”,不会被圆角裁剪掉文字。
第十步:修改 App 桌面显示名称
App 图标下面的文字默认是 MyCloudNote,我们需要将其改为中文的 小杨云笔记。
- 修改文件:
android/app/src/main/res/values/strings.xml - 修改方法:
<?xml version=Ƈ.0' encoding='utf-8'?> <resources> <string name="app_name">小杨云笔记</string> <!-- 修改这里 --> <string name="title_activity_main">小杨云笔记</string> <!-- 修改这里 --> <string name="package_name">work.yysresume.note</string> <string name="custom_url_scheme">work.yysresume.note</string> </resources>
第十一步:同步版本号(Version Management)
为了方便后续维护,安卓的版本号应与 package.json 保持同步。
- 修改文件:
android/app/build.gradle - 关键参数:
versionCode:整数,内部版本标识,每次发布新包必须递增。versionName:字符串,展示给用户的版本号。
android {
defaultConfig {
applicationId "work.yysresume.note"
// ...
versionCode 101017 // 建议去掉点号,如 10.10.17 对应 101017
versionName "10.10.17" // 与 package.json 保持一致
// ...
}
}
第十二步:最终构建与效果验证
完成上述个性化设置后,执行最后的同步与打包:
# 1. 同步设置到安卓工程
npx cap sync android
# 2. 进入安卓目录编译调试版 APK
cd android
./gradlew assembleDebug
此时生成的 app-debug.apk 已经拥有了:
- 非主流涂鸦风格图标:1254px 高清缩放,在手机桌面极具辨识度。
- 中文名称:桌面显示为“小杨云笔记”。
- 正确版本号:在手机“应用详情”中可见。
写在最后:从 Web 到原生
通过这套方案,我们利用一套代码(Next.js)同时喂饱了 Cloudflare Workers (Web端)Electron (桌面端) 和 Capacitor (移动端)。最核心的科技点不在于打包工具,而在于单页路由(SPA)的改造和本地 IndexedDB (Dexie) 的持久化策略。这让“小杨云笔记”真正做到了“云端是同步,本地是核心”。
评论区(0 条)
发表评论⏳ 加载编辑器…