Tauri Android 打包原理与实战指南

0 阅读5分钟

Tauri Android 打包原理与实战指南

基于 JoyaLand 项目的实际打包经验整理,记录原理、流程与踩坑解决方案。


一、Tauri Android 打包架构原理

1.1 整体架构

┌─────────────────────────────────────────────┐
│              JoyaLand Android App            │
├──────────────────┬──────────────────────────┤
│   前端层 (WebView) │   原生层 (Rust/JNI)      │
│  ┌────────────┐  │  ┌────────────────────┐  │
│  │ HTML/CSS   │  │  │  tauri-android     │  │
│  │ JavaScript │◄─┼─►│  (Kotlin/Java)     │  │
│  │ Canvas 2D  │  │  ├────────────────────┤  │
│  └────────────┘  │  │  libapp_lib.so     │  │
│                  │  │  (Rust 编译产物)    │  │
└──────────────────┴──────────────────────────┘
         ▲
         │ Vite 构建的静态资源
         │ (dist/ 目录)

Tauri Android 的核心思路:

  • 前端:使用系统 WebView 渲染 HTML/JS/CSS(无需打包 Chromium)
  • 后端:Rust 代码编译为 .so 动态库,通过 JNI 被 Kotlin/Java 调用
  • 桥接:Tauri 的 IPC 机制连接前后端(命令调用、事件通知)

1.2 构建工具链

Vite (前端构建)
    ↓ 生成 dist/
Cargo (Rust 编译)
    ↓ 交叉编译 4 个 ABI 的 .so 文件
Gradle (Android 构建)
    ↓ 打包 APK/AAB
apksigner (APK 签名)
    ↓
最终 APK

1.3 支持的 Android ABI

ABIRust Target对应设备
arm64-v8aaarch64-linux-android现代 64 位 ARM 手机(主流)
armeabi-v7aarmv7-linux-androideabi旧款 32 位 ARM 手机
x86i686-linux-android模拟器(32 位)
x86_64x86_64-linux-android模拟器(64 位)/ 部分平板

二、环境准备

2.1 必要工具

工具作用安装来源
Rust + Cargo编译原生代码rustup.rs
Android SDKAndroid 构建工具Android Studio / 命令行
Android NDK交叉编译工具链SDK Manager
JDK 8+Gradle 运行环境Oracle / OpenJDK
GradleAndroid 构建系统gradle.org 或系统安装
Node.js + npm前端依赖管理nodejs.org

2.2 Rust Android 编译目标

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

2.3 环境变量

# Windows PowerShell
$env:ANDROID_HOME = "D:\Android\Sdk"
$env:PATH += ";D:\Android\Sdk\platform-tools"

三、打包完整流程

3.1 初始化 Android 项目

npx tauri android init

执行后会:

  1. 检测 ANDROID_HOME 环境变量,找到 SDK 路径
  2. 自动检测并使用已安装的 NDK(如 D:\Android\Sdk\ndk\29.0.x
  3. 安装 4 个 Android Rust 编译目标(如未安装)
  4. src-tauri/gen/android/ 生成 Android Gradle 项目

生成的目录结构:

src-tauri/gen/android/
├── app/
│   ├── build.gradle.kts
│   ├── src/main/
│   │   ├── java/com/liupe/joyaland/generated/  ← Tauri 自动生成的 Kotlin 桥接代码
│   │   └── jniLibs/                            ← 编译好的 .so 文件(symlink)
│   └── proguard-rules.pro
├── buildSrc/                                   ← 包含自定义 BuildTask(调用 Cargo)
├── gradle/wrapper/
│   └── gradle-wrapper.properties               ← Gradle 版本配置
├── gradlew.bat                                 ← Gradle Wrapper 启动脚本
└── settings.gradle

3.2 构建 APK

$env:ANDROID_HOME = "D:\Android\Sdk"
npx tauri android build --apk

构建过程分为以下阶段:

阶段 1:前端构建
npm run build → Vite → dist/
  • 执行 tauri.conf.json 中配置的 beforeBuildCommand
  • 生成静态资源到 dist/ 目录
阶段 2:Rust 交叉编译(最耗时)
Cargo → 4 个 .so 文件
  • 为每个 ABI 分别运行 cargo build --release --target <abi>
  • 使用 NDK 中的交叉编译工具链(如 aarch64-linux-android24-clang
  • 首次编译需要 20-60 分钟,后续增量编译 1-5 分钟

编译产物位置:

src-tauri/target/aarch64-linux-android/release/libapp_lib.so
src-tauri/target/armv7-linux-androideabi/release/libapp_lib.so
src-tauri/target/i686-linux-android/release/libapp_lib.so
src-tauri/target/x86_64-linux-android/release/libapp_lib.so
阶段 3:Gradle 打包
gradlew assembleUniversalRelease → APK
  • Gradle 调用 buildSrc 中的 BuildTask 触发各 ABI 的 Rust 编译
  • Kotlin 代码编译(Tauri 桥接层)
  • 资源合并、R8 代码压缩混淆
  • 打包为 APK

输出位置:

src-tauri/gen/android/app/build/outputs/apk/universal/release/
    app-universal-release-unsigned.apk
阶段 4:APK 签名
# 1. 生成签名密钥(只需一次)
keytool -genkey -v -keystore joyaland-release.keystore `
  -alias joyaland -keyalg RSA -keysize 2048 -validity 10000 `
  -storepass <密码> -keypass <密码> `
  -dname "CN=JoyaLand, OU=Dev, O=liupe, L=Beijing, S=Beijing, C=CN"

# 2. 签名 APK
apksigner sign --ks joyaland-release.keystore `
  --ks-key-alias joyaland `
  --ks-pass pass:<密码> --key-pass pass:<密码> `
  --out JoyaLand-v1.0.0.apk `
  app-universal-release-unsigned.apk

⚠️ 重要joyaland-release.keystore 文件必须妥善保管。发布到应用商店后,更新版本必须使用同一密钥签名,否则无法覆盖安装。


四、实际遇到的问题与解决方案

问题 1:ANDROID_HOME 未设置导致初始化失败

现象

Error: ANDROID_HOME is not set

或自动探测到错误路径(C:\Users\xxx\AppData\Local\Android\Sdk),与实际使用的 SDK 路径不符。

原因

  • 系统环境变量未配置,或配置了不同路径的 SDK
  • Tauri 会自动探测系统默认路径,不一定是用户实际使用的路径

解决方案

# 每次打包前手动设置(临时)
$env:ANDROID_HOME = "D:\Android\Sdk"

# 或永久设置系统环境变量(推荐)
[System.Environment]::SetEnvironmentVariable("ANDROID_HOME", "D:\Android\Sdk", "User")

问题 2:Rust target 下载失败(文件重命名错误)

现象

error: component download failed for rust-std-i686-linux-android: 
could not rename downloaded file from '...partial' to '...': 
系统找不到指定的文件。(os error 2)

原因

  • 文件已存在于缓存中,但 rustup 下载流程中遇到并发/临时文件问题
  • 实际上 target 已经安装成功,只是报了错误

解决方案

# 验证是否实际已安装
rustup target list --installed
# 如果列表中有目标,则忽略错误继续即可

问题 3:Gradle 网络超时(无法下载 gradle-8.14.3-bin.zip)

现象

Downloading https://services.gradle.org/distributions/gradle-8.14.3-bin.zip
Exception in thread "main" java.io.IOException: ... failed: timeout

原因

  • 国内网络无法访问 services.gradle.org
  • gradle-wrapper.properties 配置的是从网络下载 Gradle

解决方案:直接修改 gradlew.bat,绕过 Wrapper,调用本地已安装的 Gradle

修改 src-tauri/gen/android/gradlew.bat

@if "%DEBUG%" == "" @echo off

if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem 直接使用本地安装的 Gradle,跳过网络下载
set GRADLE_CMD=D:\tool\gradle-8.14\bin\gradle.bat

if not exist "%GRADLE_CMD%" (
  echo ERROR: Gradle not found at %GRADLE_CMD%
  exit /b 1
)

"%GRADLE_CMD%" %*

:end
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

问题 4:Kotlin 增量编译失败(不同盘符路径冲突)

现象

java.lang.IllegalArgumentException: this and base files have different roots: 
C:\Users\xxx\.cargo\registry\...\tauri-2.10.3\...\ActivityCallback.kt 
and E:\WeChatProjects\JoyaLand\src-tauri\gen\android

原因

  • Kotlin 增量编译器要求所有源文件在同一个根路径下
  • Tauri 的 Kotlin 源文件在 C 盘(Cargo 注册表),项目在 E 盘,跨盘符导致相对路径计算失败

解决方案

  • 此错误会自动 fallback 到非增量编译模式(Using fallback strategy: Compile without Kotlin daemon
  • 不影响最终 APK 生成,可以忽略

若要根本解决,可将整个项目放在与 Cargo 注册表相同的盘符下(如 C 盘)。


问题 5:Gradle 版本不匹配

现象

  • 本地安装 Gradle 8.14,但 gradle-wrapper.properties 要求 8.14.3
  • 直接用 gradlew 会下载 8.14.3,网络超时失败

解决方案

  • 直接修改 gradlew.bat 调用本地 Gradle 8.14(见问题 3 的解决方案)
  • Gradle 8.14 与 8.14.3 兼容,实际构建无影响

问题 6:JDK 版本过高导致警告

现象

Kotlin does not yet support 24 JDK target, falling back to Kotlin JVM_22 JVM target
Java compiler version 24 has deprecated support for compiling with source/target version 8.

原因

  • 系统 JDK 版本为 24,Kotlin 最高支持 JVM 22 目标
  • Android 项目的 sourceCompatibility 设置为 Java 8,JDK 24 已弃用此设置

影响:仅为警告,不影响 APK 构建,可正常使用。

根本解决(可选):安装 JDK 17 或 JDK 21 并配置 JAVA_HOME


问题 7:APK 为未签名版本,无法安装

现象

  • 构建完成后的 APK 文件名为 app-universal-release-unsigned.apk
  • 直接安装到手机报错

解决方案:使用 apksigner 工具签名(见第三节阶段 4)


五、关键配置文件说明

tauri.conf.json(部分)

{
  "build": {
    "frontendDist": "../dist",
    "devUrl": "http://localhost:1420",
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build"
  },
  "app": {
    "windows": [{
      "width": 450,
      "height": 950,
      "resizable": false,
      "center": true
    }]
  }
}

gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

在网络受限环境下,此文件的 distributionUrl 会导致超时。已通过修改 gradlew.bat 绕过。


六、常用命令速查

# 设置 Android SDK 路径(每次打包前执行)
$env:ANDROID_HOME = "D:\Android\Sdk"

# 初始化 Android 项目(只需一次)
npx tauri android init

# 构建 APK
npx tauri android build --apk

# 构建 debug APK(开发调试用)
npx tauri android build --apk --debug

# 签名 APK
$buildTools = "D:\Android\Sdk\build-tools\36.1.0"
& "$buildTools\apksigner.bat" sign `
  --ks joyaland-release.keystore `
  --ks-key-alias joyaland `
  --ks-pass pass:joyaland123 `
  --key-pass pass:joyaland123 `
  --out JoyaLand-v1.0.0.apk `
  app-universal-release-unsigned.apk

# 验证签名
& "$buildTools\apksigner.bat" verify --verbose JoyaLand-v1.0.0.apk

# 通过 ADB 安装到已连接设备
adb install JoyaLand-v1.0.0.apk

七、首次 vs 增量构建时间对比

构建类型前端构建Rust 编译Gradle 打包总计
首次构建~1 秒~20-60 分钟~5-10 分钟~30-70 分钟
增量构建(无 Rust 变更)~1 秒~1-2 分钟~2-3 分钟~3-5 分钟
增量构建(有 Rust 变更)~1 秒~3-10 分钟~2-3 分钟~5-15 分钟

Rust 编译缓存保存在 src-tauri/target/ 目录,体积较大(数 GB),请勿随意删除。


八、输出文件位置

文件路径
未签名 APKsrc-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk
已签名 APKJoyaLand-v1.0.0.apk(项目根目录)
签名密钥joyaland-release.keystore(项目根目录,请备份)
Rust 编译缓存src-tauri/target/
Android 项目src-tauri/gen/android/