پلاگین های Gradle را بنویسید

افزونه‌ی اندروید گریدل (AGP) سیستم ساخت رسمی برای برنامه‌های اندروید است. این افزونه از کامپایل انواع مختلف منابع و پیوند دادن آنها به یکدیگر در قالب یک برنامه که می‌توانید روی یک دستگاه اندروید فیزیکی یا یک شبیه‌ساز اجرا کنید، پشتیبانی می‌کند.

AGP شامل نقاط توسعه‌ای برای افزونه‌ها است تا ورودی‌های ساخت را کنترل کرده و عملکرد آن را از طریق مراحل جدیدی که می‌توانند با وظایف ساخت استاندارد ادغام شوند، گسترش دهند. نسخه‌های قبلی AGP دارای APIهای رسمی نبودند که به وضوح از پیاده‌سازی‌های داخلی جدا باشند. از نسخه ۷.۰ به بعد، AGP مجموعه‌ای از APIهای رسمی و پایدار دارد که می‌توانید به آنها اعتماد کنید.

چرخه عمر AGP API

AGP برای تعیین وضعیت APIهای خود، از چرخه حیات ویژگی Gradle پیروی می‌کند:

  • داخلی : برای استفاده عمومی در نظر گرفته نشده است
  • در حال رشد : برای استفاده عمومی در دسترس است اما نسخه نهایی نیست، به این معنی که ممکن است در نسخه نهایی با نسخه‌های قبلی سازگار نباشند.
  • عمومی : برای استفاده عمومی در دسترس و پایدار است
  • منسوخ شده : دیگر پشتیبانی نمی‌شود و با APIهای جدید جایگزین شده است

سیاست استهلاک

AGP با منسوخ شدن APIهای قدیمی و جایگزینی آنها با APIهای جدید و پایدار و یک زبان خاص دامنه (DSL) جدید در حال تکامل است. این تکامل شامل چندین نسخه AGP خواهد بود و می‌توانید اطلاعات بیشتر در مورد آن را در جدول زمانی مهاجرت AGP API/DSL بیابید.

وقتی APIهای AGP منسوخ می‌شوند، چه برای این مهاجرت و چه برای موارد دیگر، در نسخه اصلی فعلی همچنان در دسترس خواهند بود اما هشدارهایی ایجاد می‌کنند. APIهای منسوخ‌شده در نسخه اصلی بعدی به‌طور کامل از AGP حذف می‌شوند. به‌عنوان مثال، اگر یک API در AGP 7.0 منسوخ شود، در آن نسخه در دسترس خواهد بود و هشدارهایی ایجاد می‌کند. آن API دیگر در AGP 8.0 در دسترس نخواهد بود.

برای دیدن نمونه‌هایی از APIهای جدید مورد استفاده در سفارشی‌سازی‌های رایج ساخت، به دستورالعمل‌های افزونه‌ی Android Gradle نگاهی بیندازید. آن‌ها نمونه‌هایی از سفارشی‌سازی‌های رایج ساخت را ارائه می‌دهند. همچنین می‌توانید جزئیات بیشتری در مورد APIهای جدید را در مستندات مرجع ما بیابید.

اصول اولیه ساخت Gradle

این راهنما کل سیستم ساخت Gradle را پوشش نمی‌دهد. با این حال، حداقل مجموعه مفاهیم لازم برای کمک به شما در ادغام با API های ما را پوشش می‌دهد و برای مطالعه بیشتر به مستندات اصلی Gradle پیوند می‌دهد.

ما فرض را بر این می‌گذاریم که دانش اولیه‌ای در مورد نحوه‌ی کار Gradle، از جمله نحوه‌ی پیکربندی پروژه‌ها، ویرایش فایل‌های ساخت، اعمال افزونه‌ها و اجرای وظایف، داریم. برای آشنایی با اصول اولیه‌ی Gradle در رابطه با AGP، توصیه می‌کنیم بخش Configure your build را بررسی کنید. برای آشنایی با چارچوب کلی سفارشی‌سازی افزونه‌های Gradle، به بخش توسعه‌ی افزونه‌های سفارشی Gradle مراجعه کنید.

واژه‌نامه‌ی انواع lazy در Gradle

Gradle تعدادی نوع داده ارائه می‌دهد که به صورت «تنبل» رفتار می‌کنند، یا به تعویق انداختن محاسبات سنگین یا ایجاد Task به مراحل بعدی ساخت کمک می‌کنند. این نوع داده‌ها در هسته بسیاری از APIهای Gradle و AGP قرار دارند. لیست زیر شامل انواع داده اصلی Gradle درگیر در اجرای تنبل و متدهای کلیدی آنها است.

Provider<T>
مقداری از نوع T (که "T" به معنی هر نوعی است) ارائه می‌دهد که می‌تواند در طول مرحله اجرا با استفاده از get() خوانده شود یا با استفاده از متدهای map() ، flatMap() و zip() به یک Provider<S> جدید تبدیل شود (که "S" به معنی نوع دیگری است). توجه داشته باشید که get() هرگز نباید در طول مرحله پیکربندی فراخوانی شود.
  • map() : یک لامبدا را می‌پذیرد و یک Provider از نوع S ، Provider<S> ، تولید می‌کند. آرگومان لامبدا برای map() مقدار T را می‌گیرد و مقدار S را تولید می‌کند. لامبدا بلافاصله اجرا نمی‌شود؛ در عوض، اجرای آن به لحظه فراخوانی get() روی Provider<S> حاصل موکول می‌شود و کل زنجیره را تنبل می‌کند.
  • flatMap() : این تابع نیز یک لامبدا را می‌پذیرد و Provider<S> تولید می‌کند، اما لامبدا مقدار T را می‌گیرد و Provider<S> را تولید می‌کند (به جای اینکه مستقیماً مقدار S را تولید کند). از flatMap() زمانی استفاده کنید که S در زمان پیکربندی قابل تعیین نباشد و فقط بتوانید Provider<S> را دریافت کنید. در عمل، اگر از map() استفاده کرده‌اید و در نهایت به نوع نتیجه Provider<Provider<S>> رسیده‌اید، احتمالاً به این معنی است که باید به جای آن از flatMap() استفاده می‌کردید.
  • zip() : به شما امکان می‌دهد دو نمونه Provider برای تولید یک Provider جدید ترکیب کنید، که مقدار آن با استفاده از تابعی که مقادیر دو نمونه Providers ورودی را ترکیب می‌کند، محاسبه می‌شود.
Property<T>
Provider<T> را پیاده‌سازی می‌کند، بنابراین مقداری از نوع T را نیز ارائه می‌دهد. برخلاف Provider<T> که فقط خواندنی است، می‌توانید برای Property<T> نیز مقداری تعیین کنید. دو راه برای انجام این کار وجود دارد:
  • مقداری از نوع T را مستقیماً در صورت موجود بودن، بدون نیاز به محاسبات معوق، تنظیم کنید.
  • یک Provider<T> دیگر را به عنوان منبع مقدار Property<T> تنظیم کنید. در این حالت، مقدار T فقط زمانی که Property.get() فراخوانی شود، محقق می‌شود.
TaskProvider
Provider<Task> را پیاده‌سازی می‌کند. برای تولید یک TaskProvider ، از tasks.register() استفاده کنید و نه tasks.create() تا مطمئن شوید که وظایف فقط زمانی که مورد نیاز هستند، به صورت تنبل (lazyly) نمونه‌سازی می‌شوند. می‌توانید از flatMap() برای دسترسی به خروجی‌های یک Task قبل از ایجاد Task استفاده کنید، که اگر می‌خواهید از خروجی‌ها به عنوان ورودی برای سایر نمونه‌های Task استفاده کنید، می‌تواند مفید باشد.

ارائه‌دهندگان و روش‌های تبدیل آنها برای تنظیم ورودی‌ها و خروجی‌های وظایف به روش تنبل، یعنی بدون نیاز به ایجاد همه وظایف از قبل و حل مقادیر، ضروری هستند.

ارائه‌دهندگان (Providers) همچنین اطلاعات وابستگی وظیفه (task dependency) را حمل می‌کنند. وقتی با تبدیل خروجی یک Task ، یک Provider (Provider) ایجاد می‌کنید، آن Task به یک وابستگی ضمنی از Provider تبدیل می‌شود و هر زمان که مقدار Provider تعیین شود، مانند زمانی که Task دیگری به آن نیاز داشته باشد، ایجاد و اجرا خواهد شد.

در اینجا مثالی از ثبت دو وظیفه، 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))
    }

این دو وظیفه فقط در صورتی اجرا می‌شوند که صریحاً درخواست شوند. این می‌تواند به عنوان بخشی از فراخوانی Gradle اتفاق بیفتد، برای مثال، اگر ./gradlew debugManifestProducer اجرا کنید، یا اگر خروجی ManifestProducerTask به وظیفه دیگری متصل شده و مقدار آن مورد نیاز شود.

در حالی که شما وظایف سفارشی خواهید نوشت که ورودی‌ها را مصرف می‌کنند و/یا خروجی تولید می‌کنند، AGP دسترسی عمومی مستقیم به وظایف خود را ارائه نمی‌دهد. آنها جزئیات پیاده‌سازی هستند که از نسخه‌ای به نسخه دیگر قابل تغییر هستند. در عوض، AGP API نوع و دسترسی به خروجی وظایف خود را ارائه می‌دهد، یا مصنوعاتی می‌سازد که می‌توانید آنها را بخوانید و تبدیل کنید. برای اطلاعات بیشتر به API نوع، مصنوعات و وظایف در این سند مراجعه کنید.

مراحل ساخت گریدل

ساخت یک پروژه ذاتاً یک فرآیند پیچیده و نیازمند منابع است و ویژگی‌های مختلفی مانند اجتناب از پیکربندی وظایف، بررسی‌های به‌روز و ویژگی ذخیره‌سازی پیکربندی وجود دارد که به حداقل رساندن زمان صرف شده برای محاسبات تکرارپذیر یا غیرضروری کمک می‌کند.

برای اعمال برخی از این بهینه‌سازی‌ها، اسکریپت‌ها و افزونه‌های Gradle باید در طول هر یک از مراحل ساخت Gradle: مقداردهی اولیه، پیکربندی و اجرا، از قوانین سختگیرانه‌ای پیروی کنند. در این راهنما، ما بر مراحل پیکربندی و اجرا تمرکز خواهیم کرد. می‌توانید اطلاعات بیشتر در مورد تمام مراحل را در راهنمای چرخه عمر ساخت Gradle بیابید.

مرحله پیکربندی

در طول مرحله پیکربندی، اسکریپت‌های ساخت برای تمام پروژه‌هایی که بخشی از ساخت هستند ارزیابی می‌شوند، افزونه‌ها اعمال می‌شوند و وابستگی‌های ساخت برطرف می‌شوند. این مرحله باید برای پیکربندی ساخت با استفاده از اشیاء DSL و برای ثبت وظایف و ورودی‌های آنها به صورت تنبل (lazyly) استفاده شود.

از آنجا که مرحله پیکربندی همیشه اجرا می‌شود، صرف نظر از اینکه کدام وظیفه برای اجرا درخواست شده است، بسیار مهم است که آن را ساده نگه دارید و هرگونه محاسباتی را از وابستگی به ورودی‌هایی غیر از خود اسکریپت‌های ساخت محدود کنید. یعنی، شما نباید برنامه‌های خارجی را اجرا کنید یا از شبکه بخوانید، یا محاسبات طولانی انجام دهید که می‌توان آنها را به عنوان نمونه‌های مناسب Task به مرحله اجرا موکول کرد.

مرحله اجرا

در مرحله اجرا، وظایف درخواستی و وظایف وابسته به آنها اجرا می‌شوند. به طور خاص، متد(های) کلاس Task که با @TaskAction مشخص شده‌اند، اجرا می‌شوند. در طول اجرای وظیفه، شما مجاز به خواندن از ورودی‌ها (مانند فایل‌ها) و حل ارائه‌دهندگان تنبل با فراخوانی Provider<T>.get() هستید. حل ارائه‌دهندگان تنبل به این روش، دنباله ای از فراخوانی‌های map() یا flatMap() را آغاز می‌کند که اطلاعات وابستگی وظیفه موجود در ارائه دهنده را دنبال می‌کنند. وظایف به صورت تنبل اجرا می‌شوند تا مقادیر مورد نیاز را تحقق بخشند.

API، مصنوعات و وظایف متغیر

API متغیر (Variant API) یک مکانیزم افزونه در افزونه‌ی Gradle اندروید است که به شما امکان می‌دهد گزینه‌های مختلفی را که معمولاً با استفاده از DSL در فایل‌های پیکربندی ساخت تنظیم می‌شوند و بر ساخت اندروید تأثیر می‌گذارند، دستکاری کنید. API متغیر همچنین به شما امکان دسترسی به مصنوعات میانی و نهایی ایجاد شده توسط ساخت، مانند فایل‌های کلاس، مانیفست ادغام شده یا فایل‌های APK/AAB را می‌دهد.

جریان ساخت اندروید و نقاط توسعه

هنگام تعامل با AGP، به جای ثبت فراخوانی‌های چرخه عمر معمول Gradle (مانند afterEvaluate() ) یا تنظیم وابستگی‌های صریح Task ، از نقاط توسعه‌ی ویژه ساخته شده استفاده کنید. وظایف ایجاد شده توسط AGP به عنوان جزئیات پیاده‌سازی در نظر گرفته می‌شوند و به عنوان یک API عمومی در معرض نمایش قرار نمی‌گیرند. شما باید از تلاش برای دریافت نمونه‌هایی از اشیاء Task یا حدس زدن نام Task و اضافه کردن فراخوانی‌ها یا وابستگی‌ها به آن اشیاء Task به طور مستقیم خودداری کنید.

AGP مراحل زیر را برای ایجاد و اجرای نمونه‌های Task خود انجام می‌دهد که به نوبه خود مصنوعات ساخت را تولید می‌کنند. مراحل اصلی مربوط به ایجاد شیء Variant با فراخوانی‌هایی دنبال می‌شوند که به شما امکان می‌دهند در اشیاء خاصی که به عنوان بخشی از یک ساخت ایجاد شده‌اند، تغییراتی ایجاد کنید. توجه به این نکته مهم است که همه فراخوانی‌ها در مرحله پیکربندی (که در این صفحه توضیح داده شده است) اتفاق می‌افتند و باید سریع اجرا شوند و هرگونه کار پیچیده را به نمونه‌های Task مناسب در مرحله اجرا موکول کنند.

  1. تجزیه DSL : این مرحله زمانی است که اسکریپت‌های ساخت ارزیابی می‌شوند و ویژگی‌های مختلف اشیاء DSL اندروید از بلوک android ایجاد و تنظیم می‌شوند. فراخوانی‌های Variant API که در بخش‌های بعدی توضیح داده شده‌اند نیز در این مرحله ثبت می‌شوند.
  2. finalizeDsl() : فراخوانی که به شما امکان می‌دهد اشیاء DSL را قبل از قفل شدن برای ایجاد کامپوننت (variant) تغییر دهید. اشیاء VariantBuilder بر اساس داده‌های موجود در اشیاء DSL ایجاد می‌شوند.

  3. قفل DSL : DSL اکنون قفل شده است و تغییرات دیگر امکان‌پذیر نیست.

  4. beforeVariants() : این فراخوانی می‌تواند از طریق VariantBuilder بر اینکه کدام کامپوننت‌ها ایجاد شوند و برخی از ویژگی‌های آنها تأثیر بگذارد. این روش همچنان امکان ایجاد تغییرات در جریان ساخت و مصنوعات تولید شده را فراهم می‌کند.

  5. ایجاد گونه‌های مختلف : فهرست اجزا و مصنوعاتی که ایجاد خواهند شد، اکنون نهایی شده و قابل تغییر نیست.

  6. onVariants() : در این فراخوانی، به اشیاء Variant ایجاد شده دسترسی پیدا می‌کنید و می‌توانید مقادیر یا ارائه‌دهندگانی را برای مقادیر Property موجود در آنها تنظیم کنید تا به صورت تنبل محاسبه شوند.

  7. قفل‌گذاری متغیر : اشیاء متغیر اکنون قفل شده‌اند و تغییرات دیگر امکان‌پذیر نیست.

  8. وظایف ایجاد شده : اشیاء Variant و مقادیر Property آنها برای ایجاد نمونه‌های Task که برای انجام ساخت ضروری هستند، استفاده می‌شوند.

AGP یک AndroidComponentsExtension معرفی می‌کند که به شما امکان می‌دهد callbackهایی برای 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 بلوک اندروید نگه دارید و هرگونه منطق دستوری سفارشی را به buildSrc یا افزونه‌های خارجی منتقل کنید . همچنین می‌توانید نگاهی به نمونه‌های buildSrc در مخزن گیت‌هاب Gradle recipes ما بیندازید تا نحوه ایجاد یک افزونه در پروژه خود را بیاموزید. در اینجا مثالی از ثبت فراخوانی‌ها از کد افزونه آورده شده است:

abstract class ExamplePlugin: Plugin<Project> {

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

بیایید نگاهی دقیق‌تر به callback های موجود و نوع موارد استفاده‌ای که افزونه شما می‌تواند در هر یک از آنها پشتیبانی کند، بیندازیم:

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

تابع فراخوانی beforeVariants() به صورت اختیاری یک VariantSelector می‌گیرد که می‌توانید آن را از طریق متد selector() در androidComponentsExtension به دست آورید. می‌توانید از آن برای فیلتر کردن کامپوننت‌های شرکت‌کننده در فراخوانی فراخوانی بر اساس نام، نوع ساخت یا نوع محصول استفاده کنید.

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

onVariants()

زمانی که onVariants() فراخوانی می‌شود، تمام مصنوعاتی که توسط AGP ایجاد خواهند شد، از قبل تعیین شده‌اند، بنابراین دیگر نمی‌توانید آنها را غیرفعال کنید. با این حال، می‌توانید برخی از مقادیر مورد استفاده برای وظایف را با تنظیم آنها برای ویژگی‌های 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() })
}

منابع تولید شده را به ساخت و ساز اختصاص دهید

افزونه شما می‌تواند از چند نوع منبع تولید شده استفاده کند، مانند:

برای لیست کامل منابعی که می‌توانید اضافه کنید، به API منابع مراجعه کنید.

این قطعه کد نشان می‌دهد که چگونه می‌توان یک پوشه منبع سفارشی به نام ${variant.name} را با استفاده از تابع addStaticSourceDirectory() به مجموعه منابع جاوا اضافه کرد. سپس زنجیره ابزار اندروید این پوشه را پردازش می‌کند.

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

برای جزئیات بیشتر به دستور addJavaSource مراجعه کنید.

این قطعه کد نحوه اضافه کردن یک دایرکتوری با منابع اندروید تولید شده از یک وظیفه سفارشی به مجموعه منابع 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 بیابید. هر نوع مصنوع دارای ویژگی‌های خاصی است که دانستن آنها مفید است:

کاردینالیتی

کاردینالیتی یک Artifact نشان دهنده تعداد نمونه‌های FileSystemLocation آن یا تعداد فایل‌ها یا دایرکتوری‌های از نوع artifact است. می‌توانید با بررسی کلاس والد آن، اطلاعاتی در مورد کاردینالیتی یک artifact به دست آورید: Artifactهایی که یک FileSystemLocation واحد دارند، زیرکلاسی از Artifact.Single خواهند بود؛ Artifactهایی که چندین نمونه FileSystemLocation دارند، زیرکلاسی از Artifact.Multiple خواهند بود.

نوع FileSystemLocation

شما می‌توانید با نگاه کردن به نوع پارامتری FileSystemLocation یک Artifact که می‌تواند RegularFile یا Directory باشد، بررسی کنید که آیا این Artifact نماینده فایل‌ها است یا دایرکتوری‌ها.

عملیات پشتیبانی شده

هر کلاس Artifact می‌تواند هر یک از رابط‌های زیر را پیاده‌سازی کند تا نشان دهد از کدام عملیات پشتیبانی می‌کند:

  • Transformable : به یک Artifact اجازه می‌دهد تا به عنوان ورودی برای یک Task استفاده شود که تبدیل‌های دلخواه را روی آن انجام می‌دهد و نسخه جدیدی از Artifact خروجی می‌دهد.
  • Appendable : فقط برای مصنوعاتی که زیرکلاس‌های Artifact.Multiple هستند، اعمال می‌شود. این بدان معناست که می‌توان به Artifact الحاق کرد، یعنی یک Task سفارشی می‌تواند نمونه‌های جدیدی از این نوع Artifact ایجاد کند که به لیست موجود اضافه می‌شوند.
  • Replaceable : فقط برای مصنوعاتی اعمال می‌شود که زیرکلاس‌های Artifact.Single هستند. یک Artifact قابل تعویض می‌تواند با یک نمونه کاملاً جدید که به عنوان خروجی یک Task تولید می‌شود، جایگزین شود.

علاوه بر سه عملیات اصلاح مصنوع، هر مصنوع از عملیات get() (یا getAll() ) پشتیبانی می‌کند که یک Provider با نسخه نهایی مصنوع (پس از اتمام تمام عملیات روی آن) را برمی‌گرداند.

افزونه‌های چندگانه می‌توانند هر تعداد عملیات روی مصنوعات را از طریق فراخوانی onVariants() به خط لوله اضافه کنند و AGP اطمینان حاصل می‌کند که آنها به درستی زنجیره شده‌اند تا همه وظایف در زمان مناسب اجرا شوند و مصنوعات به درستی تولید و به‌روزرسانی شوند. این بدان معناست که وقتی یک عملیات هر خروجی را با افزودن، جایگزینی یا تبدیل آنها تغییر می‌دهد، عملیات بعدی نسخه به‌روز شده این مصنوعات را به عنوان ورودی می‌بیند و به همین ترتیب ادامه می‌یابد.

نقطه ورود به عملیات ثبت، کلاس Artifacts است. قطعه کد زیر نشان می‌دهد که چگونه می‌توانید در فراخوانی onVariants() به یک نمونه از Artifacts از یک ویژگی روی شیء Variant دسترسی پیدا کنید.

سپس می‌توانید TaskProvider سفارشی خود را برای دریافت یک شیء TaskBasedOperation (1) ارسال کنید و از آن برای اتصال ورودی‌ها و خروجی‌هایش با استفاده از یکی از متدهای wiredWith* (2) استفاده کنید.

روش دقیقی که باید انتخاب کنید بستگی به کاردینالیتی و نوع 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 وجود دارند که برای ترکیبات دیگر از انواع Artifact cardinality و FileSystemLocation کار می‌کنند.

برای کسب اطلاعات بیشتر در مورد گسترش AGP، توصیه می‌کنیم بخش‌های زیر را از دفترچه راهنمای سیستم Gradle build مطالعه کنید: