構建併發布 Android 應用
如何準備並向 Play 商店釋出 Android 應用。
若要測試應用,可以使用命令列中的 flutter run,或 IDE 中的 Run(執行)和 Debug(除錯)選項。
當您準備好建立應用的釋出版本(例如為了釋出到 Google Play 商店)時,本頁面可以為您提供幫助。在釋出之前,您可能需要為應用做最後的潤色。本指南說明了如何執行以下任務:
- 新增啟動圖示
- 啟用 Material 元件
- 應用簽名
- 使用 R8 縮減程式碼
- 啟用多 dex (Multidex) 支援
- 檢查應用清單 (Manifest)
- 檢查構建配置
- 構建釋出版應用
- 釋出到 Google Play 商店
- 更新應用版本號
- Android 釋出常見問題解答
新增啟動圖示
#建立新的 Flutter 應用時,它會包含一個預設的啟動圖示。若要自定義此圖示,建議檢視 flutter_launcher_icons 包。
或者,您可以按照以下步驟手動操作:
-
查閱 Material Design 產品圖示設計指南。
-
在
[project]/android/app/src/main/res/目錄中,將您的圖示檔案放置在以配置限定符 (configuration qualifiers)命名的資料夾中。預設的mipmap-資料夾展示了正確的命名規範。 -
在
AndroidManifest.xml中,更新application標籤的android:icon屬性,以引用上一步中的圖示(例如:<application android:icon="@mipmap/ic_launcher" ...)。 -
若要驗證圖示是否已替換,請執行您的應用並檢查啟動器中的應用圖示。
啟用 Material 元件
#如果您的應用使用了 平臺檢視 (platform views),您可以按照 Android 入門指南中描述的步驟啟用 Material Components。
例如
- 在
<my-app>/android/app/build.gradle.kts中新增對 Android Material 的依賴:
dependencies {
// ...
implementation("com.google.android.material:material:<version>")
// ...
}
dependencies {
// ...
implementation 'com.google.android.material:material:<version>'
// ...
}
若要查詢最新版本,請訪問 Google Maven。
-
在
<my-app>/android/app/src/main/res/values/styles.xml中設定淺色主題。xml<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> -
在
<my-app>/android/app/src/main/res/values-night/styles.xml中設定深色主題。xml<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
應用簽名
#若要在 Play 商店釋出,您必須使用數字證書對應用進行簽名。
Android 使用兩種簽名金鑰:上傳金鑰 (upload key) 和 應用簽名金鑰 (app signing key)。
- 開發者使用上傳金鑰對
.aab或.apk檔案進行簽名並上傳到 Play 商店。 - 終端使用者下載的是由應用簽名金鑰簽名的
.apk檔案。
若要建立應用簽名金鑰,請使用 Google Play 應用簽名功能,具體步驟請參考官方 Play 商店文件。
若要對您的應用進行簽名,請使用以下說明。
建立上傳金鑰庫
#如果您已有金鑰庫,請直接跳至下一步。如果沒有,請使用以下任一方法建立一個:
-
按照 Android Studio 金鑰生成步驟進行操作。
-
在命令列中執行以下命令:
在 macOS 或 Linux 上,使用以下命令:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \ -storetype JKS -keysize 2048 -validity 10000 -alias upload在 Windows 上,在 PowerShell 中使用以下命令:
keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks ` -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 ` -alias upload此命令將
upload-keystore.jks檔案儲存在您的主目錄中。如果您想將其儲存在其他位置,請修改傳遞給-keystore引數的路徑。請務必保護好keystore檔案;不要將其提交到公共版本控制系統中!
在應用中引用金鑰庫
#建立一個名為 [project]/android/key.properties 的檔案,其中包含對您金鑰庫的引用。不要包含尖括號 (< >),它們僅表示該處是佔位符。
storePassword=<password-from-previous-step>
keyPassword=<password-from-previous-step>
keyAlias=upload
storeFile=<keystore-file-location>
storeFile 在 macOS 上可能位於 /Users/<使用者名稱>/upload-keystore.jks,在 Windows 上可能位於 C:\\Users\\<使用者名稱>\\upload-keystore.jks。
在 Gradle 中配置簽名
#以釋出模式構建應用時,配置 Gradle 以使用您的上傳金鑰。要配置 Gradle,請編輯 <project>/android/app/build.gradle.kts 檔案。
-
在
android屬性塊之前定義並載入金鑰庫屬性檔案。 設定
keystoreProperties物件以載入key.properties檔案。
import java.util.Properties
import java.io.FileInputStream
plugins {
...
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
...
}
import java.util.Properties
import java.io.FileInputStream
plugins {
...
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
...
}
- 在
android屬性塊內部的buildTypes屬性塊之前添加簽名配置。
android {
// ...
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now,
// so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
}
}
...
}
android {
// ...
signingConfigs {
release {
keyAlias = keystoreProperties['keyAlias']
keyPassword = keystoreProperties['keyPassword']
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword = keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now,
// so `flutter run --release` works.
signingConfig = signingConfigs.debug
signingConfig = signingConfigs.release
}
}
...
}
Flutter 現在將對所有釋出版本進行簽名。
要了解關於應用簽名的更多資訊,請查閱 Android 開發者文件中的 Sign your app(對您的應用簽名)。
後量子密碼學 (PQC) 混合簽名 (Android 17+)
#Android 17 引入了 v3.2 APK 簽名方案。該方案結合了傳統簽名(如 RSA 或 EC)與 ML-DSA 簽名,以實現後量子密碼學 (PQC) 混合簽名。這可以確保您的應用簽名身份免受量子計算帶來的潛在威脅。
- 使用 Play 應用簽名的應用:如果您使用 Play 應用簽名,可以等待 Google Play 提供升級選項,以使用 Google Play 生成的 PQC 金鑰切換到混合簽名。
-
使用自管理金鑰的應用:如果您管理自己的簽名金鑰,可以使用更新後的 Android 構建工具(如
apksigner)切換到混合身份,將 PQC 金鑰與新的傳統金鑰相結合。請注意,您必須建立新的傳統金鑰;不能重用舊金鑰。
有關詳細資訊,請查閱 Android 關於 PQC APK 簽名的文件。
使用 R8 縮減程式碼
#
R8 是 Google 新的程式碼縮減器。在構建釋出版 APK 或 AAB 時,它預設處於啟用狀態。若要停用 R8,請向 flutter build apk 或 flutter build appbundle 命令新增 --no-shrink 標誌。
啟用多 dex (Multidex) 支援
#當編寫大型應用或使用大型外掛時,如果目標 API 版本為 20 或以下,您可能會遇到 Android 的 64k 方法數限制。在執行未啟用程式碼縮減的除錯版本(使用 flutter run)時,也可能會遇到此問題。
Flutter 工具支援輕鬆啟用 Multidex。最簡單的方法是在收到提示時選擇啟用 Multidex 支援。該工具會檢測到 Multidex 構建錯誤,並在對您的 Android 專案進行更改前徵求您的意見。選擇加入後,Flutter 會自動新增 androidx.multidex:multidex 依賴,並使用生成的 FlutterMultiDexApplication 作為專案的 Application 類。
當您嘗試透過 IDE 中的 Run(執行)和 Debug(除錯)選項構建並執行應用時,您的構建可能會失敗,並出現以下訊息:
若要從命令列啟用 Multidex,請執行 flutter run --debug 並選擇一個 Android 裝置。
收到提示時,輸入 y。Flutter 工具會啟用 Multidex 支援並重新嘗試構建。
您也可以選擇按照 Android 指南手動支援 Multidex,並修改專案的 Android 目錄配置。必須指定一個 multidex keep 檔案以包含必要類。
io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class
此外,還需包含應用啟動時使用的任何其他類。有關手動新增 Multidex 支援的詳細指南,請查閱官方 Android 文件。
檢查應用清單 (Manifest)
#檢查預設的 App Manifest(應用清單)檔案。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="[project]"
...
</application>
...
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
驗證以下值:
| 標籤 | 屬性 | 值 |
|---|---|---|
application
|
在 application 標籤中編輯 android:label,以反映應用的最終名稱。 |
|
uses-permission
|
如果您的應用需要訪問 Internet,請在 android:name 屬性中新增 android.permission.INTERNET 許可權值。標準模板不包含此標籤,但在開發期間允許 Internet 訪問,以支援 Flutter 工具與執行中的應用進行通訊。 |
檢查 Gradle 構建配置
#要驗證 Android 構建配置,請檢查預設 Gradle 構建指令碼中的 android 塊。預設的 Gradle 構建指令碼位於 [project]/android/app/build.gradle.kts。
android {
namespace = "com.example.[project]"
// Any value starting with "flutter." gets its value from
// the Flutter Gradle plugin.
// To change from these defaults, make your changes in this file.
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
...
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.[project]"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.club.tw/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
...
}
}
應用 ID
#applicationId 是您的應用在 Google Play 商店和開發者裝置上的唯一識別符號。
如果您更新了 applicationId 和 namespace 屬性,您還必須更新 MainActivity.kt 或 MainActivity.java 檔案中的 package 語句,並將該檔案移動到相應的目錄結構中。
例如
- 在 Kotlin 中,如果您的新 ID 是
com.example.myapp,請將MainActivity檔案移動到android/app/src/main/kotlin/com/example/myapp/MainActivity.kt,並確保第一行是package com.example.myapp。 - 在 Java 中,請將
MainActivity檔案移動到android/app/src/main/java/com/example/myapp/MainActivity.java,並確保第一行是package com.example.myapp。
Android SDK 版本
#Flutter 工具為 Android SDK 版本設定了預設值:
compileSdk:用於編譯應用的 Android SDK 版本。minSdk:應用支援的最低 Android 版本。-
targetSdk:應用設計並測試所執行的 Android 版本。
這些預設值(flutter.compileSdkVersion 等)由 Flutter 管理,以確保與框架和外掛的相容性。除非以下情況,否則通常不需要更改它們:
-
需要較新的 API:如果您使用的外掛或功能要求的
minSdk高於 Flutter 的預設值,您可以手動將其設定為更高的版本號(例如minSdk = 24)。 - 需要鎖定版本:如果您想防止在升級 Flutter 時自動更新這些版本,可以使用特定的整數值替換預設變數。
版本程式碼和版本名稱
#versionCode 和 versionName 會根據您的 pubspec.yaml 檔案(使用 version: 1.0.0+1 欄位)自動設定。通常不需要在 Gradle 檔案中修改它們。
構建釋出版應用
#釋出到 Play 商店時,您有兩種可用的釋出格式:
- App Bundle(首選)
- APK
構建 App Bundle
#本節介紹如何構建釋出版 App Bundle。如果您已完成簽名步驟,構建出的 App Bundle 將會自動簽名。此時,您可以考慮 混淆您的 Dart 程式碼,以增加逆向工程的難度。混淆程式碼涉及在構建命令中新增標誌,並維護額外的檔案以反混淆堆疊跟蹤。
從命令列操作:
- 輸入
cd [project]。 - 執行
flutter build appbundle。
(執行flutter build預設為釋出版本構建。)
應用的釋出包創建於 [project]/build/app/outputs/bundle/release/app.aab。
預設情況下,App Bundle 包含您的 Dart 程式碼以及為 armeabi-v7a (ARM 32-bit)、arm64-v8a (ARM 64-bit) 和 x86-64 (x86 64-bit) 編譯的 Flutter 執行時。
測試 App Bundle
#App Bundle 可以透過多種方式進行測試。本節介紹兩種方式:
使用 bundle tool 離線測試
#- 如果尚未操作,請從其 GitHub 倉庫下載
bundletool。 - 從 App Bundle 生成 APK 集。
- 將 APK 部署到連線的裝置上。
使用 Google Play 線上測試
#- 將您的 App Bundle 上傳到 Google Play 進行測試。您可以使用內部測試軌道,或 Alpha/Beta 通道在正式釋出前測試 App Bundle。
- 按照步驟 上傳您的 App Bundle 到 Play 商店。
構建 APK
#雖然推薦使用 App Bundle 而非 APK,但仍有一些商店尚不支援 App Bundle。在這種情況下,請為每個目標 ABI(應用程式二進位制介面)構建釋出版 APK。
如果您已完成簽名步驟,構建出的 APK 將會自動簽名。此時,您可以考慮 混淆您的 Dart 程式碼,以增加逆向工程的難度。混淆程式碼涉及在構建命令中新增標誌。
從命令列操作:
輸入
cd [project]。-
執行
flutter build apk --split-per-abi。(flutter build命令預設為--release。)
該命令會生成三個 APK 檔案:
[project]/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk[project]/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk[project]/build/app/outputs/flutter-apk/app-x86_64-release.apk
移除 --split-per-abi 標誌將生成一個包含所有目標 ABI 編譯程式碼的 Fat APK。此類 APK 比拆分後的 APK 檔案體積更大,導致使用者下載了其裝置架構不需要的原生二進位制檔案。
在裝置上安裝 APK
#按照以下步驟在連線的 Android 裝置上安裝 APK:
從命令列操作:
- 使用 USB 電纜將您的 Android 裝置連線到計算機。
- 輸入
cd [project]。 - 執行
flutter install。
釋出到 Google Play 商店
#有關將應用釋出到 Google Play 商店的詳細說明,請查閱 Google Play 釋出文件。
更新應用版本號
#應用的預設版本號是 1.0.0。要更新它,請導航到 pubspec.yaml 檔案並更新以下行:
version: 1.0.0+1
版本號由三個由點分隔的數字組成(如上例中的 1.0.0),後跟一個可選的構建編號(如上例中的 1),用 + 分隔。
透過指定 --build-name 和 --build-number,可以在 Flutter 構建中覆蓋版本和構建編號。
在 Android 中,build-name 用作 versionName,而 build-number 用作 versionCode。有關更多資訊,請查閱 Android 文件中的 Version your app(應用版本管理)。
當您重新構建 Android 應用時,pubspec 檔案中的版本號更新將自動更新 local.properties 檔案中的 versionName 和 versionCode。
Android 釋出常見問題解答
#以下是關於 Android 應用部署的一些常見問題。
我應該構建 App Bundle 還是 APK?
#Google Play 商店建議您優先部署 App Bundle 而非 APK,因為它們能更高效地交付應用給使用者。但是,如果您透過 Play 商店以外的渠道分發應用,APK 可能是您唯一的選擇。
什麼是 Fat APK?
#Fat APK 是一種包含多個 ABI 二進位制檔案的單個 APK。其優點是相容多種架構,但缺點是檔案體積大,導致使用者在安裝時下載和儲存更多資料。在構建 APK 而非 App Bundle 時,強烈建議按照 構建 APK 部分的說明,使用 --split-per-abi 標誌構建拆分 APK。
支援哪些目標架構?
#在釋出模式下構建應用時,Flutter 應用可為 armeabi-v7a (ARM 32-bit)、arm64-v8a (ARM 64-bit) 和 x86-64 (x86 64-bit) 進行編譯。
如何對 flutter build appbundle 建立的應用包進行簽名?
#
請檢視 應用簽名。
如何從 Android Studio 中構建釋出版本?
#在 Android Studio 中,開啟您應用資料夾下的 android/ 資料夾。然後,在專案面板中選擇 build.gradle (Module: app)。
接下來,選擇構建變體。點選主選單中的 Build > Select Build Variant(構建 > 選擇構建變體)。在 Build Variants 面板中選擇任意變體(預設為 debug)。
生成的 App Bundle 或 APK 檔案位於您應用資料夾內的 build/app/outputs 中。
如何判斷 APK 是否使用了 Flutter?
#您可以使用 apkanalyzer 工具列出檔案。
apkanalyzer files list --files-only <SOME-APK> files list --files-only <SOME-APK>
然後查詢 /lib/<ARCH>/libflutter.so 檔案。
例如,以下命令應返回大於 0 的數字:
apkanalyzer files list some-flutter-app.apk | grep flutter.so | wc -l
為什麼有效?
Flutter 依賴於 Flutter 引擎使用的 C++ 程式碼。在 Android 中,此程式碼與 Flutter 框架及開發者的 Dart 程式碼一起打包為名為 libflutter.so 的原生庫。Java/Android 工具會將 flutter 庫重新命名為帶有 lib 字首的名稱,並處理跨架構的庫定位。這就是逆向工程師識別 Flutter 應用的方法。
輔助評估
#執行 apkanalyzer manifest print <SOME-APK>,查詢 android:name="flutterEmbedding" 的 <meta-data> 標籤。該值可以是 1 或 2。
例如:apkanalyzer manifest print some-flutter-app.apk | grep flutterEmbedding -C 2 將返回類似以下內容的字串。
<meta-data
android:name="flutterEmbedding"
android:value="2" />
為什麼有效?
Flutter 曾擁有兩種不同的嵌入器,該標誌用於確定使用了哪種。Flutter 3.22 移除了 v1 嵌入器應用構建的能力。不建議使用此機制,因為不清楚 flutterEmbedding 值還會被保留在所有 Flutter 應用中多久。此外,對於作為 AAR 依賴項匯入 Android 應用的 Flutter 庫,此方法無效。
非技術性評估
#- 在裝置上下載 Flutter Shark 並掃描本地應用。
- 訪問 Flutter Hunt 網站。