בדרך כלל, אפליקציות ל-Android נוצרות באמצעות מערכת ה-build של Gradle. לפני שנתעמק בפרטים לגבי הגדרת ה-build, נסביר מהם המושגים שמאחורי ה-build כדי שתוכלו לראות את המערכת כולה.
מה זה build?
מערכת build ממירה את קוד המקור לאפליקציה שניתן להריץ. בדרך כלל, תהליך ה-build כולל כמה כלים לניתוח, להדרכה, לקישור ולאריזה של האפליקציה או הספרייה. Gradle משתמשת בגישה מבוססת-משימות כדי לארגן ולהריץ את הפקודות האלה.
משימות כוללות פקודות שמתרגמות את הקלט שלהן לפלט. יישומי פלאגין מגדירים משימות והגדרות שלהן. כשמשתמשים בפלאגין כדי להחיל את ה-build, המשימות מתועדות ומחברים אותן יחד באמצעות הקלט והפלט שלהם. לדוגמה, החלת Android Gradle Plugin (AGP) על קובץ ה-build תירשם את כל המשימות הנדרשות ליצירת APK או ספריית Android. הפלאגין java-library
מאפשר ליצור צנצנת מקוד המקור של Java. יישומי פלאגין דומים קיימים ב-Kotlin ובשפות אחרות, אבל יישומי פלאגין אחרים מיועדים להרחיב יישומי פלאגין. לדוגמה, הפלאגין protobuf
נועד להוסיף תמיכה ב-protobuf ליישומי פלאגין קיימים כמו AGP או java-library
.
ב-Gradle נוטים להשתמש במקובל במקום בהגדרות, כך שהתוספים יגיעו עם ערכי ברירת מחדל טובים מראש, אבל אפשר להגדיר את ה-build באופן נוסף באמצעות שפה ייעודית לדומיין (DSL) מצהירה. ה-DSL תוכנן כך שתוכלו לציין מה אתם רוצים ליצור, ולא איך ליצור אותו. הלוגיקה ביישומי הפלאגין מנהלת את ה"איך". ההגדרה הזו מוגדרת במספר קובצי build בפרויקט (ובפרויקטים המשניים).
קלט של משימות יכול להיות קבצים ודירקטורים, וגם מידע אחר שמקודד כסוגי Java (מספר שלם, מחרוזות או כיתות בהתאמה אישית). הפלט יכול להיות רק ספרייה או קבצים, כי צריך לכתוב אותם בדיסק. חיווט פלט של משימה לקלט אחר של משימה מקשר בין המשימות זו לזו, כך שאחת מהן צריכה לרוץ לפני השנייה.
Gradle תומך בכתיבת קוד שרירותי והצהרות על משימות בקובצי ה-build, אבל זה עלול להקשות על הכלים להבין את ה-build ועל התחזוקה שלכם. לדוגמה, אפשר לכתוב בדיקות לקוד בתוך יישומי פלאגין, אבל לא בקובצי build. במקום זאת, כדאי להגביל את ההצהרות על משימות ועל לוגיקת ה-build לפלאגינים (שאתם או מישהו אחר מגדירים) ולהצהיר איך אתם רוצים להשתמש בלוגיקה הזו בקובצי ה-build.
מה קורה כשמפעילים build ב-Gradle?
ה-builds של Gradle פועלים בשלושה שלבים. כל אחד מהשלבים האלה מפעיל חלקים שונים בקוד שמגדירים בקובצי ה-build שלכם.
- הפעלה קובעת אילו פרויקטים ופרויקטים משניים נכללים ב-build, ומגדירה נתיבי classpath שמכילים את קובצי ה-build ואת הפלאגינים שהוחלו. השלב הזה מתמקד בקובץ הגדרות שבו מצהירים על הפרויקטים שצריך לבנות ועל המיקומים שמהם צריך לאחזר יישומי פלאגין וספריות.
- Configuration רושם משימות לכל פרויקט ומריץ את קובץ ה-build כדי להחיל את מפרט ה-build של המשתמש. חשוב להבין שלקוד התצורה לא תהיה גישה לנתונים או לקבצים שנוצרים במהלך הביצוע.
- הביצוע מבצע את ה"בניין" בפועל של האפליקציה. הפלט של התצורה הוא תרשים מנוהל לא צפוי (DAG) של משימות, שמייצג את כל שלבי ה-build הנדרשים שהמשתמש ביקש (המשימות שסופקו בשורת הפקודה או כברירת מחדל בקובצי ה-build). הגרף הזה מייצג את הקשר בין המשימות, בין אם הוא מפורש בהצהרה של המשימה ובין אם הוא מבוסס על הקלט והפלט שלה. אם למשימה יש קלט שהוא פלט של משימה אחרת, היא צריכה לרוץ אחרי המשימה השנייה. בשלב הזה מתבצעת הפעלה של משימות לא עדכניות לפי הסדר שהוגדר בתרשים. אם הקלט של משימה לא השתנה מאז ההפעלה האחרונה שלה, Gradle ידלג עליה.
מידע נוסף מופיע במאמר מחזור החיים של Gradle.
הגדרת DSL
ב-Gradle נעשה שימוש בשפה ספציפית לדומיין (DSL) כדי להגדיר גרסאות build. הגישה הזו מתמקדת בהצהרה על הנתונים ולא בכתיבת הוראות מפורטות (אימפרטיביות). אפשר לכתוב את קובצי ה-build באמצעות Kotlin או Groovy, אבל מומלץ מאוד להשתמש ב-Kotlin.
DSL מנסים להקל על כולם, מומחי דומיינים ומתכנתים, לתרום תוכן לפרויקט, ולהגדיר שפה קטנה שמייצגת נתונים בדרך טבעית יותר. יישומי פלאגין של Gradle יכולים להרחיב את ה-DSL כדי להגדיר את הנתונים הדרושים להם למשימות שלהם.
לדוגמה, הגדרת החלק של Android ב-build עשויה להיראות כך:
Kotlin
android { namespace = "com.example.app" compileSdk = 34 // ... defaultConfig { applicationId = "com.example.app" minSdk = 34 // ... } }
Groovy
android { namespace 'com.example.myapplication' compileSdk 34 // ... defaultConfig { applicationId "com.example.myapplication" minSdk 24 // ... } }
מאחורי הקלעים, קוד ה-DSL דומה ל:
fun Project.android(configure: ApplicationExtension.() -> Unit) {
...
}
interface ApplicationExtension {
var compileSdk: Int
var namespace: String?
val defaultConfig: DefaultConfig
fun defaultConfig(configure: DefaultConfig.() -> Unit) {
...
}
}
כל בלוק ב-DSL מיוצג על ידי פונקציה שמקבלת פונקציית lambda כדי להגדיר אותו, ומאפיין עם אותו שם כדי לגשת אליו. כך, הקוד בקובצי ה-build נראה יותר כמו מפרט נתונים.
יחסי תלות חיצוניים
מערכת ה-build של Maven הציגה מפרט, אחסון ומערכת ניהול של תלות. הספריות מאוחסנות במאגרים (שרתים או ספריות), עם מטא-נתונים שכוללים את הגרסה שלהן ואת יחסי התלות שלהן בספריות אחרות. אתם מציינים באילו מאגרים לחפש, את הגרסאות של יחסי התלות שבהם אתם רוצים להשתמש, ומערכת ה-build מורידה אותם במהלך ה-build.
ניתן לזהות את פריטי המידע שנוצרו בתהליך הפיתוח (Artifact) של Maven לפי שם הקבוצה (חברה, מפתח וכו'), שם פריט המידע שנוצר בתהליך הפיתוח (Artifact) (שם הספרייה) והגרסה שלו. בדרך כלל הוא מיוצג באמצעות group:artifact:version
.
הגישה הזו משפרת משמעותית את ניהול ה-build. לעיתים קרובות שומעים מאגרים כאלה שנקראים "Maven repositories", אבל הכול תלוי בדרך שבה הנארזים נארזים ומפרסמים אותם. המאגרים והמטא-נתונים האלה עושים שימוש חוזר במספר מערכות פיתוח, כולל Gradle (ו-Gradle יכול לפרסם במאגרים האלה). מאגרים ציבוריים מאפשרים שיתוף לכולם, ומאגרים של חברות שומרים את יחסי התלות הפנימיים בתוך הבית.
אפשר גם לשנות את הפרויקט לפרויקטים משניים (שנקראים גם 'מודולים' ב-Android Studio), ולהשתמש בהם גם כיחסי תלות. כל תת-פרויקט יוצר פלט (למשל, צנצנות) שאפשר להשתמש בו בפרויקטים משניים או בפרויקט ברמה העליונה. כך תוכלו לבודד את החלקים שצריך לבנות מחדש, וכך לקצר את זמן ה-build, וגם להפריד טוב יותר את האחריות באפליקציה.
במאמר הוספת יחסי תלות של build נפרט יותר איך לציין יחסי תלות.
יצירת וריאציות
כשיוצרים אפליקציה ל-Android, בדרך כלל רוצים ליצור כמה וריאנטים. הווריאנטים מכילים קוד שונה או שהם נוצרים עם אפשרויות שונות, ומורכבים מסוגי build וטעמים שונים של מוצרים.
סוגי גרסאות build משתנים בהתאם לאפשרויות ה-build שהוגדרו. כברירת מחדל, AGP מגדיר את סוגי ה-build 'release' ו-'debug', אבל אפשר לשנות אותם ולהוסיף עוד (למשל, לצורך עיבוד מקדים או בדיקה פנימית).
גרסת build לניפוי באגים לא מקטינה או מעורפלת (obfuscation) של האפליקציה, וכך מזרזת את הפיתוח ושומרת את כל הסמלים כפי שהם. היא גם מסמנת את האפליקציה כ'ניתנת לניפוי באגים', חותמת עליה באמצעות מפתח גנרי לניפוי באגים ומאפשרת גישה לקובצי האפליקציה המותקנים במכשיר. כך תוכלו לבחון נתונים שמורים בקבצים ובמסדי נתונים בזמן הריצה של האפליקציה.
ב-build של גרסה מתבצעת אופטימיזציה של האפליקציה, חתימה עליה באמצעות מפתח הגרסה והגנה על קובצי האפליקציה המותקנים.
באמצעות סוגים של מוצרים, אפשר לשנות את המקור הכלול ואת הגרסאות של יחסי התלות של האפליקציה. לדוגמה, יכול להיות שתרצו ליצור גרסאות 'דמו' ו'מלאה' לאפליקציה, או גרסאות 'חינם' ו'בתשלום'. כותבים את המקור המשותף בספרייה 'ראשית' של קבוצת מקורות, ומחליפים את המקור או מוסיפים אותו לקבוצת מקורות ששמה זהה לשם הטעם.
AGP יוצרת וריאנטים לכל שילוב של סוג build וטעם של המוצר. אם לא מגדירים טעמים, הווריאנטים נקראים על שם סוגי ה-build. אם מגדירים את שניהם, שם הווריאנט יהיה <flavor><Buildtype>
. לדוגמה, עם סוגי build release
ו-debug
, וסוגים demo
ו-full
, מערכת AGP תיצור את הווריאציות הבאות:
demoRelease
demoDebug
fullRelease
fullDebug
השלבים הבאים
עכשיו, אחרי שקראתם על המושגים של ה-build, תוכלו להציץ במבנה ה-build של Android בפרויקט.