نوشتن افزایه‌های Gradle

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

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

چرخه حیات AGP API

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

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

خط‌مشی منسوخ کردن

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

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

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

کامپایل کردن دربرابر آرتیفکت AGP API

برای نوشتن منطق ساخت یا افزونه Gradle سفارشی (مثلاً در دایرکتوری buildSrc، ماژول افزونه قراردادی، یا پروژه افزونه مستقل) که AGP را گسترش می‌دهد، به‌جای آرتیفکت پیاده‌سازی کامل افزونه، درمقابل آرتیفکت gradle-api AGP کامپایل کنید.

چرا باید دربرابر آرتیفکت gradle-api کامپایل کرد؟

‫AGP دو آرتیفکت اصلی منتشر می‌کند:

  • com.android.tools.build:gradle-api: Contains official AGP APIs, including stable APIs (such as AndroidComponentsExtension, Variant, and public DSL interfaces), incubating APIs (marked @Incubating), and deprecated APIs (marked @Deprecated).
  • com.android.tools.build:gradle: حاوی پیاده‌سازی کامل زمان اجرای AGP، ازجمله کلاس‌های داخلی، تکالیف ساخت، و ادغام‌های کامپایلر است.

کامپایل کردن دربرابر آرتیفکت gradle-api چندین مزیت کلیدی دارد:

  • اجتناب از میاناهای برنامه‌سازی کاربردی داخلی: این کار تضمین می‌کند که کد افزونه شما فقط به میاناهای برنامه‌سازی کاربردی رسمی دسترسی داشته باشد. این کار از وابستگی‌های تصادفی به کلاس‌های پیاده‌سازی داخلی (com.android.build.gradle.internal.*) که ممکن است در نسخه‌های AGP بدون هشدار تغییر کنند یا حذف شوند جلوگیری می‌کند.
  • ساخت سریع‌تر و ردپای کوچک‌تر: مصنوع gradle-api درخت وابستگی بسیار کوچک‌تری نسبت به مصنوع کامل gradle دارد. این کار باعث کاهش اندازه بارگیری، جلوگیری از آلودگی classpath، و افزایش سرعت کامپایل منطق ساخت شما می‌شود.
  • جداسازی زمان اجرا: افزایه‌های سفارشی فقط درطول همگردانی به تعاریف میانای برنامه‌سازی کاربردی نیاز دارند. در زمان اجرا، پیاده‌سازی کامل AGP توسط ساخت Android هنگام اعمال برنامه Android یا افزونه کتابخانه ارائه می‌شود.

افزودن وابستگی

ساخته gradle-api را به‌عنوان وابستگی در فایل ساخت پروژه افزونه یا دایرکتوری buildSrc اضافه کنید. ساخته gradle-api در مخزن Maven متعلق به Google منتشر شده است. برای حل مشکل آرتیفکت، مطمئن شوید که ساخت شما شامل مخزن google() باشد. برای مثال، آن را در بلوک dependencyResolutionManagement.repositories فایل settings.gradle یا settings.gradle.kts یا در بلوک repositories فایل ساخت افزونه یا فایل ساخت دایرکتوری buildSrc پیکربندی کنید.

با کاتالوگ نسخه

اگر پروژه‌تان از کاتالوگ نسخه استفاده می‌کند، وابستگی gradle-api را در فایل gradle/libs.versions.toml تعریف کنید:

[versions]
androidGradlePlugin = "9.4.0"

[libraries]
android-gradle-plugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }

نام مستعار کاتالوگ android-gradle-plugin-api، دسترسی‌دهنده ایمن ازنظر نوع libs.android.gradle.plugin.api را تولید می‌کند.

سپس، libs.android.gradle.plugin.api را به بلوک dependencies فایل ساخت افزونه‌تان اضافه کنید:

کاتلین

dependencies {
    compileOnly(libs.android.gradle.plugin.api)
}

شیک

dependencies {
    compileOnly libs.android.gradle.plugin.api
}

بدون کاتالوگ نسخه

اگر پروژه‌تان از کاتالوگ نسخه استفاده نمی‌کند، وابستگی را مستقیماً در فایل ساخت افزونه‌تان اعلام کنید:

کاتلین

dependencies {
    compileOnly("com.android.tools.build:gradle-api:9.4.0")
}

شیک

dependencies {
    compileOnly 'com.android.tools.build:gradle-api:9.4.0'
}

از گزینه پیکربندی compileOnly به‌عنوان پیش‌فرض برای افزونه‌های مستقل درنظرگرفته‌شده برای انتشار استفاده کنید. پروژه مصرف‌کننده AGP را اعمال می‌کند و کلاس‌های زمان اجرای آن را در مسیر کلاس buildscript ارائه می‌دهد. استفاده از پیکربندی compileOnly از نشت وابستگی زمان اجرای افزونه منتشرشده شما به نسخه AGP خاص جلوگیری می‌کند و از تداخل نسخه برای مصرف‌کنندگان جلوگیری می‌کند. اگر منطق ساخت داخلی را توسعه می‌دهید—مثلاً افزایه‌ها در دایرکتوری buildSrc یا افزایه‌های قراردادی در ساخت‌های ترکیبی—درعوض از گزینه پیکربندی implementation استفاده کنید. این افزایه‌ها در ساخت اجرا می‌شوند و به AGP در مسیر کلاس زمان اجرای خود نیاز دارند.

مبانی ساخت Gradle

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

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

واژه‌نامه انواع تنبل Gradle

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

Provider<T>
مقداری از نوع T (که در آن «T» به‌معنای هر نوعی است) ارائه می‌دهد که می‌توان آن را درطول فاز اجرا بااستفاده از get() خواند یا بااستفاده از روش‌های map()، flatMap()، و zip() به Provider<S> جدیدی (که در آن «S» به‌معنای نوع دیگری است) تبدیل کرد. توجه داشته باشید که get() هرگز نباید درطول فاز پیکربندی فراخوانی شود.
  • map(): یک lambda را می‌پذیرد و یک Provider از نوع S، Provider<S> تولید می‌کند. آرگومان LAMBDA برای map() مقدار T را می‌گیرد و مقدار S را تولید می‌کند. عبارت لامبدا بلافاصله اجرا نمی‌شود؛ بلکه اجرای آن به لحظه‌ای که get() در Provider<S> حاصل فراخوانی می‌شود موکول می‌شود، که باعث می‌شود کل زنجیره تنبل شود.
  • flatMap(): همچنین یک LAMBDA را می‌پذیرد و Provider<S> را تولید می‌کند، اما LAMBDA مقدار T را می‌گیرد و Provider<S> را تولید می‌کند (به‌جای تولید مستقیم مقدار S). وقتی S در زمان پیکربندی قابل‌تعیین نیست و فقط می‌توانید Provider<S> را دریافت کنید، از()flatMap استفاده کنید. به‌طور عملی، اگر از 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()، تا مطمئن شوید تکالیف فقط به‌صورت تنبل درصورت نیاز نمونه‌سازی می‌شوند. می‌توانید از flatMap() برای دسترسی به خروجی‌های Task قبل‌از ایجاد Task استفاده کنید، که می‌تواند مفید باشد اگر بخواهید از خروجی‌ها به‌عنوان ورودی برای سایر نمونه‌های Task استفاده کنید.

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

ارائه‌دهندگان همچنین اطلاعات وابستگی تکلیف را حمل می‌کنند. وقتی Provider را با تبدیل برونداد Task ایجاد می‌کنید، آن 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 Variant API و دسترسی به خروجی تکالیف آن، یا ساخت مصنوعات را ارائه می‌دهد که می‌توانید آن‌ها را بخوانید و تبدیل کنید. برای اطلاعات بیشتر، Variant API، مصنوعات، و تکالیف را در این سند ببینید.

مراحل ساخت Gradle

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

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

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

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

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

مرحله اجرا

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

Variant API، مصنوعات، و تکالیف

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

جریان ساخت Android و نقاط افزونه

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

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

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

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

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

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

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

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

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 نباید پس‌از اتمام اجرای فراخوانی برگشتی 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 اصلاح کنید. Because the Property values will only be resolved when AGP's tasks are executed, you can safely wire them up to providers from your own custom tasks that will perform any required computations, including reading from external inputs such as files or the network.

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

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

افزونه شما می‌تواند چند نوع منبع تولیدشده ارائه دهد، ازجمله:

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

این تکه‌کد نشان می‌دهد که چگونه بااستفاده از تابع addStaticSourceDirectory()، پوشه منبع سفارشی‌ای به‌نام ${variant.name} را به مجموعه منبع Java اضافه کنید. سپس زنجیره ابزار 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 را ببینید.

دسترسی به مصنوعات و اصلاح آن‌ها

علاوه‌بر اینکه AGP به شما امکان می‌دهد ویژگی‌های ساده را در اشیای Variant اصلاح کنید، یک سازوکار افزونه نیز دارد که به شما امکان می‌دهد مصنوعات میانی و نهایی تولیدشده درطول ساخت را بخوانید یا تبدیل کنید. برای مثال، می‌توانید محتوای فایل نهایی و ادغام‌شده AndroidManifest.xml را در یک Task سفارشی بخوانید تا آن را تجزیه‌وتحلیل کنید، یا می‌توانید محتوای آن را کاملاً با محتوای فایل مانیفست تولیدشده توسط Task سفارشی‌تان جایگزین کنید.

می‌توانید فهرست مصنوعاتی را که درحال‌حاضر پشتیبانی می‌شوند در سند مرجع کلاس Artifact پیدا کنید. هر نوع مصنوع دارای ویژگی‌های خاصی است که دانستن آن‌ها مفید است:

کاردینالیته

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

FileSystemLocation نوع

می‌توانید با نگاه کردن به نوع پارامتری‌شده FileSystemLocation که می‌تواند 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 است. تکه‌کد زیر نشان می‌دهد که چگونه می‌توانید به نمونه‌ای از Artifacts ازطریق ویژگی‌ای در شیء Variant در فراخوانی برگشتی onVariants() دسترسی پیدا کنید.

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

روش دقیقی که باید انتخاب کنید به کاردینالیتی و FileSystemLocation نوع پیاده‌سازی‌شده توسط Artifact که می‌خواهید تبدیل کنید بستگی دارد.

و درنهایت، نوع Artifact را به روشی که نشان‌دهنده عملیات انتخاب‌شده روی شیء *OperationRequest است که درمقابل دریافت می‌کنید، ارسال می‌کنید، برای مثال، toAppendTo()، toTransform() ، یا toCreate() (۳).

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 تعداد عناصر و انواع FileSystemLocation کار خواهد کرد.

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