כדי להפעיל אופטימיזציה של אפליקציות, צריך להשתמש בספריות שתואמות לאופטימיזציה של Android. אם ספרייה לא מוגדרת לאופטימיזציה ל-Android – למשל, אם היא משתמשת ברפלקציה בלי לארוז כללי שמירה משויכים – יכול להיות שהיא לא מתאימה לאפליקציית Android. בדף הזה מוסבר למה חלק מהספריות מתאימות יותר לאופטימיזציה של אפליקציות, ומפורטים טיפים כלליים שיעזרו לכם לבחור.
טיפים כלליים לבחירת ספריות
הטיפים הבאים יעזרו לכם לוודא שהספריות שלכם תואמות לאופטימיזציה של האפליקציה.
העדפה של יצירת קוד על פני רפלקציה
בוחרים ספריות שמשתמשות ביצירת קוד (codegen) במקום בהשתקפות. באמצעות codegen, כלי האופטימיזציה יכול לקבוע איזה קוד נמצא בשימוש בפועל בזמן הריצה ואיזה קוד אפשר להסיר. יכול להיות שיהיה קשה לדעת אם ספרייה משתמשת ב-codegen או ב-reflection, אבל יש כמה סימנים שיכולים לעזור לכם – כדאי לעיין בטיפים.
מידע נוסף על codegen לעומת reflection זמין במאמר אופטימיזציה למפתחי ספריות.
בדיקה של שימוש בהשתקפות (מתקדם)
כדי לדעת אם ספריה משתמשת בהשתקפות, צריך לבדוק את הקוד שלה. אם הספרייה משתמשת בהשתקפות, צריך לבדוק שהיא מספקת כללי שמירה משויכים. ספרייה כנראה משתמשת בהשתקפות אם היא מבצעת את הפעולות הבאות:
- משתמשת בכיתות או ב-methods מהחבילות
kotlin.reflectאוjava.lang.reflect. - נעשה שימוש בפונקציות
Class.forNameאוclassLoader.getClass. - קורא הערות בזמן ריצה, לדוגמה אם הוא מאחסן ערך של הערה באמצעות
val value = myClass.getAnnotation()אוval value = myMethod.getAnnotation()ואז מבצע פעולה כלשהי באמצעותvalue. מפעילים מתודות באמצעות שם המתודה כמחרוזת, כמו בדוגמה הבאה:
// Calls the private `processData` API with reflection myObject.javaClass.getMethod("processData", DataType::class.java) ?.invoke(myObject, data)
בדיקה של בעיות באופטימיזציה
כששוקלים להשתמש בספרייה חדשה, כדאי לעיין בכלי למעקב אחרי בעיות של הספרייה ובדיונים באינטרנט כדי לבדוק אם יש בעיות שקשורות למיניפיקציה או להגדרת אופטימיזציה של האפליקציה. אם יש, כדאי לחפש חלופות לספרייה הזו. חשוב לזכור:
- ספריות AndroidX וספריות כמו Hilt פועלות היטב עם אופטימיזציה של אפליקציות, כי הן משתמשות בעיקר ביצירת קוד במקום בהשתקפות. כשהם משתמשים בהשתקפות, הם מספקים כללי שמירה מינימליים כדי לשמור רק את הקוד שנדרש.
- ספריות סריאליזציה משתמשות לעיתים קרובות ברפלקציה כדי להימנע מקוד שחוזר על עצמו (boilerplate) כשיוצרים מופעים של אובייקטים או מבצעים סריאליזציה שלהם. במקום להשתמש בגישות שמבוססות על רפלקציה (כמו Gson ל-JSON), כדאי לחפש ספריות שמשתמשות ביצירת קוד כדי להימנע מהבעיות האלה. לדוגמה, אפשר להשתמש ב-Kotlin Serialization {:.external} או ב-Moshi עם יצירת קוד.
- במידת האפשר, כדאי להימנע מספריות שכוללות כללי שמירה שחלים על כל החבילה. כללי שמירה שחלים על כל החבילה יכולים לעזור לפתור שגיאות, אבל בסופו של דבר צריך לצמצם את כללי השמירה הרחבים כדי לשמור רק את הקוד שדרוש. מידע נוסף זמין במאמר בנושא יישום אופטימיזציות באופן הדרגתי.
- לפני שמפרסמים אפליקציה שמשתמשת בספרייה של צד שלישי, כדאי להשתמש בכלי לניתוח ההגדרות של R8 כדי לבדוק את כללי השמירה שסופקו. בעזרת הבדיקה של הדוח, תוכלו לוודא שכללי השמירה של הספרייה לא רחבים מדי, ולכן לא מאפשרים ל-R8 לבצע אופטימיזציות קריטיות בבסיס הקוד. הבדיקה הזו מוודאת שהספריות שבחרתם תואמות ליעדי הביצועים של האפליקציה, ושלא נוספו הגדרות מיותרות שמעמיסות על האפליקציה.
- ספריות לא צריכות לחייב אתכם להעתיק ולהדביק כללי שמירה ממסמכים לקובץ בפרויקט, במיוחד לא כללי שמירה שחלים על כל החבילה. הכללים האלה הופכים לנטל תחזוקה על מפתח האפליקציה בטווח הארוך, וקשה לבצע אופטימיזציה ולשנות אותם עם הזמן.
הפעלת אופטימיזציה אחרי הוספה של ספרייה חדשה
כשמוסיפים ספרייה חדשה, צריך להפעיל את האופטימיזציה ולבדוק אם יש שגיאות. אם יש שגיאות, כדאי לחפש חלופות לספרייה הזו או לכתוב כללי שמירה. אם ספרייה לא תואמת לאופטימיזציה, צריך לדווח על באג בספרייה הזו.
סינון כללי שמירה לא טובים (מתקדם)
כללי השמירה הם מצטברים. המשמעות היא שלא ניתן להסיר כללים מסוימים שכלולים ביחסי תלות של ספרייה, והם עשויים להשפיע על הקומפילציה של חלקים אחרים באפליקציה. לדוגמה, אם ספרייה כוללת כלל להשבתת אופטימיזציות של קוד, הכלל הזה משבית את האופטימיזציות של כל הפרויקט.
כדאי להימנע מספריות עם כללי שמירה ששומרים קוד שבאמת צריך להסיר. אבל אם אתם חייבים להשתמש בהם, אתם יכולים לסנן את הכללים כמו שמוצג בקוד הבא:
// If you're using AGP 8.4 and higher
buildTypes {
release {
optimization.keepRules {
it.ignoreFrom("com.somelibrary:somelibrary")
}
}
}
// If you're using AGP 7.3-8.3
buildTypes {
release {
optimization.keepRules {
it.ignoreExternalDependencies("com.somelibrary:somelibrary")
}
}
}
מקרה לדוגמה: למה Gson נכשל באופטימיזציות
Gson היא ספריית סריאליזציה שגורמת לעיתים קרובות לבעיות באופטימיזציה של אפליקציות, כי היא משתמשת ב-reflection באופן נרחב. בקטע הקוד הבא אפשר לראות איך בדרך כלל משתמשים ב-Gson, מה שעלול לגרום לקריסות בזמן הריצה. שימו לב: כשמשתמשים ב-Gson כדי לקבל רשימה של אובייקטים מסוג User, לא קוראים לקונסטרוקטור ולא מעבירים מפעל לפונקציה fromJson(). אם ספרייה יוצרת או משתמשת במחלקות שהוגדרו באפליקציה בלי להשתמש באף אחת מהאפשרויות הבאות, יכול להיות שהיא משתמשת בהשתקפות פתוחה:
- מחלקה של אפליקציה שמטמיעה ספרייה, או ממשק או מחלקה רגילים
- פלאגין ליצירת קוד כמו KSP
class User(val name: String)
class UserList(val users: List<User>)
// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()
כדי להבין איך R8 פועל ב-Gson, אפשר לעיין בכללי הצרכן של Gson. כש-R8 מנתח את הקוד הזה ולא רואה את UserList או User מופעלים בשום מקום, הוא יכול לשנות את השם של השדות או להסיר בנאים שלא נראה שהם בשימוש, ולגרום לקריסת האפליקציה. אם אתם משתמשים בספריות אחרות בדרכים דומות, כדאי לוודא שהן לא יפריעו לאופטימיזציה של האפליקציה, ואם הן מפריעות, כדאי להימנע מהן.
שימוש ב-@SerializedName בגרסאות מאוחרות יותר של Gson
כדי להגדיר את מודלי הנתונים באופן שתואם לכללי הצרכן של Gson, צריך להוסיף הערות לשדות עם @SerializedName, כמו בדוגמה הבאה:
import com.google.gson.annotations.SerializedName
class User(@SerializedName("name") val name: String)
class UserList(@SerializedName("users") val users: List<User>)
שימוש בהערה @SerializedName מאפשר ל-R8 להתאים את מחלקות המודל לכללי השמירה שכלולים ב-Gson מגרסה 2.11.0 ואילך.
R8 שומר באופן אוטומטי את השדות עם ההערות ואת הקונסטרוקטורים הדרושים, כך שאתם יכולים להסתמך על הכללים שכלולים בספרייה בלי שתצטרכו לתחזק הגדרות ProGuard ידניות בפרויקט.
שימו לב: הספריות Room, Hilt ו-Moshi עם codegen יוצרות סוגים שמוגדרים באפליקציה, אבל משתמשות ב-codegen כדי להימנע מהצורך ברפלקציה.