跳到主內容

構建併發布 Android 應用

如何準備並向 Play 商店釋出 Android 應用。

若要測試應用,可以使用命令列中的 flutter run,或 IDE 中的 Run(執行)和 Debug(除錯)選項。

當您準備好建立應用的釋出版本(例如為了釋出到 Google Play 商店)時,本頁面可以為您提供幫助。在釋出之前,您可能需要為應用做最後的潤色。本指南說明了如何執行以下任務:

新增啟動圖示

#

建立新的 Flutter 應用時,它會包含一個預設的啟動圖示。若要自定義此圖示,建議檢視 flutter_launcher_icons 包。

或者,您可以按照以下步驟手動操作:

  1. 查閱 Material Design 產品圖示設計指南。

  2. [project]/android/app/src/main/res/ 目錄中,將您的圖示檔案放置在以配置限定符 (configuration qualifiers)命名的資料夾中。預設的 mipmap- 資料夾展示了正確的命名規範。

  3. AndroidManifest.xml 中,更新 application 標籤的 android:icon 屬性,以引用上一步中的圖示(例如:<application android:icon="@mipmap/ic_launcher" ...)。

  4. 若要驗證圖示是否已替換,請執行您的應用並檢查啟動器中的應用圖示。

啟用 Material 元件

#

如果您的應用使用了 平臺檢視 (platform views),您可以按照 Android 入門指南中描述的步驟啟用 Material Components。

例如

  1. <my-app>/android/app/build.gradle.kts 中新增對 Android Material 的依賴:
kotlin
dependencies {
    // ...
    implementation("com.google.android.material:material:<version>")
    // ...
}
groovy
dependencies {
    // ...
    implementation 'com.google.android.material:material:<version>'
    // ...
}

若要查詢最新版本,請訪問 Google Maven

  1. <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">
    
  2. <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 商店文件

若要對您的應用進行簽名,請使用以下說明。

建立上傳金鑰庫

#

如果您已有金鑰庫,請直接跳至下一步。如果沒有,請使用以下任一方法建立一個:

  1. 按照 Android Studio 金鑰生成步驟進行操作。

  2. 在命令列中執行以下命令:

    在 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 檔案。

  1. android 屬性塊之前定義並載入金鑰庫屬性檔案。

  2. 設定 keystoreProperties 物件以載入 key.properties 檔案。

[project]/android/app/build.gradle.kts
kotlin
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 {
   ...
}
[project]/android/app/build.gradle
groovy
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 {
   ...
}
  1. android 屬性塊內部的 buildTypes 屬性塊之前添加簽名配置。
[project]/android/app/build.gradle.kts
kotlin
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")
        }
    }
...
}
[project]/android/app/build.gradle
groovy
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 apkflutter 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(除錯)選項構建並執行應用時,您的構建可能會失敗,並出現以下訊息:

Build failure because Multidex support is required

若要從命令列啟用 Multidex,請執行 flutter run --debug 並選擇一個 Android 裝置。

Selecting an Android device with the flutter CLI.

收到提示時,輸入 y。Flutter 工具會啟用 Multidex 支援並重新嘗試構建。

The output of a successful build after adding multidex.

您也可以選擇按照 Android 指南手動支援 Multidex,並修改專案的 Android 目錄配置。必須指定一個 multidex keep 檔案以包含必要類。

io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class

此外,還需包含應用啟動時使用的任何其他類。有關手動新增 Multidex 支援的詳細指南,請查閱官方 Android 文件

檢查應用清單 (Manifest)

#

檢查預設的 App Manifest(應用清單)檔案。

[project]/android/app/src/main/AndroidManifest.xml
xml
<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

[project]/android/app/build.gradle.kts
kotlin
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 商店和開發者裝置上的唯一識別符號。

如果您更新了 applicationIdnamespace 屬性,您還必須更新 MainActivity.ktMainActivity.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 管理,以確保與框架和外掛的相容性。除非以下情況,否則通常不需要更改它們:

  1. 需要較新的 API:如果您使用的外掛或功能要求的 minSdk 高於 Flutter 的預設值,您可以手動將其設定為更高的版本號(例如 minSdk = 24)。
  2. 需要鎖定版本:如果您想防止在升級 Flutter 時自動更新這些版本,可以使用特定的整數值替換預設變數。

版本程式碼和版本名稱

#

versionCodeversionName 會根據您的 pubspec.yaml 檔案(使用 version: 1.0.0+1 欄位)自動設定。通常不需要在 Gradle 檔案中修改它們。

構建釋出版應用

#

釋出到 Play 商店時,您有兩種可用的釋出格式:

  • App Bundle(首選)
  • APK

構建 App Bundle

#

本節介紹如何構建釋出版 App Bundle。如果您已完成簽名步驟,構建出的 App Bundle 將會自動簽名。此時,您可以考慮 混淆您的 Dart 程式碼,以增加逆向工程的難度。混淆程式碼涉及在構建命令中新增標誌,並維護額外的檔案以反混淆堆疊跟蹤。

從命令列操作:

  1. 輸入 cd [project]
  2. 執行 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 離線測試

#
  1. 如果尚未操作,請從其 GitHub 倉庫下載 bundletool
  2. 從 App Bundle 生成 APK 集
  3. 將 APK 部署到連線的裝置上。

使用 Google Play 線上測試

#
  1. 將您的 App Bundle 上傳到 Google Play 進行測試。您可以使用內部測試軌道,或 Alpha/Beta 通道在正式釋出前測試 App Bundle。
  2. 按照步驟 上傳您的 App Bundle 到 Play 商店。

構建 APK

#

雖然推薦使用 App Bundle 而非 APK,但仍有一些商店尚不支援 App Bundle。在這種情況下,請為每個目標 ABI(應用程式二進位制介面)構建釋出版 APK。

如果您已完成簽名步驟,構建出的 APK 將會自動簽名。此時,您可以考慮 混淆您的 Dart 程式碼,以增加逆向工程的難度。混淆程式碼涉及在構建命令中新增標誌。

從命令列操作:

  1. 輸入 cd [project]

  2. 執行 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:

從命令列操作:

  1. 使用 USB 電纜將您的 Android 裝置連線到計算機。
  2. 輸入 cd [project]
  3. 執行 flutter install

釋出到 Google Play 商店

#

有關將應用釋出到 Google Play 商店的詳細說明,請查閱 Google Play 釋出文件。

更新應用版本號

#

應用的預設版本號是 1.0.0。要更新它,請導航到 pubspec.yaml 檔案並更新以下行:

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 檔案中的 versionNameversionCode

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)

The Gradle build script menu in Android Studio.

接下來,選擇構建變體。點選主選單中的 Build > Select Build Variant(構建 > 選擇構建變體)。在 Build Variants 面板中選擇任意變體(預設為 debug)。

The build variant menu in Android Studio with Release selected.

生成的 App Bundle 或 APK 檔案位於您應用資料夾內的 build/app/outputs 中。

如何判斷 APK 是否使用了 Flutter?

#

您可以使用 apkanalyzer 工具列出檔案。

sh
apkanalyzer files list --files-only <SOME-APK> files list --files-only <SOME-APK>

然後查詢 /lib/<ARCH>/libflutter.so 檔案。

例如,以下命令應返回大於 0 的數字:

sh
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> 標籤。該值可以是 12

例如: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 庫,此方法無效。

非技術性評估

#