כדי להפעיל אופטימיזציה של אפליקציות, צריך להשתמש בספריות שתואמות לאופטימיזציה של Android. אם ספרייה לא מוגדרת לאופטימיזציה ל-Android – למשל, אם היא משתמשת ברפלקציה בלי לאגד כללי שמירה משויכים – יכול להיות שהיא לא תתאים לאפליקציית Android. בדף הזה מוסבר למה חלק מהספריות מתאימות יותר לאופטימיזציה של אפליקציות, ומוצגים טיפים כלליים שיעזרו לכם לבחור.
טיפים כלליים לבחירת ספריות
הטיפים הבאים יעזרו לכם לוודא שהספריות שלכם תואמות לאופטימיזציה של האפליקציה.
העדפה של יצירת קוד על פני רפלקציה
בוחרים ספריות שמשתמשות ביצירת קוד (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 פועלות היטב עם אופטימיזציה של אפליקציות, כי הן משתמשות בעיקר ביצירת קוד במקום בהשתקפות. אם הם משתמשים בהשתקפות, הם מספקים כללי שמירה מינימליים כדי לשמור רק את הקוד שנדרש.
- ספריות סריאליזציה משתמשות לעיתים קרובות ב-reflection כדי להימנע מקוד חוזר כשיוצרים מופעים של אובייקטים או מבצעים סריאליזציה שלהם. במקום להשתמש בגישות שמבוססות על reflection (כמו Gson ל-JSON), כדאי לחפש ספריות שמשתמשות ב-codegen כדי להימנע מהבעיות האלה. לדוגמה, אפשר להשתמש ב-Kotlin Serialization או ב-Moshi עם codegen.
- אם אפשר, כדאי להימנע מספריות שכוללות כללי שמירה שחלים על כל החבילה. כללי שמירה שחלים על כל החבילה יכולים לעזור לפתור שגיאות, אבל בסופו של דבר צריך לצמצם את כללי השמירה הרחבים כדי לשמור רק את הקוד שנדרש. מידע נוסף זמין במאמר בנושא יישום אופטימיזציות באופן הדרגתי.
- ספריות לא אמורות לדרוש מכם להעתיק ולהדביק כללי שמירה ממסמכים לקובץ בפרויקט, במיוחד לא כללי שמירה שחלים על כל החבילה. הכללים האלה הופכים לנטל תחזוקה על מפתח האפליקציה בטווח הארוך, וקשה לבצע אופטימיזציה ולשנות אותם עם הזמן.
הפעלת אופטימיזציה אחרי הוספה של ספרייה חדשה
כשמוסיפים ספרייה חדשה, צריך להפעיל את האופטימיזציה ולבדוק אם יש שגיאות. אם יש שגיאות, מחפשים חלופות לספרייה הזו או כותבים כללי שמירה. אם ספרייה לא תואמת לאופטימיזציה, צריך לדווח על באג בספרייה הזו.
סינון כללי שמירה לא טובים (מתקדם)
כללי השמירה הם מצטברים. כלומר, אי אפשר להסיר כללים מסוימים שכלולים ביחסי תלות של ספרייה, והם עשויים להשפיע על הקומפילציה של חלקים אחרים באפליקציה. לדוגמה, אם ספרייה כוללת כלל להשבתת אופטימיזציות של קוד, הכלל הזה משבית את האופטימיזציות של כל הפרויקט.
כדאי להימנע מספריות עם כללי שמירה ששומרים קוד שבאמת צריך להסיר. אבל אם אתם חייבים להשתמש בהם, אתם יכולים לסנן את הכללים כמו שמוצג בקוד הבא:
// 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, הוא יכול לשנות את השם של השדות או להסיר בנאים שלא נראה שהם בשימוש, ולגרום לקריסת האפליקציה. אם אתם משתמשים בספריות אחרות בדרכים דומות, כדאי לוודא שהן לא יפריעו לאופטימיזציה של האפליקציה, ואם כן, עדיף להימנע מהן.
כדי להגדיר את המחלקות באופן שתואם לכללי הצרכן של 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 כדי להימנע מהצורך ברפלקציה.