เขียนปลั๊กอิน Gradle

ปลั๊กอิน Android Gradle (AGP) เป็นระบบบิลด์อย่างเป็นทางการสำหรับแอปพลิเคชัน Android แพลตฟอร์มนี้รองรับการคอมไพล์แหล่งที่มาประเภทต่างๆ และลิงก์เข้าด้วยกันเป็นแอปพลิเคชันที่คุณเรียกใช้บนอุปกรณ์ Android จริงหรือโปรแกรมจำลองได้

AGP มีจุดขยายสำหรับปลั๊กอินเพื่อควบคุมอินพุตการสร้างและขยายฟังก์ชันการทำงานผ่านขั้นตอนใหม่ที่ผสานรวมกับงานการสร้างมาตรฐานได้ AGP เวอร์ชันก่อนหน้าไม่มี API อย่างเป็นทางการที่แยกออกจากการใช้งานภายในอย่างชัดเจน ตั้งแต่เวอร์ชัน 7.0 AGP มีชุดAPI อย่างเป็นทางการที่เสถียรซึ่งคุณไว้วางใจได้

วงจรชีวิตของ AGP API

AGP เป็นไปตามวงจรของฟีเจอร์ Gradle เพื่อกำหนดสถานะของ API ดังนี้

  • ภายใน: ไม่ได้มีไว้สำหรับการใช้งานสาธารณะ
  • กำลังพัฒนา: พร้อมให้ใช้งานแบบสาธารณะแต่ยังไม่เป็นเวอร์ชันสุดท้าย ซึ่งหมายความว่าอาจใช้งานร่วมกับเวอร์ชันเก่าไม่ได้ในเวอร์ชันสุดท้าย
  • สาธารณะ: พร้อมให้ใช้งานแบบสาธารณะและมีความเสถียร
  • เลิกใช้งานแล้ว: ระบบไม่รองรับอีกต่อไปและมี API ใหม่เข้ามาแทนที่

นโยบายการเลิกใช้งาน

AGP มีการพัฒนาจากการเลิกใช้งาน API เก่าและการแทนที่ API ด้วย API ใหม่ที่เสถียรและ Domain Specific Language (DSL) ใหม่ การพัฒนานี้จะครอบคลุม AGP หลายรุ่น และคุณสามารถดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ลำดับเวลาการย้ายข้อมูล AGP API/DSL

เมื่อเลิกใช้งาน AGP API ไม่ว่าจะสำหรับการย้ายข้อมูลครั้งนี้หรืออื่นๆ ก็ตาม API ดังกล่าวจะยังคงใช้งานได้ในรุ่นหลักปัจจุบัน แต่จะสร้างคำเตือน เราจะนํา API ที่เลิกใช้งานแล้วออกจาก AGP โดยสมบูรณ์ในรุ่นหลักถัดไป เช่น หากเลิกใช้งาน API ใน AGP 7.0 API ดังกล่าวจะใช้งานได้ในเวอร์ชันนั้นและสร้างคำเตือน API ดังกล่าวจะไม่พร้อมใช้งานใน AGP 8.0 อีกต่อไป

หากต้องการดูตัวอย่าง API ใหม่ที่ใช้ในการปรับแต่งบิลด์ทั่วไป โปรดดูสูตรสำหรับปลั๊กอิน Gradle ของ Android ซึ่งแสดงตัวอย่างการปรับแต่งบิลด์ที่พบบ่อย นอกจากนี้ คุณยังดูรายละเอียดเพิ่มเติมเกี่ยวกับ API ใหม่ได้ในเอกสารอ้างอิง

ข้อมูลเบื้องต้นเกี่ยวกับการสร้างของ Gradle

คำแนะนำนี้ไม่ได้ครอบคลุมระบบการสร้าง Gradle ทั้งหมด อย่างไรก็ตาม เอกสารประกอบนี้ครอบคลุมชุดแนวคิดที่จําเป็นขั้นต่ำเพื่อช่วยให้คุณผสานรวมกับ API ของเราได้ และลิงก์ไปยังเอกสารประกอบหลักของ Gradle เพื่ออ่านเพิ่มเติม

เราคาดเดาความรู้พื้นฐานเกี่ยวกับวิธีการทำงานของ Gradle ซึ่งรวมถึงวิธีกำหนดค่าโปรเจ็กต์ แก้ไขไฟล์บิลด์ ใช้ปลั๊กอิน และเรียกใช้งาน หากต้องการดูข้อมูลเบื้องต้นเกี่ยวกับ Gradle ที่เกี่ยวข้องกับ AGP เราขอแนะนําให้ดูกําหนดค่าการสร้าง ดูข้อมูลเกี่ยวกับเฟรมเวิร์กทั่วไปสำหรับการปรับแต่งปลั๊กอิน Gradle ได้ที่หัวข้อการพัฒนาปลั๊กอิน Gradle ที่กําหนดเอง

อภิธานศัพท์เกี่ยวกับประเภทแบบ Lazy ของ Gradle

Gradle มีประเภทต่างๆ ที่ทำงานแบบ "ขี้เกียจ" หรือช่วยเลื่อนการคำนวณที่หนักหน่วงหรือการสร้าง Task ไปไว้ในช่วงหลังของการสร้าง ประเภทเหล่านี้เป็นหัวใจสำคัญของ Gradle และ AGP API หลายรายการ รายการต่อไปนี้มีประเภท Gradle หลักๆ ที่เกี่ยวข้องกับการดำเนินการแบบ Lazy รวมถึงเมธอดคีย์ของประเภทนั้นๆ

Provider<T>
ให้ค่าประเภท T (โดยที่ "T" หมายถึงประเภทใดก็ได้) ซึ่งสามารถอ่านได้ในระหว่างระยะการดําเนินการโดยใช้ get() หรือเปลี่ยนรูปแบบเป็น Provider<S> ใหม่ (โดยที่ "S" หมายถึงประเภทอื่น) โดยใช้เมธอด map(), flatMap() และ zip() โปรดทราบว่าไม่ควรเรียก get() ในระหว่างระยะการกําหนดค่า
  • map(): รับ lambda และสร้าง Provider ประเภท S, Provider<S> อาร์กิวเมนต์ lambda ของ map() จะรับค่า T และสร้างค่า S ระบบจะไม่เรียกใช้ Lambda ทันที แต่จะใช้เมื่อมีการเรียกใช้ get() ใน Provider<S> ที่ได้
  • flatMap(): ยอมรับ lambda และสร้าง Provider<S> ด้วย แต่ lambda จะใช้ค่า T และให้ผลลัพธ์ Provider<S> (แทนที่จะสร้างค่า S โดยตรง) ใช้ flatMap() เมื่อไม่สามารถระบุ S ได้ในเวลาที่กําหนดค่า และคุณได้รับได้เฉพาะ Provider<S> ในทางปฏิบัติแล้ว หากคุณใช้ map() และได้ผลลัพธ์ประเภท Provider<Provider<S>> แสดงว่าคุณควรใช้ flatMap() แทน
  • zip(): ให้คุณรวมอินสแตนซ์ Provider 2 อินสแตนซ์เพื่อสร้าง Provider ใหม่ ด้วยค่าที่คำนวณโดยใช้ฟังก์ชันที่รวมค่าจากอินสแตนซ์อินพุต Providers 2 รายการ
Property<T>
ใช้ Provider<T> จึงให้ค่าประเภท T ด้วย คุณยังกำหนดค่าสำหรับ Property<T> ได้ ซึ่งต่างจาก Provider<T> ซึ่งเป็นแบบอ่านอย่างเดียว ซึ่งทำได้ 2 วิธีดังนี้
  • ตั้งค่าประเภท T โดยตรงเมื่อสามารถใช้ได้ โดยไม่ต้องคำนวณแบบหน่วงเวลา
  • ตั้งค่า Provider<T> อื่นเป็นแหล่งที่มาของค่า Property<T> ในกรณีนี้ ค่า T จะปรากฏขึ้นก็ต่อเมื่อมีการเรียกใช้ Property.get() เท่านั้น
TaskProvider
ใช้งาน Provider<Task> หากต้องการสร้าง TaskProvider ให้ใช้ tasks.register() ไม่ใช่ tasks.create() เพื่อให้ระบบสร้างอินสแตนซ์งานแบบ Lazy เฉพาะเมื่อจําเป็นเท่านั้น คุณสามารถใช้ flatMap() เพื่อเข้าถึงเอาต์พุตของ Task ก่อนที่จะสร้าง Task ซึ่งจะมีประโยชน์หากคุณต้องการใช้เอาต์พุตเป็นอินพุตสำหรับอินสแตนซ์ Task อื่นๆ

ผู้ให้บริการและวิธีการเปลี่ยนรูปแบบของผู้ให้บริการมีความสําคัญในการตั้งค่าอินพุตและเอาต์พุตของงานในลักษณะที่ไม่ต้องสร้างงานทั้งหมดล่วงหน้าและแก้ไขค่า

นอกจากนี้ ผู้ให้บริการยังมีข้อมูลการอ้างอิงของงานด้วย เมื่อคุณสร้าง Provider ด้วยการแปลงเอาต์พุต Task นั้น Task จะกลายเป็นทรัพยากร Dependency แบบโดยนัยของ Provider โดยระบบจะสร้างและเรียกใช้เมื่อใดก็ตามที่มีการแก้ไขค่าของ Provider เช่น เมื่อ Task อื่นต้องการ

ต่อไปนี้คือตัวอย่างการลงทะเบียนงาน 2 รายการ ได้แก่ GitVersionTask และ ManifestProducerTask พร้อมกับเลื่อนการสร้างอินสแตนซ์ Task จนกว่าจะจําเป็นต้องใช้ ค่าอินพุต ManifestProducerTask ได้รับการตั้งค่าเป็น Provider ซึ่งได้มาจากเอาต์พุตของ GitVersionTask ดังนั้น ManifestProducerTask จึงขึ้นอยู่กับ 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 รายการนี้จะทำงานก็ต่อเมื่อมีคำขออย่างชัดเจนเท่านั้น กรณีนี้อาจเกิดขึ้นเป็นส่วนหนึ่งของการเรียกใช้ Gradle เช่น หากคุณเรียกใช้ ./gradlew debugManifestProducer หรือหากเอาต์พุตของ ManifestProducerTask เชื่อมต่อกับงานอื่นๆ บางงานและจำเป็นต้องมีค่าของงานดังกล่าว

แม้ว่าคุณจะเขียนงานที่กำหนดเองซึ่งต้องใช้อินพุตและ/หรือสร้างเอาต์พุต แต่ AGP ไม่ได้ให้สิทธิ์เข้าถึงแบบสาธารณะโดยตรง รายละเอียดการใช้งาน อาจเปลี่ยนแปลงได้ในแต่ละเวอร์ชัน แต่ AGP จะเสนอ Variant API และสิทธิ์เข้าถึงเอาต์พุตของงานหรืออาร์ติแฟกต์การสร้างที่คุณสามารถอ่านและเปลี่ยนรูปแบบได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับVariant API, อาร์ติแฟกต์ และ Tasks ในเอกสารนี้

เฟสการสร้าง Gradle

การสร้างโปรเจ็กต์เป็นกระบวนการที่ซับซ้อนและต้องใช้ทรัพยากรโดยเนื้อแท้ และยังมีฟีเจอร์ต่างๆ เช่น การหลีกเลี่ยงการกำหนดค่างาน การตรวจสอบข้อมูลล่าสุด และฟีเจอร์การแคชการกำหนดค่า ซึ่งช่วยประหยัดเวลาในการคํานวณที่ซ้ำกันหรือไม่จําเป็น

หากต้องการใช้การเพิ่มประสิทธิภาพเหล่านี้ สคริปต์และปลั๊กอิน Gradle ต้องปฏิบัติตามกฎที่เข้มงวดในแต่ละระยะการสร้าง Gradle ซึ่งได้แก่ ระยะเริ่มต้น การกำหนดค่า และการดำเนินการ ในคู่มือนี้ เราจะมุ่งเน้นไปที่การกำหนดค่าและระยะการดําเนินการ ดูข้อมูลเพิ่มเติมเกี่ยวกับระยะต่างๆ ทั้งหมดได้ในคู่มือวงจรการสร้าง Gradle

ระยะการกําหนดค่า

ในระหว่างขั้นตอนการกําหนดค่า ระบบจะประเมินสคริปต์บิลด์สําหรับโปรเจ็กต์ทั้งหมดที่เป็นส่วนหนึ่งของบิลด์ ใช้ปลั๊กอิน และแก้ไขข้อกําหนดของบิลด์ ควรใช้ระยะนี้เพื่อกำหนดค่าบิลด์โดยใช้ออบเจ็กต์ DSL และเพื่อลงทะเบียนงานและอินพุตแบบล่าช้า

เนื่องจากเฟสการกําหนดค่าจะทํางานอยู่เสมอ ไม่ว่าระบบจะขอให้เรียกใช้งานใดก็ตาม จึงมีความสําคัญอย่างยิ่งที่จะต้องทําให้เฟสนี้ทำงานได้อย่างรวดเร็วและจำกัดการคำนวณไม่ให้ขึ้นอยู่กับอินพุตอื่นนอกเหนือจากสคริปต์บิลด์ กล่าวคือ คุณไม่ควรเรียกใช้โปรแกรมภายนอกหรืออ่านจากเครือข่าย หรือดำเนินการคํานวณที่ใช้เวลานานซึ่งสามารถเลื่อนไปไว้ที่ระยะการดําเนินการเป็นอินสแตนซ์Taskที่เหมาะสม

ระยะการดําเนินการ

ในเฟสการดำเนินการ ระบบจะดำเนินการกับงานที่ขอและงานที่ขึ้นอยู่กับงานดังกล่าว กล่าวโดยละเอียดคือ ระบบจะเรียกใช้เมธอดของคลาส Task ที่มีเครื่องหมาย @TaskAction ในระหว่างการดําเนินการของงาน คุณจะอ่านข้อมูลจากอินพุต (เช่น ไฟล์) และแก้ไขผู้ให้บริการแบบ Lazy ได้โดยเรียกใช้ Provider<T>.get() การแก้ไขผู้ให้บริการแบบ Lazy ด้วยวิธีนี้จะเริ่มต้นการเรียก map() หรือ flatMap() ตามลำดับตามข้อมูลการพึ่งพางานที่อยู่ในผู้ให้บริการ ระบบจะเรียกใช้งานแบบ Lazy เพื่อแสดงค่าที่จำเป็น

API, อาร์ติแฟกต์ และ Tasks ของตัวแปร

Variant API เป็นกลไกส่วนขยายในปลั๊กอิน Gradle ของ Android ที่ช่วยให้คุณจัดการตัวเลือกต่างๆ ซึ่งปกติจะตั้งค่าโดยใช้ DSL ในไฟล์การกําหนดค่าบิลด์ที่ส่งผลต่อบิลด์ Android ได้ Variant API ยังให้คุณเข้าถึงอาร์ติแฟกต์ขั้นกลางและขั้นสุดท้ายที่บิลด์สร้างขึ้น เช่น ไฟล์คลาส, ไฟล์ Manifest ที่ผสาน หรือไฟล์ APK/AAB

ขั้นตอนการบิลด์ Android และจุดขยาย

เมื่อโต้ตอบกับ AGP ให้ใช้จุดขยายที่สร้างขึ้นเป็นพิเศษแทนการลงทะเบียนการเรียกกลับวงจรชีวิตของ Gradle ทั่วไป (เช่น afterEvaluate()) หรือการตั้งค่าการพึ่งพา Task ที่ชัดเจน งานที่สร้างโดย AGP ถือเป็นรายละเอียดการใช้งานและไม่เปิดเผยเป็น API สาธารณะ คุณต้องหลีกเลี่ยงการพยายามรับอินสแตนซ์ของออบเจ็กต์ Task หรือคาดเดาชื่อ Task และเพิ่ม Callback หรือการ Dependency ไปยังออบเจ็กต์ Task เหล่านั้นโดยตรง

AGP จะทําตามขั้นตอนต่อไปนี้เพื่อสร้างและเรียกใช้อินสแตนซ์ Task ซึ่งจะสร้างอาร์ติแฟกต์การสร้าง ขั้นตอนหลักที่เกี่ยวข้องกับการสร้างออบเจ็กต์ Variant ตามด้วย Callback ที่ช่วยให้คุณเปลี่ยนแปลงออบเจ็กต์บางรายการที่สร้างขึ้นเป็นส่วนหนึ่งของบิลด์ได้ โปรดทราบว่าการเรียกกลับทั้งหมดจะเกิดขึ้นในระยะการกําหนดค่า (อธิบายไว้ในหน้านี้) และต้องทํางานอย่างรวดเร็ว โดยเลื่อนงานที่ซับซ้อนไปยังอินสแตนซ์ Task ที่เหมาะสมในเฟสการดําเนินการแทน

  1. การแยกวิเคราะห์ DSL: ขั้นตอนนี้จะประเมินสคริปต์บิลด์ และสร้างและตั้งค่าพร็อพเพอร์ตี้ต่างๆ ของออบเจ็กต์ DSL ของ Android จากบล็อก android นอกจากนี้ ระบบจะลงทะเบียนการเรียกกลับ Variant API ที่อธิบายไว้ในส่วนต่อไปนี้ด้วย
  2. finalizeDsl(): การเรียกกลับที่ช่วยให้คุณเปลี่ยนออบเจ็กต์ DSL ได้ก่อนที่จะมีการล็อกออบเจ็กต์ดังกล่าวไม่ให้สร้างคอมโพเนนต์ (ตัวแปร) ระบบจะสร้างออบเจ็กต์ VariantBuilder โดยอิงตามข้อมูลที่มีอยู่ในออบเจ็กต์ DSL

  3. การล็อก DSL: ขณะนี้ระบบล็อก DSL และไม่สามารถเปลี่ยนแปลงได้

  4. beforeVariants(): Callback นี้อาจส่งผลต่อการสร้างคอมโพเนนต์และพร็อพเพอร์ตี้บางรายการผ่าน VariantBuilder แต่ยังคงอนุญาตให้แก้ไขขั้นตอนการสร้างและอาร์ติแฟกต์ที่สร้างขึ้น

  5. การสร้างตัวแปร: รายการคอมโพเนนต์และอาร์ติแฟกต์ที่จะสร้างขึ้นได้รับการสรุปแล้วและไม่สามารถเปลี่ยนแปลงได้

  6. onVariants(): ใน Callback นี้ คุณจะเข้าถึงออบเจ็กต์ Variant ที่สร้างขึ้นและกําหนดค่าหรือผู้ให้บริการสําหรับค่า Property ที่มีเพื่อคํานวณแบบ Lazy Loading ได้

  7. การล็อกตัวแปร: ตอนนี้ออบเจ็กต์ตัวแปรจะล็อกอยู่และคุณจะเปลี่ยนแปลงไม่ได้อีกต่อไป

  8. งานที่สร้างขึ้น: ระบบจะใช้ออบเจ็กต์ Variant และค่า Property เพื่อสร้างอินสแตนซ์ Task ที่จําเป็นสําหรับการสร้าง

AGP เปิดตัว AndroidComponentsExtension ที่ช่วยให้คุณลงทะเบียนการเรียกกลับสําหรับ finalizeDsl(), beforeVariants() และ onVariants() ได้ ส่วนขยายมีอยู่ในสคริปต์บิลด์ผ่านทางบล็อก androidComponents:

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

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

อย่างไรก็ตาม เราขอแนะนำให้ใช้สคริปต์บิลด์สำหรับการกำหนดค่าแบบประกาศโดยใช้ DSL ของบล็อก android เท่านั้น และย้ายตรรกะคำสั่งที่กําหนดเองไปยัง buildSrc หรือปลั๊กอินภายนอก นอกจากนี้ คุณยังดูbuildSrcตัวอย่างในที่เก็บ GitHub ของสูตร Gradle เพื่อดูวิธีสร้างปลั๊กอินในโปรเจ็กต์ได้ด้วย ตัวอย่างการลงทะเบียนการเรียกกลับจากโค้ดปลั๊กอินมีดังนี้

abstract class ExamplePlugin: Plugin<Project> {

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

มาดูรายละเอียดของคอลแบ็กที่ใช้ได้และประเภทกรณีการใช้งานที่ปลั๊กอินรองรับในแต่ละรายการกัน

finalizeDsl(callback: (DslExtensionT) -> Unit)

ในการเรียกกลับนี้ คุณจะเข้าถึงและแก้ไขออบเจ็กต์ DSL ที่สร้างขึ้นโดยการแยกวิเคราะห์ข้อมูลจากบล็อก android ในไฟล์บิลด์ได้ ระบบจะใช้ออบเจ็กต์ DSL เหล่านี้เพื่อเริ่มต้นและกำหนดค่าตัวแปรในระยะถัดไปของการสร้าง ตัวอย่างเช่น คุณสามารถสร้างการกำหนดค่าใหม่หรือลบล้างพร็อพเพอร์ตี้แบบเป็นโปรแกรมได้ แต่โปรดทราบว่าค่าทั้งหมดต้องได้รับการแก้ไข ณ เวลาการกําหนดค่า จึงต้องไม่ใช้อินพุตภายนอก หลังจากการเรียกกลับนี้ดำเนินการเสร็จแล้ว ออบเจ็กต์ DSL จะใช้งานไม่ได้อีกต่อไป และคุณไม่ควรเก็บการอ้างอิงหรือแก้ไขค่าของออบเจ็กต์เหล่านั้นอีก

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

ในขั้นตอนนี้ของบิลด์ คุณจะมีสิทธิ์เข้าถึงออบเจ็กต์ VariantBuilder ซึ่งจะระบุตัวแปรที่จะสร้างขึ้นและพร็อพเพอร์ตี้ของออบเจ็กต์ เช่น คุณสามารถปิดใช้ตัวแปรบางรายการ การทดสอบของตัวแปร หรือเปลี่ยนค่าของพร็อพเพอร์ตี้ (เช่น minSdk) โดยใช้โปรแกรมได้เฉพาะกับตัวแปรที่เลือกเท่านั้น เช่นเดียวกับ finalizeDsl() ค่าทั้งหมดที่คุณระบุต้องได้รับการแก้ไข ณ เวลาการกําหนดค่าและไม่ต้องอาศัยอินพุตภายนอก ต้องไม่มีการแก้ไขออบเจ็กต์ VariantBuilder เมื่อการเรียกใช้ Callback beforeVariants() เสร็จสิ้น

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

ฟังก์ชัน Callback ของ beforeVariants() จะรับ VariantSelector (ไม่บังคับ) ซึ่งคุณรับได้จากเมธอด selector() ใน androidComponentsExtension ซึ่งคุณสามารถใช้เพื่อกรองคอมโพเนนต์ที่เข้าร่วมการเรียกใช้ Callback ตามชื่อ ประเภทบิลด์ หรือรสชาติของผลิตภัณฑ์ได้

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

onVariants()

เมื่อเรียกใช้ onVariants() ระบบจะตัดสินใจเกี่ยวกับอาร์ติแฟกต์ทั้งหมดที่จะสร้างโดย AGPT ไว้แล้ว คุณจึงปิดใช้อาร์ติแฟกต์เหล่านั้นไม่ได้อีก อย่างไรก็ตาม คุณสามารถแก้ไขค่าบางอย่างที่ใช้สำหรับงานได้โดยกำหนดค่าสำหรับแอตทริบิวต์ Property ในออบเจ็กต์ Variant เนื่องจากค่า Property จะได้รับการแก้ไขเมื่อมีการทำงานของ AGP เท่านั้น คุณจึงเชื่อมต่อค่ากับผู้ให้บริการจากงานที่คุณกำหนดเองได้อย่างปลอดภัย ซึ่งจะดำเนินการคำนวณที่จำเป็น รวมถึงการอ่านจากอินพุตภายนอก เช่น ไฟล์หรือเครือข่าย

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

เพิ่มแหล่งที่มาที่สร้างขึ้นลงในบิลด์

ปลั๊กอินของคุณสามารถส่งแหล่งที่มาที่สร้างขึ้นได้ 2-3 ประเภท เช่น

ดูรายการแหล่งที่มาทั้งหมดที่เพิ่มได้ได้ที่ Sources API

ข้อมูลโค้ดนี้แสดงวิธีเพิ่มโฟลเดอร์แหล่งที่มาที่กำหนดเองชื่อ ${variant.name} ไปยังชุดแหล่งที่มาของ Java โดยใช้ฟังก์ชัน addStaticSourceDirectory() จากนั้นเครื่องมือชุดค่าผสมของ Android จะประมวลผลโฟลเดอร์นี้

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

ดูรายละเอียดเพิ่มเติมได้ในสูตร addJavaSource

ข้อมูลโค้ดนี้แสดงวิธีเพิ่มไดเรกทอรีที่มีทรัพยากร Android ที่สร้างขึ้นจากงานที่กำหนดเองไปยังชุดแหล่งที่มา res กระบวนการนี้คล้ายกับแหล่งที่มาประเภทอื่นๆ

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

ดูรายละเอียดเพิ่มเติมได้ที่สูตร addCustomAsset

เข้าถึงและแก้ไขอาร์ติแฟกต์

นอกจากจะช่วยให้คุณแก้ไขพร็อพเพอร์ตี้ง่ายๆ ในออบเจ็กต์ Variant แล้ว AGP ยังมีกลไกส่วนขยายที่ช่วยให้คุณอ่านหรือเปลี่ยนรูปแบบอาร์ติแฟกต์ขั้นกลางและขั้นสุดท้ายที่สร้างขึ้นในระหว่างการบิลด์ได้ด้วย เช่น คุณสามารถอ่านเนื้อหาไฟล์ AndroidManifest.xml ที่ผสานแล้วใน Task ที่กําหนดเองเพื่อวิเคราะห์ หรือแทนที่เนื้อหาทั้งหมดด้วยเนื้อหาของไฟล์ Manifest ที่สร้างขึ้นโดย Task ที่กําหนดเอง

คุณดูรายการอาร์ติแฟกต์ที่รองรับในปัจจุบันได้ในเอกสารอ้างอิงสำหรับคลาส Artifact อาร์ติแฟกต์ทุกประเภทมีพร็อพเพอร์ตี้บางอย่างที่ควรทราบ ดังนี้

Cardinality

Cardinality ของ Artifact แสดงจํานวนอินสแตนซ์ FileSystemLocation หรือจํานวนไฟล์หรือไดเรกทอรีของประเภทอาร์ติแฟกต์ คุณสามารถรับข้อมูลเกี่ยวกับ Cardinality ของอาร์ติแฟกต์ได้โดยตรวจสอบคลาสหลักของอาร์ติแฟกต์ อาร์ติแฟกต์ที่มี FileSystemLocation รายการเดียวจะเป็นคลาสย่อยของ Artifact.Single ส่วนอาร์ติแฟกต์ที่มีอินสแตนซ์ FileSystemLocation หลายรายการจะเป็นคลาสย่อยของ Artifact.Multiple

FileSystemLocation ประเภท

คุณสามารถตรวจสอบว่า Artifact แสดงไฟล์หรือไดเรกทอรีหรือไม่โดยดูที่ประเภท FileSystemLocation แบบพารามิเตอร์ ซึ่งอาจเป็น RegularFile หรือ Directory

การดำเนินการที่รองรับ

คลาส Artifact ทุกคลาสสามารถใช้อินเทอร์เฟซต่อไปนี้เพื่อระบุการดำเนินการที่รองรับ

  • Transformable: อนุญาตให้ใช้ Artifact เป็นอินพุตของ Task ที่มีการเปลี่ยนรูปแบบที่กําหนดเองและเอาต์พุต Artifact เวอร์ชันใหม่
  • Appendable: ใช้กับอาร์ติแฟกต์ที่เป็นคลาสย่อยของ Artifact.Multiple เท่านั้น ซึ่งหมายความว่าสามารถต่อท้าย Artifact ได้ กล่าวคือ Task ที่กําหนดเองสามารถสร้างอินสแตนซ์ใหม่ของ Artifact ประเภทนี้ซึ่งจะเพิ่มลงในรายการที่มีอยู่
  • Replaceable: ใช้กับอาร์ติแฟกต์ที่เป็นคลาสย่อยของ Artifact.Single เท่านั้น Artifact ที่เปลี่ยนทดแทนได้สามารถแทนที่ด้วยอินสแตนซ์ใหม่ทั้งหมดที่สร้างขึ้นจากเอาต์พุตของ Task

นอกเหนือจากการแก้ไขอาร์ติแฟกต์ 3 อย่างแล้ว อาร์ติแฟกต์ทุกรายการยังรองรับการดำเนินการ get() (หรือ getAll()) ซึ่งจะแสดง Provider ที่มีอาร์ติแฟกต์เวอร์ชันสุดท้าย (หลังการดำเนินการทั้งหมดเสร็จสิ้น)

ปลั๊กอินหลายรายการสามารถเพิ่มการดำเนินการกับอาร์ติแฟกต์ลงในไปป์ไลน์ได้เท่าใดก็ได้จาก onVariants() callback และ AGP จะตรวจสอบว่ามีการเชื่อมโยงอย่างถูกต้องเพื่อให้งานทั้งหมดทำงานในเวลาที่เหมาะสม และสร้างและอัปเดตอาร์ติแฟกต์อย่างถูกต้อง ซึ่งหมายความว่าเมื่อการดำเนินการเปลี่ยนเอาต์พุตโดยการเพิ่ม แทนที่ หรือเปลี่ยนรูปแบบเอาต์พุต การดำเนินการถัดไปจะเห็นเวอร์ชันอัปเดตของอาร์ติแฟกต์เหล่านี้เป็นอินพุต เป็นต้น

จุดแรกเข้าในการลงทะเบียนการดำเนินการคือคลาส Artifacts ข้อมูลโค้ดต่อไปนี้แสดงวิธีเข้าถึงอินสแตนซ์ของ Artifacts จากพร็อพเพอร์ตี้บนออบเจ็กต์ Variant ใน onVariants() callback

จากนั้นคุณสามารถส่ง TaskProvider ที่กําหนดเองเพื่อรับออบเจ็กต์ TaskBasedOperation (1) และใช้เพื่อเชื่อมต่ออินพุตและเอาต์พุตโดยใช้เมธอด wiredWith* อย่างใดอย่างหนึ่ง (2)

วิธีที่แน่นอนที่คุณต้องเลือกขึ้นอยู่กับ Cardinality และประเภท FileSystemLocation ที่ Artifact ที่คุณต้องการเปลี่ยนรูปแบบ

และสุดท้าย คุณจะต้องส่งประเภท Artifact ไปยังเมธอดที่แสดงถึงการดำเนินการที่เลือกในออบเจ็กต์ *OperationRequest ที่คุณจะได้รับ เช่น toAppendTo(), toTransform() หรือ 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)
}

ในตัวอย่างนี้ MERGED_MANIFEST เป็น SingleArtifact และเป็น RegularFile ด้วยเหตุนี้ เราจึงต้องใช้เมธอด wiredWithFiles ซึ่งรับการอ้างอิง RegularFileProperty รายการเดียวสำหรับอินพุต และ RegularFileProperty รายการเดียวสำหรับเอาต์พุต มีเมธอด wiredWith* อื่นๆ ในคลาส TaskBasedOperation ที่จะทํางานกับชุดค่าผสมอื่นๆ ของ Cardinality Artifact และประเภท FileSystemLocation

หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการขยาย AGP เราขอแนะนําให้อ่านส่วนต่อไปนี้จากคู่มือระบบการบิลด์ Gradle