כדי להפעיל אופטימיזציה של אפליקציות, צריך להשתמש בספריות שתואמות לאופטימיזציה של Android. אם ספרייה לא מוגדרת לאופטימיזציה ל-Android – למשל, אם היא משתמשת ברפלקציה בלי לאגד כללי שמירה משויכים – יכול להיות שהיא לא מתאימה לאפליקציית Android. בדף הזה מוסבר למה חלק מהספריות מתאימות יותר לאופטימיזציה של אפליקציות, ומפורטים טיפים כלליים שיעזרו לכם לבחור.
טיפים כלליים לבחירת ספריות
הטיפים האלה יעזרו לכם לוודא שהספריות שלכם תואמות לאופטימיזציה של האפליקציה.
העדפה של יצירת קוד על פני רפלקציה
בוחרים ספריות שמשתמשות ביצירת קוד (codegen) במקום בהשתקפות. באמצעות codegen, כלי האופטימיזציה יכול לקבוע איזה קוד נמצא בשימוש בפועל בזמן הריצה ואיזה קוד אפשר להסיר. יכול להיות שיהיה קשה לדעת אם ספרייה משתמשת ב-codegen או ב-reflection, אבל יש כמה סימנים שיכולים לעזור – אפשר להיעזר בטיפים.
מידע נוסף על codegen לעומת reflection זמין במאמר אופטימיזציה למפתחי ספריות.
בדיקה של שימוש בהשתקפות (מתקדם)
כדי לדעת אם נעשה שימוש ברפלקציה בספרייה, צריך לבדוק את הקוד שלה. אם הספרייה משתמשת בהשתקפות, צריך לבדוק שהיא מספקת כללי שמירה משויכים. ספרייה כנראה משתמשת בהשתקפות אם היא מבצעת את הפעולות הבאות:
- משתמשת במחלקות או בשיטות מהחבילות
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 פועלות היטב עם אופטימיזציה של אפליקציות, כי הן משתמשות בעיקר ביצירת קוד במקום בהשתקפות. כשהם משתמשים ברפלקציה, הם מספקים כללי שמירה מינימליים כדי לשמור רק את הקוד שנדרש.
- ספריות סריאליזציה משתמשות לעיתים קרובות ב-reflection כדי להימנע מקוד שחוזר על עצמו (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, לא קוראים לקונסטרוקטור ולא מעבירים פונקציית factory לפונקציה 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 בשום מקום, הוא יכול לשנות את השם של השדות או להסיר בנאים שלא נראה שהם בשימוש, ולגרום לקריסת האפליקציה. אם אתם משתמשים בספריות אחרות בדרכים דומות, כדאי לבדוק שהן לא יפריעו לאופטימיזציה של האפליקציה, ואם הן מפריעות, כדאי להימנע מהן.
כדי להגדיר את המחלקות באופן שתואם לכללי הצרכן של Gson, אפשר להשתמש בקטע הקוד הבא כהפניה:
class User(@com.google.gson.annotations.SerializedName("name") val name: String)
class UserList(@com.google.gson.annotations.SerializedName("users") val users: List<User>)
שימו לב: הספריות Room, Hilt ו-Moshi עם codegen יוצרות סוגים שמוגדרים באפליקציה, אבל משתמשות ב-codegen כדי להימנע מהצורך ברפלקציה.