مقدمه‌ای بر Gradle از نگاه یک توسعه‌دهنده اندروید

اندروید استودیو به گونه‌ای طراحی شده که بدون آشنایی با gradle هم می‌توان به توسعه برنامه‌های اندرویدی پرداخت. بااین‌حال، یک آشنایی اولیه می‌تواند امکانات متعددی را پیش روی ما قرار دهد. در این نوشته قصد دارم از نگاه یک توسعه‌دهندهٔ اندروید، توضیحاتی را در مورد Gradle ارائه کنم.

Gradle چیست؟

هنگام توسعهٔ یک برنامه، کارهایی هستند که بارها و بارها تکرار می‌شوند. مثلاً برای به اجرا درآوردن کدهای جاوا، لازم است ابتدا آن‌ها را کامپایل کنیم. برخی از این کارها تنها زمانی قابل‌اجرا هستند که عملیات دیگری به عنوان پیش‌نیاز، به اجرا در آمده باشد. مثلاً ممکن است بخواهیم در هر build، ابتدا ابزار lint به اجرا در بیاید و خطاهای احتمالی کدهای نوشته شده را به ما گزارش کند. در این شرایط، باید بتوانیم اجرا شدن بعضی از کارها یا وظایف را در الویت قرار دهیم.

کار اصلی gradle، مدیریت و اجرای همین وظایف است. وظایفی که کدهای منبع و دارایی‌های برنامه را به یک محصول نرم‌افزاری قابل‌اجرا تبدیل می‌کند. در دنیای اندروید، محصول نرم‌افزاری قابل‌اجرا، چیزی نیست جز یک فایل APK.

آشنایی اولیه با زبان Groovy

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

اگر یک توسعه‌دهندهٔ اندروید باشید، حتماً با قطعه‌کد زیر آشنایی دارید:

android {  
    compileSdkVersion 27
    buildToolsVersion "27.0.0"
 ⋮
}

قطعه‌کد بالا که از فایل gradle.build استخراج شده، به زبان Groovy نوشته شده است. با توجه به این‌که Groovy را می‌توان از هم‌خانواده‌های جاوا دانست، انتظار داریم که بتوانیم به راحتی با آن ارتباط برقرار کنیم.

در Groovy استفاده از پرانتز برای فراخوانی توابع، اختیاری است. همچنین لازم نیست در انتهای هر عبارت، از ; استفاده کنیم. با این حساب اگر عبارت ;(compileSdkVersion(26 را با عبارت compileSdkVersion 26 جایگزین کنیم، نباید اتفاق خاصی بیفتد. پس می‌توان این‌طور نتیجه گرفت که در بیش‌تر بخش‌های فایل پیکربندی، در حال فراخوانی توابعی هستیم که به زبان Groovy یا جاوا نوشته شده‌اند.

هنگام فراخوانی توابع Groovy، می‌توانیم از توابع دیگر، به عنوان پارامتر ورودی استفاده کنیم. به این مثال توجه کنید: ‍‍‍

def dep1 = { jcenter() }  
dependencies ( dep1 )  

در خط اول ما یک تابع (بستار) تعریف کرده‌‌ایم و آن را در متغیری به نام dep1 نگهداری می‌کنیم. سپس هنگام فراخوانی تابع dependencies، شیء dep1 را (که خود حاوی یک تابع است) به عنوان پارامتر به آن تحویل می‌دهیم.

شکل ساده شدهٔ عبارت بالا بدین صورت است:

dependencies ({ jcenter() })  

اگر پرانتزهای اختیاری را حذف کنیم، نتیجهٔ کار برای توسعه‌دهندگان اندروید حتماً آشنا خواهد بود:

dependencies {  
    jcenter()
}

تعریف پروژه در Gradle‌

در Gradle، مجموعه‌ای از وظایف (task) در کنار هم قرار می‌گیرند و یک پروژه را می‌سازند. هر پروژه دارای یک فایل gradle.build می‌باشد که پیکربندی پروژه در آن تعریف می‌شود. Gradle‌ می‌تواند به جای یک پروژه، مجموعه‌ای از پروژه‌ها را مدیریت کند که به شکل سلسله‌مراتبی، در کنار یک‌دیگر قرار گرفته‌اند. در ساختار چند-پروژه‌ای، هر یک از پروژه‌ها می‌تواند به دیگری وابسته باشد.

در این‌جا واژه «پروژه» می‌تواند برای ما دو معنی متفاوت داشته باشد:

  • یک معنای عام که به پروژه‌های اندروید اطلاق می‌شود: برای ایجاد چنین پروژه‌ای باید به منوی File-> New-> New Project در اندروید استودیو مراجعه کنید؛
  • و یک معنای خاص که Gradle از آن استفاده می‌کند و در بالا به آن اشاره کردیم.

برای این‌که این دو مفهوم با هم اشتباه گرفته نشود، هرکجا که بخواهم از معنای عام استفاده کنم، عبارت «پروژه اندرویدی» را به طور کامل می‌آورم.

بررسی فایل‌های مربوط به Gradle در پروژهٔ اندروید

هدف ما این است که از نگاه یک توسعه‌دهنده اندروید، عملکرد gradle را بررسی کنیم. با این حال توضیحاتی که در این بخش به آن اشاره می‌کنم، به طور مستقل به ساختار پروژه در gradle تعلق دارد و ما به عنوان یک توسعه‌دهندهٔ اندروید، لازم است از این ساختار مطلع باشیم.

اگر در اندروید استودیو با استفاده از ‍File-> New-> New Project یک پروژه اندرویدی جدید ایجاد کنید، به طور خودکار چند فایل در پوشه اصلی ایجاد می‌شود که برای gradle‌ نقش حیاتی دارند. در این‌جا این فایل‌ها را به طور مختصر بررسی می‌کنیم. ساختار پروژه در اندروید استودیو تصویر ۱- ساختار پروژه در اندروید استودیو

فایل settings.gradle

برای این‌که Gradle بتواند ساختار چند-پروژه‌ای را مدیریت کند، در ابتدای فرآیند باید درختی از اشیاء Project‌بسازد که نشان‌دهنده سلسله‌مراتب پروژه‌ها می‌باشد. برای ساختن این درخت، Gradle محتوای فایل ‍settings.gradle را بررسی می‌کند. برای روشن‌تر شدن موضوع، فایل settings.gradle را باز کنید:

include ':app'  

این محتوای پیش‌فرض فایل settings.gradle در پروژه‌های اندرویدی است. در این فایل مشخص می‌کنیم که پروژهٔ ما در بالاترین سطح، به پروژهٔ دیگری به نام app وابسته است.

فایل build.gradle

اگر به ساختار درختی فایل‌های پروژهٔ اندرویدی خود نگاه کنید، دو فایل با نام gradle.build مشاهده خواهید کرد (در تصویر ۱ با رنگ آبی برجسته شده‌اند). همان‌طور که در بخش قبلی اشاره شد، هر یک از این فایل‌ها مسئول پیکربندی یک پروژه در Gradle هستند.

در ساده‌ترین شکل، ما یک پروژه سطح بالا داریم که از یک زیر پروژه به نام app تشکیل شده است. فایل‌ها و منابع برنامه اندرویدی ما معمولاً درون پوشه (پروژه) app قرار می‌گیرد. فایل gradle.build‌ درون پوشه app، به زیر پروژه app اختصاص دارد.

در صورت نیاز می‌توانیم زیرپروژه‌های دیگری را در کنار app، تعریف کنیم. برای انجام این کار، کافی‌ست پوشهٔ جدیدی بسازیم، و یک فایل build.gradle در آن قرار دهیم. در نهایت لازم است نام زیرپروژه جدید را به settings.gradle بیفزاییم.

وظایف و پیکربندی‌هایی که در فایل build.gradle ریشه، تعریف می‌شوند، در همهٔ زیرپروژه‌ها قابل استفاده خواهند بود. می‌توانیم برای هر زیرپروژه، پیکربندی و وظایف یکتایی تعریف کنیم؛ به همین جهت است که برای هر زیرپروژه، یک فایل gradle.build مستقل در اختیار داریم.

توابع مشهور در فایل gradle.build

در پروژه‌های اندرویدی، چند تابع پر کاربرد تقریبا درون همه فایل‌ها build.gradle قابل مشاهده هستند. مثلاً تابع dependencies وابستگی‌های پروژه را تعریف می‌کند. همچنین تابع repositories تعیین می‌کند که برای فراهم کردن این وابستگی‌ها، Gradle‌ باید به چه مخازنی رجوع کند. هر کدام از این توابع یک closure (بستار) را به عنوان پارامتر می‌پذیرند. درون این closureها،‌ ما می‌توانیم وابستگی‌ها و مخازن پروژه را مشخص کنیم.

مدیریت task در Gradle

همان‌طور که در ابتدای نوشته اشاره شد، ما با استفاده از gradle می‌توانیم مجموعه‌ای از کارها یا وظایف را به اجرا در بیاوریم. به هر یک از این کارها که هدف مشخصی دارند، task گفته می‌شود.

اجرای Task

برای اجرا کردن و مشاهده خروجی هر task، می‌توانید از طریق command-line با Gradle ارتباط برقرار کنید. من اما در این‌جا از کنار این موضوع می‌گذرم و به سراغ امکاناتی می‌روم که اندروید استودیو در اختیار ما قرار می‌دهد.

با استفاده از View-> Tool Windows-> Gradle‍ ابزار Gradle را در اندروید استودیو باز کنید. ابزار Gradle در اندروید استودیو تصویر ۲- لیست taskهای قابل اجرا در Gradle

همان‌طور که در تصویر مشاهده می‌کنید، پایین دو عنوان MyApplication و :app، لیستی از taskهای قابل‌اجرا وجود دارد. این taskها توسط پلاگین اندروید یا جاوا فراهم شده‌اند. به زیرشاخهٔ install در پروژه MyApplication بروید و با دو بار کلیک روی installDebug آن را به اجرا دربیاورید تا با نحوه‌ٔ کارکرد taskهای Gradle در اندروید استودیو آشنا شوید. اگر شبیه‌ساز یا دستگاه اندرویدی‌ای موجود باشد، با اجرای این task برنامه در حالت debug روی آن نصب خواهد شد.

همچنین درون زیرشاخه android، روی signingReport دو بار کلیک کنید تا این task نیز به اجرا در بیاید. قبل از این کار از منوی View-> Tool Windows، روی دو مورد Gradle Console‍ و Run کلیک کنید. می‌توانید خروجی task اجرا شده را در Gradle Console‌ ببینید. همچنین Run نشان می‌دهد که برای اجرا شدن signingReport، چه وظایف دیگری به عنوان پیش‌نیاز اجرا می‌شوند.

این بخشی از خروجی حاصل از اجرای signingReport است که می‌توانید آن را در Gradle Console‌ مشاهده کنید. خروجی task در Gradle Console تصویر ۳- خروجی اجرای signingReport

همان‌طور که می‌بینید، جزئیاتی از در مورد sign شدن برنامه در حالت debug قابل مشاهده است که در این‌جا وارد جزییات آن نمی‌شوم. اگر به خروجی توجه کنید، Config: none نشان می‌دهد که کلیدی برای sign کردن برنامه در حالت release تعریف نکرده‌ایم.

تعریف یک Task جدید

هرگز خودتان را به taskهای پیش‌فرضی که توسط پلاگین اندروید یا جاوا فراهم شده‌اند محدود نکنید. از Gradle کارهای جدید بخواهید!

برای تعریف کردن یک task جدید در gradle، کد زیر را به فایل build.gradle، در زیرشاخهٔ پروژه app‌ اضافه کنید:

task hello {  
    group "custom1"
}

حالا با کلیک کردن روی Sync Now، اجازه دهید Gradle تغییرات جدید را اعمال کند. از طریق ‍Views-> Tool Windows-> Gradle‍ به زیرشاخه پروژه app بروید و در زیرشاخهٔ custom1، تنها گزینهٔ موجود را (hello) به اجرا در بیاورید.

تا این‌جا گریدل task‌ جدید شما را شناسایی کرده و آن را درون دسته‌‌ٔ custom1‌ قرار داده است. با اجرای این task اما، نباید انتظار داشته باشید خروجی خاصی در Gradle Console‌ پدیدار شود، چون هنوز بدنهٔ آن خالی است.

مراحل اجرای یک Task

هر task از دو مرحله تشکیل می‌شود: مرحلهٔ پیکربندی و مرحلهٔ اجرا. در مرحلهٔ پیکربندی ما برای اجرا شدن task (مرحلهٔ اجرا) آماده می‌شویم. در مرحلهٔ اجرا، کد اصلی task به اجرا درمی‌آید.

برای روشن‌تر شدن موضوع، تغییرات پایین را اعمال کنید و دوباره روی Sync Now‌ کلیک کنید.

task hello {  
    group "custom1"
    println "`hello` added to `custom1` group."

    doLast {
        println "Task executed successfully"
    }

}

حالا دوباره hello را به اجرا دربیاورید و نتیجه را در Gradle Console بررسی کنید:

Executing tasks: [hello]

`hello` added to `custom1` group.
:app:hello
Task executed successfully

BUILD SUCCESSFUL in 0s  
1 actionable task: 1 executed  

به خروجی task توجه کنید (خروجی شما احتمالاً حاوی چند خط دیگر هم هست که برای ساده شدن، آن‌ها را حذف کرده‌ام). در مرحلهٔ پیکربندی، ابتدا task جدید به دسته custom1 اضافه می‌شود و سپس در مرحلهٔ اجرا (‍:app:hello)، عبارت ‍Task executed successfully در خروجی چاپ می‌شود. بر همین اساس، همواره باید بعد از پایان آماده‌سازی و پیکربندی اولیه، بدنه و کد اجرایی taskها را داخل بخش doLast قرار دهیم.

taskهایی که در مثال بالا تعریف کردیم، از نوع ad-hoc یا تک‌کاره هستند. این نوع از taskها یک عمل مشخص را انجام می‌دهند و قابل پیکربندی نیستند. اگر بخواهید از همه قابلیت‌های Gradle بهره‌مند شوید، باید به سراغ taskهای سفارشی Gradle بروید. من در این‌جا قصد ندارم به این موضوع وارد شوم؛ برای کسب اطلاعات بیش‌تر به این بخش از مستندات Gradle مراجعه کنید.

وابستگی Taskها

برای این‌که خروجی یک پروژه اندروید (فایل APK) در اختیار شما قرار بگیرد، تعداد زیادی task به طور متوالی اجرا می‌شود. این‌که کدام task‌ زودتر به اجرا در بیاید، اهمیت زیادی دارد.

گراف جهت‌دار غیرمدور برای مدیریت وابستگی taskها، Gradle از یک «گراف جهت‌دار غیرمدور» استفاده می‌کند. در تصویر بالا، E به دو task با نام C و D وابسته است. همچنین D به B و B به A وابستگی دارد. Gradle این اطمینان را به شما می‌دهد که قبل از اجرای E، دو وظیفهٔ C و D را به اجرا برساند. اما در مورد ترتیب اجرای دو وظیفهٔ C و D، این Gradle است که تصمیم‌گیری می‌کند.

اکنون قطعه‌کد زیر را به فایل gradle.build پروژهٔ app اضافه کنید. در این قطعه‌کد، ابتدا ما task جدیدی به نام smile تعریف می‌کنیم و سپس آن را به عنوان وابستگی hello به Gradle معرفی می‌کنیم

task smile {  
    doLast {
        println 'Time to smile :)'
    }
}
hello.dependsOn 'smile'  

اکنون دوباره hello را اجرا کنید.

Executing tasks: [hello]

`hello` added to `custom1` group.
:app:smile
Time to smile :)  
:app:hello
Task executed successfully

BUILD SUCCESSFUL in 0s  
2 actionable tasks: 2 executed  

همان‌طور که در خروجی می‌بینید، با این‌که smile قبل از hello اجرا شده، عبارت
hello added to custom1 group. قبل از اجرا شدن هر دو task در خروجی چاپ شده است. این نحوهٔ کارکرد مرحلهٔ پیکربندی را شفاف‌تر می‌سازد.

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

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

اگر یک‌بار دیگر به تصویر ۲ نگاه کنید، با انبوهی از taskها مواجه می‌شوید که در چند گروه مختلف قرار گرفته‌اند. همان‌طور که پیش‌تر اشاره شد، این taskها توسط پلاگین اندروید در اختیار شما قرار گرفته‌اند. اما این پلاگین دقیقا کجاست؟

برای یافتن پاسخ، یک‌بار دیگر به سراغ فایل gradle.build در زیرپروژه app بروید. در همان ابتدای فایل می‌توانید عبارت زیر را پیدا کنید:

apply plugin: 'com.android.application'  

این خط کد باعث می‌شود پلاگین اندروید روی پروژه شما اعمال شود و همه taskهایی که در این پلاگین تعریف شده، در اختیار شما قرار گیرد. گوگل با نوشتن این پلاگین، همه آن‌چه را که در هنگام build کردن پروژه اتفاق می‌افتد، مدیریت می‌کند.

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

چند نکته پایانی

۱− برای نوشتن taskهای سودمند و بهره‌گیری از همه قابلیت‌های پلاگین اندروید، لازم است با امکانات و نحوهٔ عملکرد این پلاگین آشنا شوید. صفحه مرجع این پلاگین را بررسی کنید. این صفحه حاوی اطلاعات سودمندی است.

۲− پلاگین‌های Gradle بسته‌های مستقلی هستند که به راحتی می‌توان آن‌ها را در پروژه‌های گوناگون به کار گرفت. این‌جا یک نقطهٔ شروع مناسب برای آشنایی با نحوهٔ نوشتن پلاگین است.

۳− یکی از قابلیت‌های سودمند Gradle، پرهیز از به اجرا در آوردن taskهایی است که به‌روز هستند. این یعنی وقتی که یک task را اجرا می‌کنید، بلافاصله نتیجه آخرین اجرای آن task در اختیار شما قرار می‌گیرد؛ مگر این‌که اجرا شدن دوباره task نتیجهٔ متفاوتی داشته باشد. برای آشنایی با این ویژگی، این قسمت را بخوانید.