מצב דילוג חזק

דילוג חזק הוא מצב שזמין במהדר של Compose. כשהיא מופעלת, היא משנה את ההתנהגות של הקומפיילר בשתי דרכים:

הפעלת מצב דילוג חזק

דילוג חזק מופעל כברירת מחדל ב-Kotlin 2.0.20.

כדי להפעיל דילוג חזק במודול Gradle בגרסה שקודמת ל-2.0.20, צריך לכלול את האפשרות הבאה בבלוק composeCompiler של הגדרת Gradle:

android { ... }

composeCompiler {
   enableStrongSkippingMode = true
}

אפשרות דילוג על מודעות קומפוזביליות

מצב דילוג חזק מקל על חלק מכללי היציבות שבדרך כלל חלים על ידי מהדר Compose כשמדובר בדילוג ובפונקציות קומפוזביליות. כברירת מחדל, הקומפיילר של "פיתוח נייטיב" מסמן פונקציה קומפוזבילית כפונקציה שניתן לדלג עליה אם כל הארגומנטים שלה כוללים ערכים יציבים. מצב דילוג חזק משנה את זה.

כשהדילוג החזק מופעל, אפשר לדלג על כל הפונקציות הקומפוזביליות שניתנות להפעלה מחדש. ההגדרה הזו חלה גם אם יש להם פרמטרים לא יציבים. פונקציות קומפוזביליות שלא ניתנות להפעלה מחדש עדיין לא ניתנות לדילוג.

מתי לדלג

כדי לקבוע אם לדלג על פונקציה קומפוזבילית במהלך קומפוזיציה מחדש, Compose משווה את הערך של כל פרמטר לערכים הקודמים שלו. סוג ההשוואה תלוי ביציבות של הפרמטר.

  • פרמטרים לא יציבים מושווים באמצעות שוויון מופעים (===)
  • פרמטרים יציבים מושווים באמצעות שוויון אובייקטים (Object.equals())

אם כל הפרמטרים עומדים בדרישות האלה, פיתוח נייטיב מדלג על הרכיב הקומפוזבילי במהלך ההרכבה מחדש.

יכול להיות שתרצו להגדיר פונקציית Composable שלא תשתמש בדילוג חזק. כלומר, יכול להיות שתרצו רכיב קומפוזבילי שניתן להפעיל מחדש אבל לא ניתן לדלג עליו. במקרה כזה, צריך להשתמש בהערה @NonSkippableComposable.

@NonSkippableComposable
@Composable
fun MyNonSkippableComposable {}

הוספת הערות למחלקות כ"יציבות"

אם רוצים להשוות אובייקט באמצעות שוויון אובייקטים (Object.equals()) במקום שוויון מופעים (===), צריך להמשיך להוסיף הערות למחלקה הנתונה באמצעות @Stable או @Immutable.

לפני שמוסיפים את @Stable או את @Immutable, חשוב לזכור את היתרונות והחסרונות הבאים:

  • מחלקות עם מאפיינים יציבים במודולים של Compose כבר יציבות: מחלקה עם מאפייני val של סוגים יציבים במודול שעבר הידור באמצעות המהדר של Compose, מוסקת באופן אוטומטי כיציבה ומשתמשת ב-.equals() בלי צורך ב-@Immutable או ב-@Stable.
  • הוסיפו הערות רק למחלקות עם בדיקות .equals() פשוטות: הוסיפו @Stable או @Immutable כשהקומפיילר לא יכול להסיק יציבות (כמו מחלקות ממודולים שלא שייכים ל-Compose) ומוקצים לעיתים קרובות מופעים חדשים עם ערכים זהים, בתנאי שבדיקת ה-.equals() של המחלקה היא פשוטה.
  • היזהרו כשאתם מוסיפים הערות למודלים עם אוספים: הוספה של @Stable או @Immutable למודלים שמכילים אוספים (או החלפה של List ב-ImmutableList) גורמת ל-Compose להשוות כל רכיב באוסף עם .equals() בכל מעבר של יצירה מחדש. באוספים גדולים או במודלים שמשמשים בפריסות עם טעינה מדורגת (LazyColumn, ‏ LazyRow, ‏ LazyGrid), השוואה של כל רכיב יכולה להיות איטית יותר מאשר שימוש בשוויון מופעים (===) או יצירה מחדש באופן ישיר.

שמירת תוצאות של פונקציות LAMBDA בזיכרון (memoization)

מצב דילוג חזק מאפשר גם יותר שמירת תוצאות של פונקציות למבדה בתוך פונקציות קומפוזביליות. כשהדילוג החזק מופעל, כל למבדה בתוך פונקציה קומפוזבילית נשמרת בזיכרון באופן אוטומטי.

דוגמאות

כדי להשיג שינון של פונקציות למבדה בתוך פונקציות קומפוזביליות כשמשתמשים בדילוג חזק, המהדר עוטף את פונקציית הלמבדה בקריאה ל-remember. הוא מוגדר עם הלכידות של הלמבדה.

נניח שיש לכם פונקציית lambda כמו בדוגמה הבאה:

@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
    val lambda = {
        use(unstableObject)
        use(stableObject)
    }
}

כשהדילוג החזק מופעל, המהדר שומר את פונקציית הלמבדה בזיכרון המטמון על ידי גלישתה בקריאה ל-remember:

@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
    val lambda = remember(unstableObject, stableObject) {
        {
            use(unstableObject)
            use(stableObject)
        }
    }
}

המפתחות פועלים לפי אותם כללי השוואה כמו פונקציות קומפוזביליות. סביבת זמן הריצה משווה בין מפתחות לא יציבים באמצעות שוויון מופעים. הוא משווה בין מפתחות יציבים באמצעות שוויון אובייקטים.

שמירת תוצאות של פונקציות (memoization) ויצירה מחדש (recomposition)

האופטימיזציה הזו מגדילה באופן משמעותי את מספר הרכיבים הניתנים לשילוב שזמן הריצה מדלג עליהם במהלך היצירה מחדש. בלי שינון, סביר יותר שבזמן הריצה תוקצה פונקציית lambda חדשה לכל פונקציה קומפוזבילית שמקבלת פרמטר lambda במהלך הרכבה מחדש. כתוצאה מכך, לפונקציית הלמבדה החדשה יש פרמטרים שלא שווים לקומפוזיציה האחרונה. התוצאה היא הרכבה מחדש.

הימנעו משינון

אם יש לכם למבדה שאתם לא רוצים לשמור, השתמשו בהערה @DontMemoize.

val lambda = @DontMemoize {
    ...
}

גודל ה-APK

כשמבצעים קומפילציה, פונקציות Composable שניתנות לדילוג יוצרות יותר קוד מאשר פונקציות Composable שלא ניתנות לדילוג. כשהתכונה "דילוג חזק" מופעלת, המהדר מסמן כמעט את כל הפונקציות הניתנות ליצירה כניתנות לדילוג, ועוטף את כל פונקציות הלמבדה ב-remember{...}. לכן, הפעלת מצב דילוג חזק משפיעה מעט מאוד על גודל ה-APK של האפליקציה.

הפעלת דילוג חזק ב-Now In Android הגדילה את קובץ ה-APK ב-4KB. ההבדל בגודל תלוי בעיקר במספר הרכיבים הניתנים להרכבה שלא ניתן היה לדלג עליהם בעבר, שהיו קיימים באפליקציה הנתונה, אבל הוא אמור להיות קטן יחסית.