Mở rộng trình bổ trợ Android cho Gradle

Trình bổ trợ Android cho Gradle (AGP) là hệ thống xây dựng chính thức dành cho các ứng dụng Android. Trình bổ trợ này hỗ trợ biên dịch nhiều loại nguồn và liên kết những nguồn này với nhau tạo thành một ứng dụng có thể chạy trên một thiết bị Android thực tế hoặc trên trình mô phỏng.

AGP chứa các điểm mở rộng cho các trình bổ trợ kiểm soát đầu vào của bản dựng và mở rộng chức năng của trình bổ trợ đó thông qua các bước mới có thể tích hợp được với các tác vụ xây dựng chuẩn. Các phiên bản trước của AGP không có các API chính thức được tách biệt rõ ràng với các hoạt động triển khai nội bộ. Kể từ phiên bản 7.0, AGP phát hành một bộ API chính thức, ổn định và đáng tin cậy.

Vòng đời API của AGP

AGP tuân theo Vòng đời tính năng của Gradle để chỉ định trạng thái của các API:

  • Internal (Nội bộ): Không dành cho mục đích sử dụng công khai
  • Incubating (Tiềm năng): Sẵn sàng sử dụng công khai nhưng chưa hoàn thiện, nghĩa là các API này không thể tương thích ngược trong phiên bản cuối cùng
  • Public (Công khai): Sẵn sàng sử dụng công khai và ổn định
  • Deprecated (Không dùng nữa): Không còn được hỗ trợ và được thay thế bằng API mới

Chính sách về việc ngừng cung cấp

AGP đang phát triển theo hướng ngừng cung cấp các API cũ và thay thế bằng các API mới, ổn định và một Ngôn ngữ đặc tả chuyên biệt (DSL) mới. Quá trình phát triển này sẽ trải dài trên nhiều bản phát hành AGP. Bạn có thể tìm hiểu thêm về quá trình phát triển này tại mốc thời gian di chuyển API/DSL của AGP.

Khi các API của AGP không được cung cấp nữa thì trong quá trình di chuyển này, các API đó vẫn có sẵn trong bản phát hành chính hiện tại kèm theo các cảnh báo. Các API không dùng nữa sẽ bị xoá hoàn toàn khỏi AGP trong bản phát hành chính sau đó. Ví dụ: nếu API không còn dùng trong AGP 7.0, thì API đó vẫn có sẵn trong phiên bản đó nhưng kèm theo các cảnh báo. API đó sẽ không tồn tại trong AGP 8.0 nữa.

Để xem ví dụ về cách sử dụng API mới trong các tuỳ chỉnh bản dựng phổ biến, hãy tham khảo công thức (recipes) cho trình bổ trợ Android cho Gradle. Tài liệu này sẽ cung cấp các ví dụ về cách tuỳ chỉnh các bản dựng phổ biến. Bạn cũng có thể tìm thêm thông tin chi tiết về các API mới trong tài liệu tham khảo của chúng tôi.

Thông tin cơ bản về bản dựng Gradle

Hướng dẫn này không đề cập đến toàn bộ hệ thống xây dựng Gradle. Tuy nhiên, hướng dẫn này sẽ đề cập đến tập khái niệm tối thiểu cần thiết, giúp bạn tích hợp với các API của chúng tôi cũng như liên kết đến những tài liệu quan trọng về Gradle để tìm hiểu thêm.

Chúng tôi giả định bạn có kiến thức cơ bản về cách hoạt động của Gradle, bao gồm cách định cấu hình dự án, chỉnh sửa các tệp bản dựng, áp dụng trình bổ trợ và chạy các tác vụ. Để tìm hiểu những kiến thức cơ bản về Gradle liên quan đến AGP, bạn nên xem lại bài viết Định cấu hình bản dựng. Để tìm hiểu về khung sườn chung cho việc tuỳ chỉnh các trình bổ trợ Gradle, hãy xem bài viết về cách phát triển các trình bổ trợ Gradle tuỳ chỉnh.

Bảng chú giải thuật ngữ về các kiểu khởi tạo từng phần (lazy) của Gradle

Gradle cung cấp một số kiểu có hành vi "lười biếng" (lazily), hoặc giúp trì hoãn các công việc tính toán nặng nề hoặc tạo Task cho các giai đoạn sau của quá trình tạo bản dựng. Các kiểu này là thành phần cốt lõi của nhiều Gradle và API của AGP. Danh sách sau đây bao gồm các kiểu Gradle chính liên quan đến quá trình thực thi từng phần (lazy exceturion) và các phương thức quan trọng của các kiểu này.

Provider<T>
Cung cấp một giá trị kiểu T (trong đó "T" là một kiểu bất kỳ) có thể đọc được trong giai đoạn thực thi thông qua phương thức get() hoặc chuyển thành một Provider<S> mới (trong đó "S" là một kiểu bất kỳ khác) thông qua các phương thức map(), flatMap()zip(). Lưu ý rằng phương thức get() không bao giờ được gọi trong giai đoạn cấu hình.
  • map(): Chấp nhận một lambda và tạo Provider có kiểu S, Provider<S>. Đối số lambda của map() nhận giá trị T và tạo ra giá trị S. Lambda này không được thực thi tức thì; thay vào đó, quá trình thực thi sẽ được trì hoãn đến thời điểm gọi phương thức get() trên giá trị Provider<S> trả về, tạo thành một chuỗi hoàn toàn lười biếng.
  • flatMap(): Cũng chấp nhận lambda và tạo Provider<S>, nhưng lambda lấy giá trị T và tạo Provider<S> (thay vì tạo trực tiếp giá trị S). Sử dụng flatMap() khi không thể xác định S tại thời điểm cấu hình và bạn chỉ có thể thu được giá trị Provider<S>. Thực tế, nếu dùng map() và kết thúc bằng kiểu kết quả Provider<Provider<S>>, điều này có nghĩa là bạn nên sử dụng flatMap().
  • zip(): Cho phép bạn kết hợp hai thực thể Provider để tạo thành một Provider mới, trong đó giá trị được tính toán bằng hàm kết hợp các giá trị nhập vào từ hai thực thể Providers.
Property<T>
Triển khai Provider<T>, vì vậy cũng cung cấp một giá trị có kiểu T. Không giống như Provider<T>, là giá trị chỉ đọc, bạn cũng có thể thiết lập giá trị cho Property<T>.
    Có hai cách để thực hiện việc này:
  • Thiết lập trực tiếp giá trị kiểu T khi sẵn sàng mà không cần các tính toán trì hoãn.
  • Thiết lập một Provider<T> khác làm nguồn của giá trị Property<T>. Trong trường hợp này, giá trị T chỉ được cụ thể hoá khi gọi Property.get().
TaskProvider
Triển khai Provider<Task>. Để tạo một TaskProvider, sử dụng tasks.register() chứ không phải tasks.create(), nhằm đảm bảo các tác vụ chỉ được khởi tạo từng phần khi cần thiết. Bạn có thể sử dụng flatMap() để truy cập kết quả của một Task trước khi tạo Task này. Điều này có thể hữu ích nếu bạn muốn sử dụng kết quả đầu ra này làm đầu vào cho các thực thể Task khác.

Giao diện cung cấp giá trị (Provider) cùng với các phương thức biến đổi là các thành phần cần thiết để thiết lập đầu vào và đầu ra của các tác vụ thực thi theo từng phần (lười biếng), tức là không cần phải tạo trước tất cả tác vụ và phân giải các giá trị.

Giao diện cung cấp giá trị cũng chứa thông tin về phần phụ thuộc tác vụ. Khi bạn tạo một Provider bằng cách biến đổi đầu ra của một Task, Task đó sẽ trở thành phần phụ thuộc ngầm định của Provider đó và sẽ được tạo và chạy bất cứ khi nào giá trị của Provider được phân giải, chẳng hạn như khi có một Task khác yêu cầu.

Dưới đây là ví dụ về cách đăng ký hai tác vụ, GitVersionTaskManifestProducerTask, trong khi trì hoãn việc tạo các thực thể của Task đến thời điểm thực sự cần thiết. Giá trị nhập vào ManifestProducerTask được gán cho một Provider nhận được từ dữ liệu đầu ra của GitVersionTask, nên ManifestProducerTask ngầm ẩn sẽ phụ thuộc vào GitVersionTask.

// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
    project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
        it.gitVersionOutputFile.set(
            File(project.buildDir, "intermediates/gitVersionProvider/output")
        )
    }

...

/**
 * Register another task in the configuration block (also executed lazily,
 * only if the task is required).
 */
val manifestProducer =
    project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
        /**
         * Connect this task's input (gitInfoFile) to the output of
         * gitVersionProvider.
         */
        it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
    }

2 tác vụ này sẽ chỉ thực thi khi được yêu cầu rõ ràng. Điều này có thể xuất hiện trong lệnh gọi Gradle, chẳng hạn như khi bạn chạy ./gradlew debugManifestProducer hoặc nếu đầu ra của ManifestProducerTask được kết nối với một tác vụ khác và bắt buộc phải có giá trị tương ứng.

Mặc dù bạn sẽ viết các tác vụ tuỳ chỉnh sử dụng dữ liệu đầu vào và/hoặc tạo ra kết quả đầu ra, nhưng AGP không trực tiếp cung cấp quyền truy cập công khai vào các tác vụ đó. Các tác vụ này là chi tiết triển khai phải được thay đổi từ phiên bản này sang phiên bản khác. Thay vào đó, AGP sẽ cung cấp API biến thể (Variant API) và quyền truy cập vào kết quả của các tác vụ của API này hoặc tạo các cấu phần phần mềm (Artifacts) có thể đọc và biến đổi. Hãy xem API biến thể, Cấu phần phần mềm và Tác vụ trong tài liệu này để biết thêm thông tin.

Các giai đoạn tạo bản dựng Gradle

Xây dựng dự án vốn là một quá trình phức tạp và tiêu tốn rất nhiều tài nguyên. Ngoài ra, dự án còn phải đáp ứng nhiều yêu cầu chức năng khác như tránh cấu hình các tác vụ, kiểm tra tình trạng cập nhật và lưu cấu hình vào bộ nhớ đệm nhằm tránh lãng phí thời gian cho các tính toán mang tính lặp lại hoặc không cần thiết.

Để áp dụng một số giải pháp tối ưu hoá này, các tập lệnh và trình bổ trợ cho Gradle phải tuân thủ các quy tắc nghiêm ngặt trong mỗi giai đoạn xây dựng Gradle riêng biệt: khởi chạy, cấu hình và thực thi. Trong hướng dẫn này, chúng tôi sẽ tập trung vào các giai đoạn cấu hình và thực thi. Bạn có thể tìm thêm thông tin về tất cả giai đoạn trong Hướng dẫn về vòng đời bản dựng Gradle.

Giai đoạn cấu hình

Trong giai đoạn cấu hình, tập lệnh của tất cả dự án thuộc bản dựng sẽ được đánh giá, các trình bổ trợ sẽ được áp dụng và các phần phụ thuộc bản dựng sẽ được phân giải. Bạn nên dùng giai đoạn này để định cấu hình bản dựng bằng cách sử dụng các đối tượng DSL và để đăng ký các tác vụ và dữ liệu đầu vào của các tác vụ này theo từng phần.

Giai đoạn cấu hình này luôn đảm bảo được chạy, bất kể tác vụ yêu cầu chạy là gì, do đó điều đặc biệt quan trọng trong giai đoạn này là phải duy trì cấu hình tinh gọn và hạn chế bất kỳ các tính toán phụ thuộc vào dữ liệu đầu vào ngoài bản thân tập lệnh bản dựng. Điều này có nghĩa rằng bạn không nên thực thi các chương trình bên ngoài hoặc đọc dữ liệu từ hệ thống mạng, hoặc thực hiện các phép tính kéo dài. Những việc này có thể trì hoãn đến giai đoạn thực thi thông qua thực thể Task phù hợp.

Giai đoạn thực thi

Giai đoạn này sẽ thực thi các tác vụ được yêu cầu và các tác vụ phụ thuộc. Cụ thể, giai đoạn này sẽ thực thi (các) phương thức lớp Task được đánh dấu bằng @TaskAction. Trong quá trình thực thi tác vụ, bạn được phép đọc dữ liệu từ các đầu vào (chẳng hạn như tệp) và phân giải các giao diện cung cấp giá trị theo từng phần (lazy provider) bằng cách gọi Provider<T>.get(). Việc xử lý các giao diện cung cấp giá trị theo từng phần theo cách này sẽ khởi động một chuỗi lệnh gọi map() hoặc flatMap() theo sau thông tin về phần phụ thuộc tác vụ chứa trong giao diện cung cấp đó. Các tác vụ có thể chạy từng phần để cụ thể hoá các giá trị được yêu cầu.

API biến thể, Cấu phần phần mềm và Tác vụ

API biến thể là một cơ chế mở rộng trong trình bổ trợ Android cho Gradle, cho phép bạn thao tác với các tuỳ chọn khác nhau, thường được thiết lập thông qua DSL trong các tệp cấu hình bản dựng, tạo ảnh hưởng lên bản dựng Android. API Variant cũng cho phép bạn truy cập các cấu phần phần mềm trung gian và cuối cùng do bản dựng tạo ra, chẳng hạn như tệp lớp, tệp kê khai hợp nhất hoặc tệp APK/AAB.

Bản dựng Android và các điểm mở rộng

Khi tương tác với AGP, hãy sử dụng các điểm mở rộng được tạo lập chuyên biệt thay vì đăng ký các phương thức gọi lại trong vòng đời thông thường của Gradle (chẳng hạn như afterEvaluate()) hoặc thiết lập các phần phụ thuộc Task tường minh. Những tác vụ do AGP tạo ra được xem là các chi tiết triển khai và không được cung cấp dưới dạng API công khai. Bạn phải tránh thử lấy các thực thể của đối tượng Task hoặc đoán tên Task và thêm trực tiếp các phương thức gọi lại hoặc phần phụ thuộc vào các đối tượng Task đó.

AGP sẽ hoàn thành các bước sau để tạo và thực thi các thực thể Task, cho phép tạo ra các cấu phần phần mềm của bản dựng. Các bước chính liên quan đến việc tạo đối tượng Variant đều được thực hiện thông qua các phương thức gọi lại, cho phép bạn thay đổi một số đối tượng được tạo trong một bản dựng. Điều quan trọng bạn cần lưu ý là tất cả cuộc gọi lại đều xảy ra trong giai đoạn cấu hình (như mô tả trong tài liệu này) và phải được chạy nhanh chóng, hoãn lại mọi công việc phức tạp cho các thực thể Task phù hợp trong giai đoạn thực thi.

  1. Phân tích cú pháp DSL: Đây là thời điểm đánh giá các tập lệnh bản dựng cũng như tạo lập và thiết lập các thuộc tính khác nhau cho các đối tượng Android DSL trong khối android. Giai đoạn này cũng là thời điểm để đăng ký các phương thức gọi lại API biến thể như được mô tả trong các phần bên dưới.
  2. finalizeDsl(): Phương thức gọi lại cho phép thay đổi các đối tượng DSL trước khi các đối tượng này bị khoá để tạo thành phần (biến thể). Các đối tượng VariantBuilder được tạo dựa trên dữ liệu chứa trong các đối tượng DSL.

  3. Khoá DSL: DSL hiện tại đã bị khoá và không thể thay đổi.

  4. beforeVariants(): Phương thức gọi lại này có thể tác động đến các thành phần được tạo và một số thuộc tính của các thành phần đó thông qua VariantBuilder. Phương thức này vẫn cho phép chỉnh sửa quy trình tạo và các cấu phần phần mềm được tạo.

  5. Tạo biến thể: Danh sách các thành phần và cấu phần phần mềm sẽ được tạo đã hoàn tất và không thể thay đổi.

  6. onVariants(): Trong phương thức gọi lại này, bạn được phép truy cập các đối tượng Variant đã tạo và có thể thiết lập giá trị hoặc cung cấp giá trị cho Property theo từng phần.

  7. Khoá biến thể: Các đối tượng biến thể hiện tại đã bị khoá và không thể thay đổi.

  8. Tác vụ đã tạo: Các đối tượng Variant và giá trị Property của các đối tượng này được dùng để tạo các thực thể Task cần thiết để tạo bản dựng.

AGP sẽ giới thiệu thành phần AndroidComponentsExtension cho phép bạn đăng ký các phương thức gọi lại cho finalizeDsl(), beforeVariants()onVariants(). Thành phần mở rộng này có sẵn trong tập lệnh bản dựng, được truy xuất thông qua khối androidComponents:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

Tuy nhiên, bạn chỉ nên giữ lại các tập lệnh bản dựng chỉ dùng cho cấu hình khai báo thông qua DSL của khối android và di chuyển mọi logic bắt buộc tuỳ chỉnh sang buildSrc hoặc các trình bổ trợ bên ngoài. Bạn cũng có thể xem buildSrc mẫu trong kho lưu trữ GitHub về công thức Gradle để tìm hiểu cách tạo một trình bổ trợ trong dự án. Dưới đây là ví dụ về cách đăng ký phương thức gọi lại trong mã của trình bổ trợ:

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

Hãy xem xét kỹ hơn các phương thức gọi lại có sẵn và loại trường hợp sử dụng mà trình bổ trợ có thể hỗ trợ trong mỗi phương thức gọi lại này:

finalizeDsl(callback: (DslExtensionT) -> Unit)

Trong phương thức gọi lại này, bạn có thể truy cập và chỉnh sửa các đối tượng DSL đã được tạo bằng cách phân tích cú pháp thông tin trong khối android trong tệp bản dựng. Các đối tượng DSL này sẽ được dùng để khởi chạy và định cấu hình các biến thể trong các giai đoạn sau này của quá trình tạo bản dựng. Ví dụ: bạn có thể lập trình để tạo cấu hình mới hoặc ghi đè các thuộc tính. Tuy nhiên, lưu ý rằng bạn phải phân giải tất cả giá trị tại thời điểm cấu hình để các giá trị đó không phụ thuộc vào bất kỳ dữ liệu đầu vào bên ngoài nào. Sau khi thực thi xong phương thức gọi lại này, các đối tượng DSL không còn hữu ích nữa và bạn không nên lưu giữ tham chiếu đến các đối tượng đó hoặc chỉnh sửa giá trị của các đối tượng đó nữa.

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            extension.buildTypes.create("extra").let {
                it.isJniDebuggable = true
            }
        }
    }
}

beforeVariants()

Ở giai đoạn này của quá trình tạo bản dựng, bạn có quyền truy cập vào các đối tượng VariantBuilder, cho phép xác định các biến thể sẽ được tạo cũng như các thuộc tính của biến thể đó. Ví dụ: bạn có thể lập trình để tắt một số biến thể, các kiểm thử của biến thể đó hoặc chỉ thay đổi giá trị thuộc tính (ví dụ: minSdk) cho một biến thể đã chọn. Tương tự như finalizeDsl(), tất cả giá trị bạn cung cấp phải được phân giải tại thời điểm cấu hình và không phụ thuộc vào thông tin đầu vào bên ngoài. Bạn không thể chỉnh sửa đối tượng VariantBuilder sau khi thực thi phương thức gọi lại beforeVariants().

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

Phương thức gọi lại beforeVariants() có thể nhận tham số tuỳ chọn VariantSelector có được sau khi gọi phương thức selector() trên thành phần androidComponentsExtension. Bạn có thể sử dụng đối tượng trả về này để lọc các thành phần gọi đến phương thức gọi lại này dựa trên tên, loại bản dựng hoặc phiên bản sản phẩm.

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

Vào thời điểm gọi onVariants(), tất cả cấu phần phần mềm sẽ được tạo bằng AGP đã được quyết định nên bạn không thể tắt các cấu phần phần mềm đó nữa. Tuy nhiên, bạn có thể chỉnh sửa một số giá trị dùng cho các tác vụ bằng cách thiết lập giá trị cho các thuộc tính Property trong các đối tượng Variant. Các giá trị của Property chỉ được phân giải khi thực thi các tác vụ của AGP, do đó bạn có thể chuyển các giá trị này đến các giao diện cung cấp giá trị một cách an toàn từ các tác vụ tuỳ chỉnh của riêng bạn, qua đó thực hiện các tính toán cần thiết, bao gồm việc đọc các dữ liệu đầu vào từ bên ngoài như tệp hoặc thông qua hệ thống mạng.

// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
    // Gather the output when we are in single mode (no multi-apk).
    val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

    // Create version code generating task
    val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
        it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
    }
    /**
     * Wire version code from the task output.
     * map() will create a lazy provider that:
     * 1. Runs just before the consumer(s), ensuring that the producer
     * (VersionCodeTask) has run and therefore the file is created.
     * 2. Contains task dependency information so that the consumer(s) run after
     * the producer.
     */
    mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}

Đóng góp nguồn được tạo cho bản dựng

Trình bổ trợ của bạn có thể đóng góp một số loại nguồn được tạo, chẳng hạn như:

Để biết danh sách đầy đủ các nguồn bạn có thể thêm, hãy xem API Nguồn.

Đoạn mã này cho biết cách thêm thư mục nguồn tuỳ chỉnh có tên ${variant.name} vào nhóm tài nguyên Java bằng cách sử dụng hàm addStaticSourceDirectory(). Sau đó, chuỗi công cụ Android sẽ xử lý thư mục này.

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

Hãy xem công thức addJavaSource để biết thêm thông tin chi tiết.

Đoạn mã này cho biết cách thêm thư mục có tài nguyên Android được tạo từ một tác vụ tuỳ chỉnh vào nhóm tài nguyên res. Quá trình này tương tự như các loại nguồn khác.

onVariants(selector().withBuildType("release")) { variant ->
    // Step 1. Register the task.
    val resCreationTask =
       project.tasks.register<ResCreatorTask>("create${variant.name}Res")

    // Step 2. Register the task output to the variant-generated source directory.
    variant.sources.res?.addGeneratedSourceDirectory(
       resCreationTask,
       ResCreatorTask::outputDirectory)
    }

...

// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
   @get:OutputFiles
   abstract val outputDirectory: DirectoryProperty

   @TaskAction
   fun taskAction() {
      // Step 4. Generate your resources.
      ...
   }
}

Xem công thức addCustomAsset để biết thêm thông tin chi tiết.

Truy cập và chỉnh sửa cấu phần phần mềm

Ngoài việc cho phép bạn chỉnh sửa các thuộc tính đơn giản trên đối tượng Variant, AGP còn có cơ chế mở rộng cho phép đọc hoặc biến đổi các cấu phần phần mềm trung gian và cuối cùng được tạo ra trong quá trình tạo bản dựng. Ví dụ: bạn có thể đọc nội dung tệp AndroidManifest.xml cuối cùng, được hợp nhất trong Task tuỳ chỉnh để phân tích, hoặc bạn có thể thay thế hoàn toàn nội dung của tệp bằng nội dung của tệp kê khai được tạo bằng Task tuỳ chỉnh.

Bạn có thể tìm thấy danh sách các cấu phần phần mềm được hỗ trợ đến thời điểm này trong tài liệu tham khảo về lớp Artifact. Mỗi kiểu cấu phần phần mềm (artifact type) đều có một số thuộc tính hữu ích bạn nên biết như sau:

Lượng số

Lượng số của một Artifact thể hiện số lượng thực thể FileSystemLocation hoặc số lượng tệp hoặc thư mục của một kiểu cấu phần phần mềm. Bạn có thể nhận thông tin về lượng số của một cấu phần phần mềm bằng cách kiểm tra lớp cha của cấu phần phần mềm đó: Các cấu phần phần mềm có FileSystemLocation duy nhất sẽ là lớp con của Artifact.Single; cấu phần phần mềm có nhiều thực thể FileSystemLocation sẽ là một lớp con của Artifact.Multiple.

Kiểu FileSystemLocation

Bạn có thể kiểm tra xem một Artifact có đại diện cho tệp hay thư mục hay không bằng cách xem kiểu FileSystemLocation có tham số của cấu phần phần mềm này, có thể là RegularFile hoặc Directory

Thao tác được hỗ trợ

Mọi lớp Artifact đều có thể triển khai bất kỳ giao diện nào dưới đây để cho biết lớp này hỗ trợ những thao tác nào:

  • Transformable: Cho phép sử dụng Artifact làm dữ liệu đầu vào cho một Task để thực hiện các biến đổi tuỳ ý trên dữ liệu đó và tạo ra một phiên bản mới của Artifact này.
  • Appendable: Chỉ áp dụng cho các cấu phần phần mềm thuộc lớp con của Artifact.Multiple. Điều này có nghĩa rằng Artifact có thể được bổ sung thêm, tức là một Task tuỳ chỉnh có thể tạo các thực thể mới của loại Artifact này và sẽ được thêm vào danh sách hiện có.
  • Replaceable: Chỉ áp dụng cho các cấu phần phần mềm thuộc lớp con của Artifact.Single. Một Artifact có khả năng thay thế nghĩa là có thể được thay thế bằng một thực thể hoàn toàn mới, được tạo ra dưới dạng kết quả của một Task.

Ngoài 3 thao tác chỉnh sửa cấu phần phần mềm này, mỗi cấu phần phần mềm đều hỗ trợ thao tác get() (hoặc getAll()), trả về một Provider chứa phiên bản cuối cùng của cấu phần phần mềm này (sau khi hoàn tất cả thao tác trên cấu phần phần mềm này).

Nhiều trình bổ trợ có thể thêm số lượng thao tác bất kỳ trên các cấu phần phần mềm vào quy trình (pipeline) từ phương thức gọi lại onVariants(). AGP sẽ đảm bảo các thao tác đó sẽ được liên kết đúng cách để tất cả tác vụ đều chạy đúng thời điểm và các cấu phần phần mềm được tạo ra và cập nhật một cách chính xác. Điều này có nghĩa rằng khi một thao tác nào đó thay đổi thông tin đầu ra bằng cách thêm, thay thế hoặc biến đổi các đầu ra đó, thì thao tác tiếp theo sẽ thấy được phiên bản cập nhật của các cấu phần phần mềm này dưới dạng đầu vào, v.v.

Điểm truy cập để đăng ký các thao tác là lớp Artifacts. Đoạn mã sau đây hướng dẫn bạn cách truy cập một thực thể của Artifacts qua một thuộc tính trên đối tượng Variant trong phương thức gọi lại onVariants().

Sau đó, bạn có thể truyền vàoTaskProvider tuỳ chỉnh để có được đối tượng TaskBasedOperation (1) và sử dụng đối tượng đó để kết nối dữ liệu đầu vào và đầu ra của đối tượng thông qua phương thức wiredWith* (2).

Phương thức chính xác mà bạn cần chọn phụ thuộc vào lượng số và kiểu FileSystemLocation được triển khai bằng Artifact mà bạn muốn biến đổi.

Cuối cùng, bạn sẽ truyền kiểu Artifact này vào một phương thức đại diện cho thao tác đã chọn trên đối tượng *OperationRequest nhận được, ví dụ: toAppendTo(), toTransform() hoặc toCreate() (3).

androidComponents.onVariants { variant ->
    val manifestUpdater = // Custom task that will be used for the transform.
            project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
                it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
            }
    // (1) Register the TaskProvider w.
    val variant.artifacts.use(manifestUpdater)
         // (2) Connect the input and output files.
        .wiredWithFiles(
            ManifestTransformerTask::mergedManifest,
            ManifestTransformerTask::updatedManifest)
        // (3) Indicate the artifact and operation type.
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

Trong ví dụ này, MERGED_MANIFEST là một SingleArtifact và là một RegularFile. Do đó, chúng ta cần sử dụng phương thức wiredWithFilesđể chấp nhận một tham chiếu RegularFileProperty duy nhất cho dữ liệu đầu vào và một RegularFileProperty duy nhất cho kết quả đầu ra. Các phương thức wiredWith* khác trên lớp TaskBasedOperation sẽ phù hợp với các kết hợp khác từ lượng số Artifact và kiểu FileSystemLocation.

Để tìm hiểu thêm về cách mở rộng AGP, bạn nên tham khảo các phần sau trong hướng dẫn sử dụng hệ thống xây dựng Gradle: