iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Rust

用 Tauri 打造你的應用程式系列 第 22

[Day 22] Android 打包

  • 分享至 

  • xImage
  •  

前幾天我們介紹了如何將 Tauri 專案打包成 Windows 桌面應用程式,今天讓我們來看一下如何打包成 Android APP。

錯誤示範:直接打包

如果直接使用 Tauri 提供的打包指令去打包 Android 應用程式,會成功,但打包出來的東西沒什麼用...

打包指令如下:

npm run tauri android build -- --aab

或是

npm run tauri android build -- --apk

這個命令會啟動完整的建置流程,包括前端資源編譯、Rust 程式碼編譯、資源打包,以及最終的簽署步驟。

打包過程比較久,需要數分鐘。打包完成後,會在 src-tauri/gen/android/app/build/outputs 目錄下產生兩種類型的檔案 (e.g. apkapk/universal/release/app-universal-release-unsigned.apk):

  • APK 檔案 (.apk):這是傳統的 Android 應用程式格式,可以直接安裝到任何 Android 裝置上。APK 檔案非常適合用於內部測試、Beta 版發布,或是透過第三方管道分發應用程式。
  • AAB 檔案 (.aab):這是 Google 推薦的現代打包格式,全稱為 Android App Bundle。AAB 格式允許 Google Play 針對不同的裝置配置(螢幕尺寸、CPU 架構等)產生最佳化的 APK,有效減少使用者下載的檔案大小。

如果嘗試使用這個 .apk 檔案進行安裝,會發現根本無法安裝,只會看到安裝時的錯誤提示:

(為了方便測試與 Demo,這邊使用 BlueStacks 作為手機模擬器)

而導致這一切的原因是:沒有數位簽署!

數位簽署的重要性

數位簽署是 Android 生態系統的安全基石。每個發布到 Google Play 的應用都必須經過數位簽署,這不僅是技術要求,更是安全保障的重要機制。

簽署的主要目的包括:身份驗證確保應用程式來源的真實性,讓使用者和平台能夠識別開發者身份;完整性保護防止應用程式在傳輸或儲存過程中被惡意篡改;更新連續性確保應用程式的更新版本來自同一開發者,維護使用者的信任關係。

未簽署的應用程式無法在正式環境中安裝,這是 Android 系統的基本安全機制。透過簽署,我們建立了一個可信任的發布鏈條,從開發者到使用者的每個環節都得到保障。

簽署

1. 產生簽署金鑰

在正式打包前,我們需要產生一個數位憑證(Keystore),這將成為我們應用程式的「身份證」。使用 Java 提供的 keytool 工具可以輕鬆完成這個任務:

keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload

執行這個命令後,系統會要求您輸入一系列資訊,包括密碼、組織名稱、城市等。這些資訊將被嵌入到數位憑證中,作為開發者身份的證明。

特別注意:這個 Keystore 檔案極其重要,一旦遺失將無法發布應用程式的更新版本。建議將其安全備份到多個位置,並妥善保管相關密碼。

2. 設定簽署金鑰

src-tauri/gen/android 資料夾中,建立 keystore.properties 檔案,內容如下

password=<password defined when keytool was executed>
keyAlias=upload
storeFile=<location of the key store file, such as /Users/<user name>/upload-keystore.jks or C:\\Users\\<user name>\\upload-keystore.jks>

重要提醒

  • 這個檔案包含敏感資訊,絕對不要提交到 Git 版本控制
  • 不要推送到公開的 repository
  • 建議將 keystore.properties 加入到 .gitignore 檔案中

3. 設定 Gradle 使用簽署金鑰

找到這個檔案

src-tauri/gen/android/app/build.gradle.kts

做出以下調整:

+ import java.io.FileInputStream

android {
    compileSdk = 36
    namespace = "com.ck642509.ithome_ironman_2025_tauri"
    defaultConfig {
        manifestPlaceholders["usesCleartextTraffic"] = "false"
        applicationId = "com.ck642509.ithome_ironman_2025_tauri"
        minSdk = 24
        targetSdk = 36
        versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
        versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
    }
+   signingConfigs {
+       create("release") {
+           val keystorePropertiesFile = rootProject.file("keystore.properties")
+           val keystoreProperties = Properties()
+           if (keystorePropertiesFile.exists()) {
+               keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+         + }

+           keyAlias = keystoreProperties["keyAlias"] as String
+           keyPassword = keystoreProperties["password"] as String
+           storeFile = file(keystoreProperties["storeFile"] as String)
+           storePassword = keystoreProperties["password"] as String
+       }
+   }
    buildTypes {
        getByName("debug") {
            manifestPlaceholders["usesCleartextTraffic"] = "true"
            isDebuggable = true
            isJniDebuggable = true
            isMinifyEnabled = false
            packaging {                jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
                jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
                jniLibs.keepDebugSymbols.add("*/x86/*.so")
                jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
            }
        }
        getByName("release") {
-           isMinifyEnabled = true
-           proguardFiles(
-               *fileTree(".") { include("**/*.pro") }
-                   .plus(getDefaultProguardFile("proguard-android-optimize.txt"))
-                   .toList().toTypedArray()
-           )
+           signingConfig = signingConfigs.getByName("release")
        }
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        buildConfig = true
    }
}

打包命令與產物類型

當簽署環境準備就緒後,我們可以執行打包命令:

npm run tauri android build -- --apk

這次打包出來的 apk 檔,就可以順利安裝了:

那個百變怪的 icon 是自己設定的,設定方式請參考 Day 20

版本管理最佳實踐

每次發布新版本時,都要確保在 tauri.conf.json 中正確更新版本號。版本號的管理應該遵循語義化版本控制的原則,讓使用者和平台都能清楚了解更新的性質和重要程度。

同時,建議為每個發布版本建立對應的 Git 標籤,這樣可以輕鬆追蹤不同版本的程式碼狀態,也便於在需要時回溯到特定版本。

小結

今天我們學會了 Tauri Android 應用的簽署打包流程。從錯誤示範中了解到數位簽署的重要性,並掌握了三個關鍵步驟:使用 keytool 產生憑證、建立 keystore.properties 配置、修改 Gradle 腳本整合簽署。

這套完整流程不僅解決了應用安裝問題,更為 Google Play 上架奠定基礎。同時要注意 Keystore 檔案的安全保管,避免洩露到公開版本控制中。


上一篇
[Day 21] Windows 打包 (二):嵌入外部資源檔案
下一篇
[Day 23] Plugin (五):Logging
系列文
用 Tauri 打造你的應用程式24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言