Repository Wiki
LYOfficial/OneDocs

Android 构建与跨平台发布

本页说明 OneDocs 基于 Tauri v2 的 Android 构建链路:从前置工具链、环境变量和 Rust Android targets,到本地 APK 构建、ABI 拆分、签名配置,以及前端针对 Android WebView 的构建目标适配。

Purpose and Scope

本页聚焦 Android 构建与跨平台发布所需的开发者流程和仓库内已声明的构建入口,适合需要在本地生成 APK、准备发布签名,或理解 Android 与桌面端前端产物差异的开发者。

覆盖范围包括:

  • JDK 17、Android SDK/NDK 和 Rust Android targets 的准备;
  • package.json 中的 Android 初始化、开发、通用构建和 ABI 拆分命令;
  • Vite 根据 TAURI_ENV_PLATFORM 和 VITE_BUILD_TARGET 选择 Android/桌面编译目标的机制;
  • 本地签名所需的 keystore、keystore.properties 和 CI Secrets 约定;
  • APK 输出位置、首次构建成本和发布相关注意事项。

桌面端 Tauri 构建、应用功能本身、前端业务模块和完整 CI workflow 的实现不在本页展开;这里仅说明它们与 Android 发布链路直接相关的连接点。仓库的 Android 操作手册位于 docs/BUILD_ANDROID.md,脚本入口位于 package.json。

Overview

OneDocs 使用 Tauri v2 将前端应用包装为 Android 应用。Android 构建不是单一的 Vite 打包:它同时依赖 Java/Gradle、Android SDK/NDK、Rust 的 Android 交叉编译 targets,以及 Tauri CLI 生成和构建 Android 项目的能力。

仓库将构建职责分成两层:

  1. 前端构建层:Vite 负责解析 React 应用、生成 dist,并根据平台选择 JavaScript 编译目标。Android 默认使用 es2021 和 chrome70,而非 Android 之外默认的 esnext、chrome120 和 safari17。
  2. Tauri Android 层:tauri android init 初始化 Android 工程;tauri android dev 启动 Android 开发模式;tauri android build 负责生成 APK;追加 --split-per-abi 时按 ABI 拆分 APK。

这种分层的关键原因是 Android WebView 的运行时能力不同于现代桌面浏览器。vite.config.ts 明确指出较旧 Android 设备不支持 esnext/chrome120,因此平台默认 target 降级为 es2021/chrome70。同时,VITE_BUILD_TARGET 可以显式覆盖默认选择,支持单个 target 或逗号分隔的多个 target。

Architecture

Loading diagram...

图中的关系来自仓库实际脚本和配置:package.json 暴露 Tauri Android 命令;vite.config.ts 负责前端 target、React 插件、产物目录和 PDF.js 分块;Android 指南定义 JDK、SDK、NDK、Rust targets 和输出目录。tauri android init 先生成 src-tauri/gen/android,之后构建流程使用该 Android 工程和前端产物。

Sources:

构建前置条件与环境

JDK 与 Gradle 兼容性

Android 指南要求使用 JDK 17。文档同时说明项目使用 Gradle 8.9,Java 21 及以上版本可能导致 Unsupported class file major version 70 一类的 class-file 版本错误。因此,不能只设置 JAVA_HOME:如果系统 PATH 的前部仍然指向其他 Java 版本,Gradle 可能继续使用错误的运行时。正确做法是让 JDK 17 的 bin 位于 PATH 前部。

仓库文档给出的 Windows 环境变量约定是:

变量示例/要求用途
JAVA_HOMEJDK 17 安装目录指定 Java 工具链
ANDROID_HOME%LOCALAPPDATA%\\Android\\Sdk指定 Android SDK
NDK_HOME$ANDROID_HOME\\ndk\\27.0.12077973指定 Android NDK

如果已安装 Android Studio,也可以使用其内置 JBR 作为 JAVA_HOME;但文档仍然强调实际构建时必须确保使用的是兼容的 Java 17 运行时。

Rust Android targets

本地构建前需要安装四个 Rust 目标:aarch64-linux-android、armv7-linux-androideabi、i686-linux-android 和 x86_64-linux-android。这组 targets 对应 Android 构建中常见的 ARM64、ARMv7、x86 和 x86_64 架构;如果只验证 ARM64,可以按照文档建议使用 --target aarch64 缩短首次构建时间,但仓库本身的默认脚本仍然提供通用构建和 ABI 拆分两种入口。

bash
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android

Source: BUILD_ANDROID.md

脚本入口与实际构建流程

package.json 将 Tauri CLI 命令封装为 npm scripts,避免开发者直接记忆完整的 CLI 调用:

json
1"scripts": { 2 "android:init": "tauri android init", 3 "android:dev": "tauri android dev", 4 "android:build": "tauri android build", 5 "android:build:split": "tauri android build -- --split-per-abi", 6 "android:build:signed": "tauri android build && node scripts/rename-apk.mjs" 7}

Source: package.json

这些命令的职责不同:

  • npm run android:init:只需初始化一次,用于生成 Android 工程;
  • npm run android:dev:进入 Android 开发模式,适合联调;
  • npm run android:build:执行通用 Android 构建;
  • npm run android:build:split:向 Tauri 构建命令传入 --split-per-abi,生成按 ABI 拆分的 APK,单个文件体积更小;
  • npm run android:build:signed:先执行 Tauri 构建,再调用仓库中的 APK 重命名脚本。该脚本在 package.json 中被引用,但其实现未在本页已读取的源材料中展开,因此不能据此推断重命名规则。

典型生命周期是“初始化 → 配置签名 → 开发验证 → 构建发布”:

Loading diagram...

Sources:

前端跨平台编译目标

vite.config.ts 先读取 VITE_BUILD_TARGET,再判断 TAURI_ENV_PLATFORM 是否为 android。选择顺序如下:

  1. 如果设置了非空的 VITE_BUILD_TARGET 且不包含逗号,直接使用该字符串;
  2. 如果包含逗号,按逗号分割、去除空白并过滤空项,形成 target 数组;
  3. 没有显式 target 且平台为 Android 时,使用 ["es2021", "chrome70"];
  4. 其他平台默认使用 ["esnext", "chrome120", "safari17"]。

这意味着显式 VITE_BUILD_TARGET 会覆盖 Android 的安全默认值。修改该变量时,应确认目标 Android WebView 支持对应语法和浏览器特性,否则可能把前端编译成设备无法执行的代码。

除 target 选择外,Android/桌面共用以下 Vite 行为:使用 React 插件;通过 @ alias 指向 src;将输出写入 dist;在非 TAURI_DEBUG 模式启用 esbuild 压缩,在调试模式生成 sourcemap;并把 pdfjs-dist 放入名为 pdfjs 的 manual chunk。这里的 PDF.js 分块是构建产物组织策略,不是 Android 专属逻辑。

typescript
1const buildTargetEnv = process.env.VITE_BUILD_TARGET?.trim(); 2const isAndroid = process.env.TAURI_ENV_PLATFORM === "android"; 3const buildTarget = (() => { 4 if (buildTargetEnv) { 5 if (!buildTargetEnv.includes(",")) { 6 return buildTargetEnv; 7 } 8 return buildTargetEnv.split(",").map((item) => item.trim()).filter(Boolean); 9 } 10 11 // Android WebView on older devices doesn't support esnext/chrome120 12 // Chrome 70 covers Android 10+; es2021 is widely supported 13 if (isAndroid) { 14 return ["es2021", "chrome70"]; 15 } 16 17 return ["esnext", "chrome120", "safari17"]; 18})();

Source: vite.config.ts

签名配置与发布安全

发布 APK 必须签名。仓库指南把签名分为一次性密钥生成、本地构建配置和 CI Secrets 三个步骤。

生成 keystore

文档要求使用 JDK 17 提供的 keytool,在项目根目录生成 upload-keystore.jks,别名为 upload,RSA 密钥长度为 2048,有效期为 10000 天。该文件应妥善保管且不能提交到版本控制。CI 场景下,文档给出将 keystore 转为 Base64 的方式,以便存入 GitHub Actions Secrets。

本地 keystore.properties

本地构建前需要在 src-tauri/gen/android 下生成 keystore.properties,其中包含 keyAlias、password 和 storeFile。指南特别给出以无 BOM UTF-8 写入该文件的 PowerShell 示例,原因是 Gradle 属性文件需要稳定的编码和路径值。storeFile 可以是 keystore 的绝对路径。

配置项

配置项类型默认值/状态说明
ANDROID_KEY_BASE64Secret/string无,发布 CI 时配置Base64 编码的 keystore 内容
ANDROID_KEY_ALIASSecret/string文档示例为 uploadkeystore 中使用的 alias
ANDROID_KEY_PASSWORDSecret/string无,使用者设置keystore 密码
ANDROID_KEYSTORE_PATH.env 配置示例为 upload-keystore.jks本地 keystore 路径
keyAliaskeystore.properties 属性uploadAndroid Gradle 签名 alias
passwordkeystore.properties 属性使用者设置Android Gradle 签名密码
storeFilekeystore.properties 属性使用者路径keystore 文件路径

.env 中的前三项用于本地/CI 约定,GitHub Actions Secrets 至少需要 ANDROID_KEY_BASE64、ANDROID_KEY_ALIAS 和 ANDROID_KEY_PASSWORD;ANDROID_KEYSTORE_PATH 是指南列出的 .env 路径配置。仓库文档没有在已读取材料中给出 workflow 如何消费这些变量的逐步实现,因此 CI 内部解码和注入细节应以实际 workflow 为准。

产物、性能与运维注意事项

  • APK 产物位于 src-tauri/gen/android/app/build/outputs/apk/;构建完成后应从该目录检查实际文件。
  • android:build:split 通过 ABI 拆分降低单个 APK 体积,但分发时需要根据设备架构选择对应文件;通用构建更适合需要一个覆盖多架构入口的场景。
  • 首次构建需要下载 Gradle 依赖并编译 Rust 交叉目标,耗时会明显高于后续构建。优先只构建 aarch64 可以缩短验证周期,但不能替代完整多架构发布验证。
  • Android 默认的 es2021/chrome70 target 是兼容性策略。若通过 VITE_BUILD_TARGET 覆盖它,应在目标 Android WebView 上验证启动、路由、Markdown/PDF 等关键功能。
  • 签名密钥是发布身份的一部分。丢失 keystore 或密码会影响后续同一应用的发布更新;不要把 .jks、密码或未脱敏的 keystore.properties 提交到仓库。

Failure Modes、边界条件与扩展点

常见失败模式

  1. Java 版本错误:JDK 不是 17,或 PATH 中其他 Java 优先级更高,可能在 Gradle 阶段出现 class-file major version 错误。修复时同时检查 JAVA_HOME 和 PATH。
  2. 工具链变量缺失:ANDROID_HOME 或 NDK_HOME 未指向实际安装目录时,Tauri/Gradle 无法定位 Android 编译工具。
  3. Rust target 缺失:目标架构未通过 rustup target add 安装时,对应 ABI 的 Rust 部分无法交叉编译。
  4. 签名文件缺失或编码不正确:没有 keystore.properties、路径错误、密码不匹配,或文件编码不符合预期,会使签名构建失败。
  5. 前端 target 过新:显式覆盖 Android target 后,旧 WebView 可能无法解析产物。该风险来自配置中的平台兼容性注释和默认 target 设计。

并发与一致性

已读取的 Android 构建文档和构建配置没有实现共享状态、后台队列或并发锁;因此本页无法声称构建过程具有额外的并发协调机制。实际构建主要依赖 Gradle、Rust 和 Tauri 工具链自身的缓存与任务调度。发布时应避免多个任务同时写入同一个生成目录或签名配置文件,以免产生难以复现的中间产物问题。

扩展点

  • 通过新增或调整 package.json scripts,可以封装新的 Tauri CLI 参数;
  • 通过 VITE_BUILD_TARGET 可以为特定设备/运行时显式选择 Vite target;
  • 通过 Tauri CLI 的 Android 参数可以扩展构建模式,但应保持与 vite.config.ts 的平台判断一致;
  • vite.config.ts 的 manualChunks 可继续承载大型依赖的拆分策略,但这属于通用前端构建优化,不应与签名流程混合。

API Reference

本页主题没有 HTTP API。可操作的公开入口是 npm scripts:

命令参数行为返回/产物
npm run android:init无初始化 Tauri Android 工程src-tauri/gen/android
npm run android:dev无启动 Android 开发模式运行中的 Android 开发应用
npm run android:build无构建通用 APKsrc-tauri/gen/android/app/build/outputs/apk/ 下的 APK
npm run android:build:split内部传入 --split-per-abi按 ABI 拆分 APK按架构区分的 APK
npm run android:build:signed无构建后执行 scripts/rename-apk.mjs重命名行为取决于该脚本实现

配置覆盖关系与发布检查清单

Vite 的 target 选择存在明确的覆盖优先级:显式 VITE_BUILD_TARGET 最高;没有显式值时,Android 平台使用兼容性默认值;非 Android 平台使用桌面默认值。这个优先级比“按平台硬编码”更灵活,但也意味着发布脚本或 CI 若注入了 VITE_BUILD_TARGET,可能改变 Android 默认兼容策略。

Loading diagram...

Source: vite.config.ts

正式发布前可按以下顺序检查:

  1. 确认 JAVA_HOME 和 PATH 都指向 JDK 17;
  2. 确认 ANDROID_HOME、NDK_HOME 可访问,且 NDK 版本与本地环境一致;
  3. 确认需要发布的 Rust Android targets 已安装;
  4. 首次使用时执行 npm run android:init;
  5. 本地签名构建前确认 src-tauri/gen/android/keystore.properties 的 alias、密码和路径有效;
  6. 先用 npm run android:dev 验证 Android 运行时,再执行通用或 ABI 拆分构建;
  7. 从 src-tauri/gen/android/app/build/outputs/apk/ 检查 APK,并根据分发策略选择通用或 ABI 拆分产物;
  8. CI 发布时只通过 Secrets 提供签名材料,不把 keystore 或密码写入版本库。

如果构建失败但不属于上述工具链、target 或签名问题,当前已读取的源材料不足以确定具体原因;应继续检查 Tauri 生成的 Android 工程、Gradle 日志以及仓库中实际的 workflow 实现。