Tworzenie wielu pakietów APK

Chociaż lepiej jest utworzyć pojedynczy plik APK, który będzie obsługiwać wszystkie urządzenia docelowe, może to spowodować, że plik APK będzie bardzo duży ze względu na pliki obsługujące wiele interfejsów binarnych aplikacji (ABI). Jednym ze sposobów na zmniejszenie rozmiaru pliku APK jest utworzenie wielu plików APK zawierających pliki dla konkretnych interfejsów ABI.

Gradle może utworzyć osobne pliki APK, które zawierają tylko kod i zasoby specyficzne dla każdego ABI. Na tej stronie dowiesz się, jak skonfigurować kompilację, aby wygenerować wiele pakietów APK. Jeśli chcesz utworzyć różne wersje aplikacji, które nie są oparte na ABI, użyj wariantów kompilacji.

Konfigurowanie kompilacji pod kątem wielu plików APK

Aby skonfigurować kompilację pod kątem wielu plików APK, dodaj blok splits do pliku build.gradle na poziomie modułu. W bloku splits podaj blok abi, który określa, jak Gradle ma generować pliki APK dla poszczególnych ABI.

Konfigurowanie wielu plików APK pod kątem interfejsów ABI

Aby utworzyć osobne pliki APK dla różnych interfejsów ABI, dodaj blok abi w bloku splits. W bloku abi podaj listę wybranych interfejsów ABI.

Do konfigurowania wielu plików APK na ABI służą te opcje DSL Gradle:

enable w przypadku Groovy lub isEnable w przypadku skryptu Kotlin.
Jeśli ustawisz ten element na true, Gradle wygeneruje wiele plików APK na podstawie zdefiniowanych przez Ciebie interfejsów ABI. Wartością domyślną jest false.
exclude
Określa rozdzieloną przecinkami listę interfejsów ABI, dla których Gradle nie ma generować osobnych plików APK. Użyj exclude, jeśli chcesz wygenerować pliki APK dla większości interfejsów ABI, ale musisz wykluczyć kilka interfejsów ABI, których Twoja aplikacja nie obsługuje.
reset()

Czyści domyślną listę ABI. Używaj tylko w połączeniu z elementem include, aby określić interfejsy ABI, które chcesz dodać.

Ten fragment kodu ustawia listę interfejsów ABI na x86 i x86_64, wywołując reset(), aby wyczyścić listę, a następnie używając include:

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

Określa rozdzieloną przecinkami listę interfejsów ABI, dla których Gradle ma wygenerować pliki APK. Używaj tylko w połączeniu z reset(), aby określić dokładną listę interfejsów ABI.

universalApk w przypadku Groovy lub isUniversalApk w przypadku skryptu Kotlin.

Jeśli true, Gradle generuje uniwersalny pakiet APK oprócz pakietów APK dla poszczególnych ABI. Uniwersalny plik APK zawiera kod i zasoby dla wszystkich interfejsów ABI w jednym pliku APK. (wartością domyślną jest false);

W poniższym przykładzie generowany jest osobny plik APK dla każdego interfejsu ABI: x86 i x86_64. W tym celu użyj reset(), aby zacząć od pustej listy interfejsów ABI, a następnie include z listą interfejsów ABI, z których każdy otrzyma pakiet APK.

Dynamiczny

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 you only
      // want APKs for x86 and x86_64.

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

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

      // Specifies that you don't 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 you only
      // want APKs for x86 and x86_64.

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

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

      // Specifies that you don't want to also generate a universal APK that includes all ABIs.
      isUniversalApk = false
    }
  }
}

Listę obsługiwanych interfejsów ABI znajdziesz w tym artykule.

Projekty bez kodu natywnego/C++

W przypadku projektów bez kodu natywnego/C++ panel Warianty kompilacji ma 2 kolumny: Moduł i Aktywny wariant kompilacji, jak pokazano na rysunku 1.

Panel Warianty kompilacji zawiera 2 kolumny dla projektów bez kodu natywnego/C++.
Rysunek 1. Panel Warianty kompilacji zawiera 2 kolumny dla projektów bez kodu natywnego/C++.

Wartość Aktywny wariant kompilacji modułu określa wariant kompilacji, który jest wdrażany i widoczny w edytorze. Aby przełączać się między wariantami, kliknij komórkę Aktywny wariant kompilacji modułu i wybierz odpowiedni wariant z listy.

Projekty z kodem natywnym/C++

W przypadku projektów z kodem natywnym/C++ panel Warianty kompilacji zawiera 3 kolumny: Moduł, Aktywny wariant kompilacji i Aktywny ABI, jak pokazano na rysunku 2.

W panelu Warianty kompilacji dodano kolumnę Aktywny ABI dla projektów z kodem natywnym/C++.
Rysunek 2. W panelu Warianty kompilacji dodano kolumnę Aktywny ABI w przypadku projektów z kodem natywnym/C++.

Wartość Aktywny wariant kompilacji modułu określa wariant kompilacji, który jest wdrażany i widoczny w edytorze. W przypadku modułów natywnych wartość Aktywny interfejs ABI określa interfejs ABI używany przez edytor, ale nie ma wpływu na to, co jest wdrażane.

Aby zmienić typ kompilacji lub ABI:

  1. Kliknij komórkę w kolumnie Aktywny wariant kompilacji lub Aktywny ABI.
  2. Wybierz wariant lub ABI z listy. Automatycznie uruchomi się nowa synchronizacja.

Zmiana dowolnej kolumny w module aplikacji lub biblioteki powoduje zastosowanie zmiany we wszystkich zależnych wierszach.

Konfigurowanie obsługi wersji

Domyślnie, gdy Gradle generuje wiele plików APK, każdy z nich ma te same informacje o wersji, które są określone w pliku build.gradle lub build.gradle.kts na poziomie modułu. Sklep Google Play nie zezwala na wiele plików APK tej samej aplikacji, które mają te same informacje o wersji, dlatego przed przesłaniem plików APK do Sklepu Play musisz się upewnić, że każdy z nich ma unikalny element versionCode.

Możesz skonfigurować plik build.gradle na poziomie modułu, aby zastąpić versionCode dla każdego pliku APK. Tworząc mapowanie, które przypisuje unikalną wartość liczbową do każdego interfejsu ABI, dla którego konfigurujesz wiele plików APK, możesz zastąpić kod wersji wyjściowej wartością, która łączy kod wersji zdefiniowany w bloku defaultConfig lub productFlavors z wartością liczbową przypisaną do interfejsu ABI.

W tym przykładzie plik APK dla ABI x86 otrzymuje versionCode 2004, a ABI x86_64 – versionCode 3004.

Przypisywanie kodów wersji w dużych odstępach, np. co 1000, pozwala później przypisywać unikalne kody wersji, jeśli trzeba zaktualizować aplikację. Jeśli na przykład w kolejnej aktualizacji wartość defaultConfig.versionCode wzrośnie do 5, Gradle przypisze versionCode o wartości 2005 do pakietu APK x86 i 3005 do pakietu APK x86_64.

Dynamiczny

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]

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 doesn't override the version code for universal APKs.
    // However, because you 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 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)

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 doesn't override the version code for universal APKs.
            // However, because you 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))
            }
        }
    }
}

Więcej przykładów alternatywnych schematów kodów wersji znajdziesz w artykule Przypisywanie kodów wersji.

Tworzenie wielu plików APK

Po skonfigurowaniu pliku build.gradle lub build.gradle.kts na poziomie modułu pod kątem tworzenia wielu plików APK kliknij Build > Build APK (Utwórz > Utwórz plik APK), aby utworzyć wszystkie pliki APK dla wybranego modułu w panelu Project (Projekt). Gradle utworzy pliki APK dla każdego interfejsu ABI w katalogu build/outputs/apk/ projektu.

Gradle tworzy plik APK dla każdego interfejsu ABI, dla którego skonfigurujesz wiele plików APK.

Na przykład ten fragment kodu build.gradle umożliwia utworzenie wielu plików APK dla interfejsów ABI x86 i x86_64:

Dynamiczny

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

Kotlin

...
  splits {
    abi {
      isEnable = true
      reset()
      include("x86", "x86_64")
    }
  }

Dane wyjściowe z przykładowej konfiguracji obejmują te 4 pliki APK:

  • app-X86-release.apk: zawiera kod i zasoby dla interfejsu ABI x86.
  • app-X86_64-release.apk: zawiera kod i zasoby dla interfejsu ABI x86_64.

Podczas tworzenia wielu plików APK na podstawie ABI Gradle generuje tylko plik APK, który zawiera kod i zasoby dla wszystkich ABI, jeśli określisz universalApk true w bloku splits.abi w pliku build.gradle (w przypadku Groovy) lub isUniversalApk = true w bloku splits.abi w pliku build.gradle.kts (w przypadku skryptu Kotlin).

Format nazwy pliku APK

Podczas tworzenia wielu plików APK Gradle generuje nazwy plików APK według tego schematu:

modulename-ABI-buildvariant.apk

Komponenty schematu:

modulename
Określa nazwę tworzonego modułu.
ABI
Jeśli włączono wiele pakietów APK dla ABI, określa ABI dla pakietu APK, np. x86.
buildvariant
Określa tworzony wariant kompilacji, np. debug.