Xây dựng nhiều APK

Nếu bạn phát hành ứng dụng lên Google Play thì bạn nên tạo và tải một Android App Bundle lên. Khi bạn làm như vậy, Google Play sẽ tự động tạo và phân phối các APK được tối ưu hóa cho cấu hình thiết bị của từng người dùng, vì vậy, họ chỉ tải về mã và tài nguyên mà họ cần để chạy ứng dụng của bạn. Việc phát hành nhiều APK sẽ hữu ích nếu bạn đang phát hành ứng dụng lên một cửa hàng chưa hỗ trợ định dạng AAB. Trong trường hợp đó, bạn phải tự xây dựng, ký và quản lý từng APK.

Mặc dù bạn nên xây dựng một APK duy nhất để hỗ trợ tất cả các thiết bị mục tiêu bất cứ khi nào có thể, nhưng điều đó có thể dẫn đến kích thước APK rất lớn do tệp đó cần hỗ trợ nhiều mật độ màn hình hoặc Giao diện nhị phân của ứng dụng (ABI). Có một cách để giảm kích thước APK là tạo nhiều APK chứa các tệp có mật độ màn hình hoặc ABI cụ thể.

Gradle có thể tạo nhiều APK riêng biệt chỉ chứa mã và tài nguyên cụ thể cho từng mật độ hoặc ABI. Trang này mô tả cách định cấu hình bản dựng để tạo nhiều APK. Nếu cần tạo các phiên bản ứng dụng khác nhau không dựa trên mật độ màn hình hoặc ABI, bạn có thể sử dụng các biến thể bản dựng.

Định cấu hình bản dựng cho nhiều APK

Để định cấu hình bản dựng cho nhiều APK, hãy thêm khối splits vào tệp build.gradle cấp mô-đun. Trong phạm vi khối splits, hãy cung cấp khối density chỉ định cách Gradle sẽ tạo APK theo mật độ hoặc khối abi chỉ định cách Gradle sẽ tạo APK theo ABI. Bạn có thể cung cấp cả khối mật độ và ABI. Hệ thống bản dựng sẽ tạo một APK cho mỗi tổ hợp mật độ và ABI.

Định cấu hình nhiều APK cho mật độ màn hình

Để tạo các APK riêng cho nhiều mật độ màn hình khác nhau, hãy thêm một khối density bên trong khối splits của bạn. Trong khối density, hãy cung cấp một danh sách các mật độ màn hình mong muốn và kích thước màn hình tương thích. Bạn chỉ nên sử dụng danh sách các kích thước màn hình tương thích nếu cần các phần tử <compatible-screens> cụ thể trong tệp kê khai của mỗi APK.

Các tùy chọn DSL Gradle sau đây được dùng để định cấu hình nhiều APK cho các mật độ màn hình:

enable
Nếu bạn đặt phần tử này thành true, Gradle sẽ tạo nhiều APK dựa trên mật độ màn hình mà bạn xác định. Giá trị mặc định là false.
exclude
Chỉ định một danh sách mật độ được phân tách bằng dấu phẩy mà Gradle không nên tạo các APK riêng biệt. Sử dụng exclude nếu bạn muốn tạo APK cho hầu hết các mật độ, nhưng cần loại trừ một vài mật độ mà ứng dụng của bạn không hỗ trợ.
reset()
Xóa danh sách mật độ màn hình mặc định. Chỉ sử dụng khi kết hợp với phần tử include để chỉ định mật độ mà bạn muốn thêm. Đoạn mã sau đây sẽ đặt danh sách mật độ thành chỉ ldpixxhdpi bằng cách gọi reset() để xóa danh sách, rồi sử dụng include.
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
Chỉ định danh sách mật độ được phân tách bằng dấu phẩy mà Gradle nên tạo APK. Chỉ sử dụng kết hợp với reset() để chỉ định danh sách mật độ chính xác.
compatibleScreens
Xác định danh sách kích thước màn hình tương thích được phân tách bằng dấu phẩy. Thao tác này sẽ chèn một nút <compatible-screens> phù hợp trong tệp kê khai cho từng APK. Chế độ cài đặt này giúp bạn quản lý cả mật độ màn hình và kích thước màn hình thuận tiện trong cùng phần build.gradle. Tuy nhiên, việc sử dụng <compatible-screens> có thể hạn chế loại thiết bị mà ứng dụng của bạn sẽ hoạt động tương thích. Để biết những cách hỗ trợ nhiều kích thước màn hình khác, hãy xem phần Hỗ trợ nhiều màn hình.

Vì mỗi APK dựa trên mật độ màn hình đều bao gồm một thẻ <compatible-screens> với các hạn chế cụ thể về các loại màn hình mà APK đó hỗ trợ, ngay cả khi bạn phát hành nhiều APK, một số thiết bị mới sẽ không khớp với nhiều bộ lọc APK của bạn. Do đó, Gradle luôn tạo một APK chung chứa các tài nguyên cho tất cả mật độ màn hình và không chứa thẻ <compatible-screens>. Bạn nên phát hành APK chung này cùng với các APK theo mật độ để cung cấp tính năng dự phòng cho các thiết bị không khớp với APK bằng thẻ <compatible-screens>.

Ví dụ sau đây sẽ tạo ra một APK riêng cho mỗi mật độ màn hình được liệt kê trong phần Phạm vi màn hình được hỗ trợ, ngoại trừ ldpi, xxhdpixxxhdpi. Bạn có thể thực hiện việc này bằng cách sử dụng exclude để xóa 3 mật độ khỏi danh sách mặc định của tất cả các mật độ.

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")
        }
    }
}

Để biết danh sách tên mật độ và tên kích thước màn hình, hãy xem phần Cách hỗ trợ nhiều màn hình. Để biết thêm thông tin chi tiết về việc phân phối ứng dụng của bạn cho các loại màn hình và thiết bị cụ thể, hãy xem phần Phân phối theo màn hình cụ thể.

Định cấu hình nhiều APK cho ABI

Để tạo các APK riêng cho từng ABI, hãy thêm một khối abi bên trong khối splits của bạn. Trong khối abi, hãy cung cấp một danh sách các ABI mong muốn.

Các tùy chọn DSL Gradle sau đây được dùng để định cấu hình nhiều APK theo ABI:

enable
Nếu bạn đặt phần tử này thành true, Gradle sẽ tạo nhiều APK dựa trên ABI mà bạn xác định. Giá trị mặc định là false
exclude
Chỉ định một danh sách ABI được phân tách bằng dấu phẩy mà Gradle không nên tạo các APK riêng biệt. Sử dụng exclude nếu bạn muốn tạo APK cho hầu hết các ABI, nhưng cần loại trừ một vài ABI mà ứng dụng của bạn không hỗ trợ.
reset()
Xóa danh sách ABI mặc định. Chỉ sử dụng khi kết hợp với phần tử include để chỉ định ABI mà bạn muốn thêm. Đoạn mã sau đây đặt danh sách ABI thành chỉ x86x86_64 bằng cách gọi reset() để xóa danh sách, rồi sử dụng 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
Chỉ định danh sách ABI được phân tách bằng dấu phẩy mà Gradle nên tạo APK. Chỉ sử dụng kết hợp với reset() để chỉ định danh sách ABI chính xác.
universalApk
Nếu true, Gradle sẽ tạo một APK chung ngoài các APK theo ABI. Một APK chung chứa mã và tài nguyên cho tất cả ABI trong một APK duy nhất. Giá trị mặc định là false. Hãy lưu ý rằng tùy chọn này chỉ có trong khối splits.abi. Khi tạo nhiều APK dựa trên mật độ màn hình, Gradle luôn tạo một APK chung chứa mã và tài nguyên cho mọi mật độ màn hình.

Ví dụ sau đây sẽ tạo ra một APK riêng cho mỗi ABI: x86x86_64. Bạn có thể thực hiện việc này bằng cách sử dụng reset() để bắt đầu với danh sách ABI trống, theo sau là include kèm theo danh sách các ABI, trong đó mỗi ABI sẽ nhận được một APK.

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
    }
  }
}

Để biết danh sách các ABI được hỗ trợ, hãy xem Các ABI được hỗ trợ.

mips, mips64 và armeabi

Trình bổ trợ Android dành cho Gradle 3.1.0 trở lên không còn tạo APK cho các ABI sau đây theo mặc định: mips, mips64armeabi. Lý do là NDK r17 trở lên không còn coi các ABI này như mục tiêu được hỗ trợ nữa.

Trước tiên, hãy kiểm tra Google Play Console để xác minh rằng bạn có người dùng tải xuống các APK của ứng dụng nhắm mục tiêu đến các ABI này. Nếu không, bạn có thể bỏ qua chúng từ bản dựng. Nếu muốn tiếp tục xây dựng các APK nhắm mục tiêu đến các ABI này, bạn phải sử dụng NDK r16b trở xuống, đặt các biến thể bản dựng đang hoạt động và ABI, đồng thời chỉ định ABI trong tệp build.gradle của bạn, như minh họa bên dưới:

Groovy

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

Kotlin

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

Lỗi đã biết: Nếu bạn đang dùng Trình bổ trợ Android dành cho Gradle 3.0.1 trở xuống với NDK r17 trở lên, bạn có thể gặp lỗi sau: Error:ABIs [mips64, armeabi, mips] are not supported for platform. Lý do là vì các phiên bản cũ của trình bổ trợ này vẫn bao gồm các ABI không được hỗ trợ theo mặc định khi bạn tạo các APK theo ABI. Để giải quyết lỗi này, hãy cập nhật trình bổ trợ lên phiên bản mới nhất hoặc đặt lại danh sách ABI mặc định của trình bổ trợ trong tệp build.gradle của ứng dụng của bạn, đồng thời chỉ bao gồm các ABI được hỗ trợ mà bạn muốn, như minh họa bên dưới:

Groovy

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

Kotlin

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

Dự án không có mã gốc/C++

Bảng Biến thể bản dựng có hai cột: Mô-đunBiến thể bản dựng đang hoạt động. Giá trị Biến thể bản dựng đang hoạt động cho mô-đun giúp xác định biến thể bản dựng sẽ được triển khai và hiển thị trong trình chỉnh sửa.

Hình 1: Bảng Biến thể bản dựng có 2 cột cho các dự án không có mã gốc/C++

Để chuyển đổi giữa các biến thể, hãy nhấp vào ô Biến thể bản dựng đang hoạt động cho một mô-đun và chọn biến thể mong muốn từ trường danh sách.

Dự án có mã gốc/C++

Bảng điều khiển Biến thể bản dựng có 3 cột: Mô-đun, Biến thể bản dựng đang hoạt độngABI đang hoạt động. Giá trị Biến thể bản dựng đang hoạt động cho mô-đun giúp xác định biến thể bản dựng sẽ được triển khai và hiển thị trong trình chỉnh sửa. Đối với các mô-đun gốc, giá trị ABI đang hoạt động giúp xác định ABI mà trình chỉnh sửa sẽ sử dụng, nhưng không ảnh hưởng đến biến thể sẽ triển khai.

Hình 2: Bảng Biến thể bản dựng bổ sung thêm cột ABI đang hoạt động cho các dự án có mã gốc/C++

Để thay đổi loại bản dựng hoặc ABI, hãy nhấp vào ô cho cột Biến thể hoạt động của bản dựng hoặc ABI hiện hoạt và chọn biến hoặc ABI mà bạn muốn từ danh sách. Quá trình đồng bộ mới sẽ tự động chạy. Thay đổi cột cho một ứng dụng hoặc mô-đun thư viện sẽ áp dụng thay đổi đó cho tất cả các hàng phụ thuộc.

Định cấu hình phiên bản

Theo mặc định, khi Gradle tạo nhiều APK, mỗi APK sẽ có cùng một thông tin phiên bản, như được chỉ định trong tệp build.gradle ở cấp mô-đun. Vì Cửa hàng Google Play không cho phép nhiều APK cho cùng một ứng dụng và tất cả đều có cùng một thông tin phiên bản, bạn cần đảm bảo rằng mỗi APK có một APK riêng versionCode trước khi tải lên Cửa hàng Play.

Bạn có thể định cấu hình tệp build.gradle cấp mô-đun để ghi đè versionCode cho từng APK. Bằng cách tạo liên kết sẽ chỉ định một giá trị số duy nhất cho mỗi ABI và mật độ bạn định cấu hình nhiều APK, bạn có thể ghi đè mã phiên bản đầu ra bằng một giá trị kết hợp mã phiên bản được xác định trong khốidefaultConfig hoặc productFlavors có giá trị số được chỉ định cho mật độ hoặc ABI.

Trong ví dụ sau, APK cho ABI x86 sẽ nhận được versionCode là 2004 và ABI x86_64 sẽ nhận được 3004. Việc chỉ định mã phiên bản theo gia số lớn, chẳng hạn như 1000, cho phép bạn chỉ định mã phiên bản duy nhất sau này nếu bạn cần cập nhật ứng dụng. Ví dụ: nếu defaultConfig.versionCode lặp lại đến 5 trong lần cập nhật tiếp theo, Gradle sẽ chỉ định versionCode là 2005 cho APK x86 và 3005 cho APK x86_64.

Mẹo: Nếu bản dựng của bạn chứa một APK chung, thì bạn nên chỉ định APK đó cho versionCode thấp hơn so với bất kỳ APK nào khác. Do Cửa hàng Google Play cài đặt phiên bản ứng dụng vừa tương thích với thiết bị mục tiêu vừa có versionCode cao nhất, nên việc chỉ định versionCode thấp hơn cho APK chung sẽ đảm bảo Chửa hàng Google Play có thể cài đặt một trong các APK của bạn trước khi quay lại APK chung. Mã mẫu bên dưới xử lý vấn đề này bằng cách không ghi đè versionCode mặc định của một APK chung.

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))
            }
        }
    }
}

Để biết thêm ví dụ về các lược đồ mã phiên bản thay thế, hãy xem phần Chỉ định mã phiên bản.

Xây dựng nhiều APK

Sau khi bạn định cấu hình tệp build.gradle cấp mô-đun để xây dựng nhiều APK, hãy nhấp vào Build > Build APK (Xây dựng > Xây dựng APK) để xây dựng tất cả APK cho mô-đun hiện được chọn trong ngăn Project (Dự án). Gradle sẽ tạo các APK cho mỗi mật độ hoặc ABI trong thư mục build/outputs/apk/ của dự án.

Gradle sẽ xây dựng một APK cho mỗi mật độ hoặc ABI mà bạn định cấu hình nhiều APK. Nếu bạn cho phép nhiều APK cho cả mật độ và ABI, Gradle sẽ tạo một APK cho mỗi tổ hợp mật độ và ABI. Ví dụ: đoạn mã build.gradle sau đây cho phép xây dựng nhiều APK cho mật độ mdpi và hdpi, cũng như các ABI x86 và x86_64.

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"
    }
  }

Kết quả từ cấu hình mẫu bao gồm 4 APK sau:

  • app-hdpiX86-release.apk: Chỉ chứa mã và tài nguyên cho mật độ hdpi và ABI x86.
  • app-hdpiX86_64-release.apk: Chỉ chứa mã và tài nguyên cho mật độ hdpi, và ABI x86_64.
  • app-mdpiX86-release.apk: Chỉ chứa mã và tài nguyên cho mật độ mdpi và ABI x86.
  • app-mdpiX86_64-release.apk: Chỉ chứa mã và tài nguyên cho mật độ mdpi và ABI x86_64.

Khi xây dựng nhiều APK dựa trên mật độ màn hình, Gradle luôn tạo một APK chung, bao gồm mã và tài nguyên cho tất cả các mật độ. ngoài các APK theo mật độ. Khi xây dựng nhiều APK dựa trên ABI, Gradle chỉ tạo một APK chứa mã và tài nguyên cho tất cả ABI nếu bạn chỉ định universalApk true trong khối splits.abi trong tệp build.gradle của bạn.

Định dạng tên tệp APK

Khi xây dựng nhiều APK, Gradle sử dụng tên tệp APK theo lược đồ sau:

modulename-screendensityABI-buildvariant.apk

Thành phần của lược đồ này bao gồm:

modulename
Chỉ định tên mô-đun đang được xây dựng.
screendensity
Nếu bạn bật nhiều APK cho mật độ màn hình, hãy chỉ định mật độ màn hình cho APK, chẳng hạn như "mdpi".
ABI
Nếu bạn đã bật nhiều APK cho ABI, hãy chỉ định ABI cho APK, chẳng hạn như "x86". Nếu bạn bật nhiều APK cho cả mật độ màn hình và ABI, Gradle sẽ liên kết tên mật độ với tên ABI, ví dụ: "mdpiX86". Nếu bạn bật universalApk cho mỗi APK ABI thì Gradle sẽ sử dụng APK chung "universal" làm phần ABI của tên tệp APK chung.
buildvariant
Chỉ định biến thể bản dựng đang được xây dựng, chẳng hạn như "gỡ lỗi".

Ví dụ: khi tạo APK mật độ màn hình mdpi cho phiên bản gỡ lỗi của "myApp", tên tệp APK là myApp-mdpi-debug.apk. Phiên bản phát hành của "myApp" được định cấu hình để xây dựng nhiều APK cho cả mật độ màn hình mdpi và ABI x86 có tên tệp APK là myApp-mdpiX86-release.apk.