建構多個 APK

如果將應用程式發布到 Google Play,您應該建構並上傳 Android App Bundle。這樣一來,Google Play 就會自動為每位使用者的裝置設定產生並提供最佳化的 APK,以便只下載執行應用程式所需的程式碼和資源。如果要發布至不支援 AAB 格式的商店,請發布多個 APK。 在此情況下,您必須自行建構、簽署及管理各個 APK。

雖然您應建構單一 APK 來支援所有目標裝置,但這可能會因為檔案需要支援多個螢幕密度應用程式二進位檔介面 (ADA),而導致 APK 過大。減少 APK 大小的方法之一,就是建立多個 APK,其中包含特定螢幕密度或 ABI 的檔案。

Gradle 可以建立單獨的 APK,其中僅包含每個密度或 ABI 專屬的程式碼和資源。本頁說明如何設定版本以產生多個 APK。如果您需要建立不是根據螢幕密度或 ABI 提供的不同應用程式版本,可以改用建構變數

為多個 APK 設定版本

如要為多個 APK 設定版本,請將 splits 區塊新增至模組層級 build.gradle 檔案。在 splits 區塊,提供指定 Gradle 如何依每個密度產生不同 APK 的 density 區塊,或指定 Gradle 如何依每個 ABI 產生不同 APK 的 abi 區塊。您可以同時提供密度和 ABI 區塊,而且建構系統會為每個密度和 ABI 組合建立 APK。

針對螢幕密度設定多個 APK

若要針對不同螢幕密度分別建立 APK,請在 splits 區塊中加入 density 區塊。在 density 區塊中,提供所需螢幕密度和相容螢幕大小的清單。只有在每個 APK 資訊清單中都需要特定的 <compatible-screens> 元素時,才可使用相容螢幕大小清單。

下列 Gradle DSL 選項可為螢幕密度設定多個 APK:

enable
如果將這項元素設為 true,Gradle 就會根據您定義的螢幕密度產生多個 APK。預設值為 false
exclude
指定 Gradle 不應產生個別 APK 的密度清單 (以半形逗號分隔)。如果要為大部分密度產生 APK,但是需要排除應用程式不支援的某些密度,請使用 exclude
reset()
清除螢幕密度的預設清單。只有在與 include 元素搭配使用時,才能指定要新增的密度。 下列程式碼片段會呼叫 reset() 以清除清單並使用 include,將密度設為 ldpixxhdpi
reset()  // Clears the default list from all densities to no densities.
include "ldpi", "xxhdpi" // Specifies the two densities we want to generate APKs for.
include
指定 Gradle 要產生 APK 的密度清單 (以半形逗號分隔)。只有在與 reset() 搭配使用時,才能指定正確的密度清單。
compatibleScreens
指定相容螢幕大小的逗號分隔清單。這會針對每個 APK 在資訊清單中插入相符的 <compatible-screens> 節點。這項設定可讓您在同一個 build.gradle 區段中,輕鬆管理螢幕密度和螢幕尺寸。 不過,使用 <compatible-screens> 可能會限制應用程式能使用的裝置類型。如需支援不同螢幕尺寸的替代方式,請參閱支援多種螢幕

由於每個採用螢幕密度的 APK 都有一個 <compatible-screens> 標記,對於 APK 支援的螢幕類型有特定限制,即使您發布了多個 APK,也有一些新裝置將不會符合多個 APK 篩選器。因此,Gradle 一律會產生額外的通用 APK,其中包含所有螢幕密度的資產,而且不含 <compatible-screens> 標記。您必須發布這個通用 APK 和個別密度 APK,以便為不符合 APK (含有 <compatible-screens> 標記) 的裝置提供後援機制。

下列範例會針對支援的螢幕範圍中列出的各個螢幕密度產生獨立 APK,但 ldpixxhdpixxxhdpi 除外。方法是使用 exclude 從所有密度的預設清單中移除三個密度。

Groovy

android {
  ...
  splits {

    // Configures multiple APKs based on screen density.
    density {

      // Configures multiple APKs based on screen density.
      enable true

      // Specifies a list of screen densities Gradle should not create multiple APKs for.
      exclude "ldpi", "xxhdpi", "xxxhdpi"

      // Specifies a list of compatible screen size settings for the manifest.
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
}

Kotlin

android {
    ...
    splits {

        // Configures multiple APKs based on screen density.
        density {

            // Configures multiple APKs based on screen density.
            isEnable = true

            // Specifies a list of screen densities Gradle should not create multiple APKs for.
            exclude("ldpi", "xxhdpi", "xxxhdpi")

            // Specifies a list of compatible screen size settings for the manifest.
            compatibleScreens("small", "normal", "large", "xlarge")
        }
    }
}

如需密度名稱和螢幕尺寸名稱的清單,請參閱如何支援多螢幕。如要進一步瞭解如何將應用程式發布到特定的螢幕類型和裝置,請參閱發布至特定螢幕

為 ABI 設定多個 APK

如要針對不同的 ABI 建立個別的 APK,請在 splits 區塊中新增 abi 區塊。在 abi 區塊中,提供所需 ABI 的清單。

下列 Gradle DSL 選項可為每個 ABI 設定多個 APK:

enable
如果將這項元素設為 true,Gradle 會根據您定義的 ABI 產生多個 APK。預設值為 false
exclude
指定 Gradle 不應產生個別 APK 的 ABI 清單 (以半形逗號分隔)。如果要為大部分 ABI 產生 APK,但是需要排除應用程式不支援的某些 ABI,請使用 exclude
reset()
清除 ABI 的預設清單。只有在與 include 元素搭配使用時,才能指定您要新增的 ABI。 下列程式碼片段會呼叫 reset() 以清除清單並使用 include,將 ABI 清單設為 x86x86_64
reset()  // Clears the default list from all ABIs to no ABIs.
include "x86", "x86_64" // Specifies the two ABIs we want to generate APKs for.
include
指定 Gradle 要產生 APK 的 ABI 清單 (以半形逗號分隔)。只有在與 reset() 搭配使用時,才能指定正確的 ABI 清單。
universalApk
如果為 true,除了個別 ABI APK 以外,Gradle 還會產生通用 APK。通用 APK 含有單一 APK 內所有 ABI 的程式碼和資源。 預設值為 false。請注意,這個選項僅適用於 splits.abi 區塊。根據螢幕密度建構多個 APK 時,Gradle 一律會產生通用 APK,其中包含所有螢幕密度適用的程式碼和資源。

下列範例會為每個 ABI 產生單獨的 APK:x86x86_64。方法是使用 reset() 從 ABI 的空白清單開始,後面接著 include 並提供會個別取得一個 APK 的 ABI 清單。

Groovy

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      enable true

      // By default all ABIs are included, so use reset() and include to specify that we only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs that Gradle should create APKs for to none.
      reset()

      // Specifies a list of ABIs that Gradle should create APKs for.
      include "x86", "x86_64"

      // Specifies that we do not want to also generate a universal APK that includes all ABIs.
      universalApk false
    }
  }
}

Kotlin

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      isEnable = true

      // By default all ABIs are included, so use reset() and include to specify that we only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs that Gradle should create APKs for to none.
      reset()

      // Specifies a list of ABIs that Gradle should create APKs for.
      include("x86", "x86_64")

      // Specifies that we do not want to also generate a universal APK that includes all ABIs.
      isUniversalApk = false
    }
  }
}

如需支援 ABI 的清單,請參閱支援的 ABI

mips、mips64 和 armeabi

根據預設,Gradle 適用的 Android 外掛程式 3.1.0 以上版本將不再為下列 ABI 產生 APK:mipsmips64armeabi。這是因為 NDK r17 以上版本已不再包含這些 ABI 做為支援的目標。

建議您先檢查 Google Play 管理中心,確認是否有使用者已下載指定這些 ABI 的應用程式 APK。如果沒有,則建議您從版本中將其略過。如果您要繼續建構以這些 ABI 為目標的 APK,則必須使用 NDK r16b 以下版本設定使用中的建構變數和 ABI,然後在 build.gradle 檔案中指定 ABI,如下所示:

Groovy

splits {
    abi {
        include 'armeabi', 'mips', 'mips64'
        ...
    }
}

Kotlin

splits {
    abi {
        include ("armeabi", "mips", "mips64")
        ...
    }
}

已知問題:如果您使用 NDK r17 以上版本來使用 Gradle 適用的 Android 外掛程式 3.0.1 以下版本,可能會發生下列錯誤:Error:ABIs [mips64, armeabi, mips] are not supported for platform.當您在建構個別 ABI APK 時,舊版外掛程式預設仍會包含不支援的 ABI。若要解決這個問題,請將外掛程式更新到最新版本,或是在應用程式的 build.gradle 檔案中重設外掛程式的預設 ABI 清單,以及僅包含所需的支援 ABI,如下所示:

Groovy

...
splits {
    abi {
        ...
        reset()
        include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
    }
}

Kotlin

...
splits {
    abi {
        ...
        reset()
        include("x86", "armeabi-v7a", "arm64-v8a", "x86_64")
    }
}

不含原生/C++ 程式碼的專案

建構變數面板有兩個資料欄:模組使用中的建構變數。模組的使用中的建構變數值會決定即將部署且顯示在編輯器中的建構變數。

圖 1:建構變數面板針對沒有原生/C++ 程式碼的專案提供兩個資料欄

如要切換變數,請按一下模組的使用中的建構變數儲存格,然後從清單欄位選擇所需的變數。

含有原生/C++ 程式碼的專案

建構變數面板有三個資料欄:模組使用中的建構變數使用中的 ABI。模組的使用中的建構變數值會決定即將部署且顯示在編輯器中的建構變數。針對原生模組,使用中的 ABI 值會決定編輯器要使用的 ABI,但不會影響部署的內容。

圖 2:建構變數面板為使用原生/C++ 程式碼的專案新增使用中的 ABI

若要變更建構類型或 ABI,請按一下使用中的建構變數使用中的 ABI 資料欄的儲存格,然後從清單欄位中選擇所需的變數或 ABI。系統會自動執行新的同步作業。變更應用程式或資料庫模組的資料欄,會將變更套用到所有相依資料列。

設定版本管理

根據預設,Gradle 產生多個 APK 時,每個 APK 都會有相同的版本資訊,如同模組層級 build.gradle 檔案所指定。由於 Google Play 商店不允許同一個應用程式使用多個 APK,這會使得全都具有相同的版本資訊,因此您必須確保每個 APK 都有各自專屬的 versionCode,然後再上傳至 Play 商店。

您可以將模組層級的 build.gradle 檔案覆寫每個 APK 的 versionCode。透過建立對應,為設定多個 APK 的每個 ABI 和密度指派不重複的數值,您可將 defaultConfigproductFlavors 區塊中定義的版本代碼與指派給密度或 ABI 的數值合併,用此值來覆寫輸出版本代碼。

在以下範例中,x86 ABI 的 APK 會取得 2004 的 versionCodex86_64 ABI 則會取得 3004。以較大的增量值 (例如 1000) 指派版本代碼後,如果您需要更新應用程式,之後就可以指派不重複的版本代碼。舉例來說,如果 defaultConfig.versionCode 在後續更新中疊代為 5,Gradle 會指派 2005 的 versionCodex86 APK,指派 3005 至 x86_64 APK。

提示:如果您的版本包含通用 APK,則應為其指派一個低於任何其他 APK 版本代碼的 versionCode。 由於 Google Play 商店會安裝與目標裝置相容的應用程式版本,並具有最高的 versionCode,因此將較低的 versionCode 指派給通用 APK 可確保 Google Play 商店會先嘗試安裝其中一個 APK,然後才退回使用通用 APK。下方的程式碼範例不會覆寫通用 APK 的預設 versionCode 來處理這個問題。

Groovy

android {
  ...
  defaultConfig {
    ...
    versionCode 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

// For per-density APKs, create a similar map like this:
// ext.densityCodes = ['mdpi': 1, 'hdpi': 2, 'xhdpi': 3]

import com.android.build.OutputFile

// For each APK output variant, override versionCode with a combination of
// ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
android.applicationVariants.all { variant ->

  // Assigns a different version code for each output APK
  // other than the universal APK.
  variant.outputs.each { output ->

    // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
    def baseAbiVersionCode =
            // Determines the ABI for this variant and returns the mapped value.
            project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

    // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
    // the following code does not override the version code for universal APKs.
    // However, because we want universal APKs to have the lowest version code,
    // this outcome is desirable.
    if (baseAbiVersionCode != null) {

      // Assigns the new version code to versionCodeOverride, which changes the version code
      // for only the output APK, not for the variant itself. Skipping this step simply
      // causes Gradle to use the value of variant.versionCode for the APK.
      output.versionCodeOverride =
              baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    versionCode = 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
val abiCodes = mapOf("armeabi-v7a" to 1, "x86" to 2, "x86_64" to 3)

// For per-density APKs, create a similar map like this:
// val densityCodes = mapOf("mdpi" to 1, "hdpi" to 2, "xhdpi" to 3)

import com.android.build.api.variant.FilterConfiguration.FilterType.*

// For each APK output variant, override versionCode with a combination of
// abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
androidComponents {
    onVariants { variant ->

        // Assigns a different version code for each output APK
        // other than the universal APK.
        variant.outputs.forEach { output ->
            val name = output.filters.find { it.filterType == ABI }?.identifier

            // Stores the value of abiCodes that is associated with the ABI for this variant.
            val baseAbiCode = abiCodes[name]
            // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
            // the following code does not override the version code for universal APKs.
            // However, because we want universal APKs to have the lowest version code,
            // this outcome is desirable.
            if (baseAbiCode != null) {
                // Assigns the new version code to output.versionCode, which changes the version code
                // for only the output APK, not for the variant itself.
                output.versionCode.set(baseAbiCode * 1000 + (output.versionCode.get() ?: 0))
            }
        }
    }
}

如需更多替代版本代碼配置的範例,請參閱指派版本代碼

建構多個 APK

設定模組層級的 build.gradle 檔案以建構多個 APK 後,按一下建構 > 建構 APK,為專案窗格中目前選取的模組建構所有 APK。Gradle 會將每個密度或 ABI 的 APK 新增至專案的 build/outputs/apk/ 目錄。

對於您設定多個 APK 的每個密度或 ABI,Gradle 會建構 APK。 如果您為密度和 ABI 啟用多個 APK,Gradle 會為每個密度和 ABI 組合建立 APK。舉例來說,下列 build.gradle 程式碼片段可讓您針對 mdpi 和 hdpi 密度以及 x86 和 x86_64 ABI 建構多個 APK。

Groovy

...
  splits {
    density {
      enable true
      reset()
      include "mdpi", "hdpi"
    }
    abi {
      enable true
      reset()
      include "x86", "x86_64"
    }
  }

Kotlin

...
  splits {
    density {
      enable true
      reset()
      include "mdpi", "hdpi"
    }
    abi {
      enable true
      reset()
      include "x86", "x86_64"
    }
  }

範例設定中的輸出內容含有以下 4 個 APK:

  • app-hdpiX86-release.apk:只包含 hdpi 密度和 x86 ABI 的程式碼和資源。
  • app-hdpiX86_64-release.apk:只包含 hdpi 密度和 x86_64 ABI 的程式碼和資源。
  • app-mdpiX86-release.apk:只包含 mdpi 密度和 x86 ABI 的程式碼和資源。
  • app-mdpiX86_64-release.apk:只包含 mdpi 密度和 x86_64 ABI 的程式碼和資源。

根據螢幕密度建構多個 APK 時,除了個別密度 APK 以外,Gradle 一律會產生通用 APK,其中包含所有密度適用的程式碼和資源。根據 ABI 建構多個 APK 時,如果您在 build.gradle 檔案的 splits.abi 區塊中指定 universalApk true,則 Gradle 只會產生含有所有 ABI 適用之程式碼和資源的 APK。

APK 檔案名稱格式

建構多個 APK 時,Gradle 會使用以下配置使用 APK 檔案名稱:

modulename-screendensityABI-buildvariant.apk

配置元件如下:

modulename
指定正在建構的模組名稱。
screendensity
如果為螢幕密度啟用多個 APK,請指定 APK 的螢幕密度,例如「mdpi」。
ABI
如果為 ABI 啟用多個 APK,請指定 APK 的 ABI,例如「x86」。如果同時為螢幕密度和 ABI 啟用多個 APK,Gradle 會將密度名稱與 ABI 名稱建立關聯,例如「mdpiX86」。如果為個別 ABI APK 啟用 universalApk,Gradle 會使用「universal」做為通用 APK 檔案名稱的 ABI 部分。
buildvariant
指定目前建構的建構變數,例如「debug」。

舉例來說,為「myApp」偵錯版本建構 mdpi 螢幕密度 APK 時,APK 檔案名稱為 myApp-mdpi-debug.apk。設為同時為 mdpi 螢幕密度和 x86 ABI 建構多個 APK 的「myApp」發布版本,具有 myApp-mdpiX86-release.apk 的 APK 檔案名稱。