אם אתם יוצרים ספריות, אתם צריכים לוודא שמפתחי אפליקציות יוכלו לשלב בקלות את הספרייה באפליקציה שלהם, ועדיין לספק חוויית משתמש איכותית. כלומר, הספרייה צריכה להיות תואמת לאופטימיזציה של Android (R8) בלי לדרוש הגדרה נוספת מהמפתח – או שצריך לציין שהספרייה לא מתאימה לשימוש ב-Android. חשוב מאוד שספריות שמיועדות לשימוש ב-Android לא ימנעו אופטימיזציות חשובות של אפליקציות ויעמדו בדרישות אופטימיזציה נוספות.
התיעוד הזה מיועד למפתחים של ספריות שפורסמו, אבל הוא יכול להיות שימושי גם למפתחים של מודולים פנימיים של ספריות באפליקציה גדולה ומודולרית.
מפתחי אפליקציות שרוצים לדעת איך לבצע אופטימיזציה לאפליקציה ל-Android יכולים לעיין במאמר בנושא הפעלת אופטימיזציה של אפליקציות. במאמר בחירה מושכלת של ספריות מוסבר אילו ספריות מתאימות לשימוש.
הסבר על סוגים של כללי שמירה
יש שני סוגים שונים של כללי שמירה שאפשר להגדיר בספריות:
- כללי שמירה על נתונים של צרכנים צריכים לציין כללים לשמירה של כל מה שהספרייה משקפת. אם ספרייה משתמשת ב-reflection או ב-JNI כדי לקרוא לקוד שלה, או לקוד שהוגדר על ידי אפליקציית לקוח, הכללים האלה צריכים לתאר איזה קוד צריך לשמור. ספריות צריכות לארוז כללי שמירה של צרכנים, שמשתמשים באותו פורמט כמו כללי שמירה של אפליקציות. הכללים האלה נכללים בארטיפקטים של ספריות (AAR או JAR) ונעשה בהם שימוש אוטומטי במהלך האופטימיזציה של אפליקציית Android כשמשתמשים בספרייה. הכללים האלה מנוהלים בקובץ שצוין באמצעות המאפיין
consumerProguardFilesבקובץbuild.gradle.kts(אוbuild.gradle). מידע נוסף על המלצות נוספות - כללי שמירה של בניית ספריות מופעלים כשבונים את הספרייה. הם נדרשים רק אם מחליטים לבצע אופטימיזציה חלקית של הספרייה בזמן הבנייה. הם צריכים לוודא שממשק ה-API הציבורי של הספרייה לא יוסר, אחרת ממשק ה-API הציבורי לא יהיה זמין בהפצת הספרייה, כלומר מפתחי אפליקציות לא יוכלו להשתמש בספרייה. הכללים האלה מנוהלים בקובץ שצוין באמצעות המאפיין
proguardFilesבקובץbuild.gradle.kts(אוbuild.gradle). מידע נוסף זמין במאמר אופטימיזציה של בניית ספריית AAR.
דרישות והנחיות לאופטימיזציה
להגדרות של R8 בספריות יש השפעה גלובלית על הגודל הסופי של הקובץ הבינארי ועל הביצועים של האפליקציה שמשתמשת בספריות. בנוסף לשיטות המומלצות הכלליות לכללי שמירה, יוצרי ספריות צריכים לעמוד בדרישות ספציפיות ולפעול בהתאם להנחיות נוספות.
עמידה בדרישות האופטימיזציה
חוסר יעילות בספריות תורם באופן משמעותי לניפוח האפליקציה, לבזבוז זיכרון, להפעלות איטיות ולמקרי ANR (שגיאות מסוג 'האפליקציה לא מגיבה'). כדי להימנע מפגיעה משמעותית באיכות האפליקציה ובחוויית המשתמש, הספריות צריכות לעמוד בדרישות הבאות:
אין כללי שמירה רחבים או כאלה שחלים על כל החבילה: בספרייה לא יכולים להיות כללי שמירה רחבים ששומרים את רוב הקוד בספרייה או בספרייה אחרת. כללי שמירה רחבים עשויים לפתור קריסות בטווח הקצר, אבל הם מגדילים את גודל האפליקציה של כל האפליקציות שמשתמשות בספרייה שלכם.
אל תכללו כללי שמירה שחלים על כל החבילה (כמו
-keep class com.mylibrary.** {*; }) בחבילות בספרייה או בספריות אחרות שמפנים אליהן הפניות. כללים כאלה מגבילים את האופטימיזציה של החבילות האלה בכל האפליקציות שמשתמשות בספרייה שלכם.אין כללים גלובליים לא הולמים: אל תשתמשו באפשרויות גלובליות כמו
-dontobfuscateאו-allowaccessmodification.שימוש ב-codegen במקום ב-reflection כשזה אפשרי: כשזה אפשרי, עדיף להשתמש ביצירת קוד (codegen) במקום ב-reflection. יצירת קוד (codegen) ורפלקציה הן שתי גישות נפוצות להימנעות מקוד חוזר (boilerplate) בתכנות, אבל יצירת קוד תואמת יותר לאופטימיזציה של אפליקציות כמו R8.
ב-codegen, הקוד מנותח ומשתנה במהלך תהליך ה-build. מכיוון שאין שינויים משמעותיים אחרי משך הזמן לקימפול, האופטימיזציה יודעת איזה קוד נדרש בסופו של דבר ואיזה קוד אפשר להסיר בבטחה.
באמצעות רפלקציה, הקוד מנותח ועובר מניפולציה בזמן הריצה. הקוד לא באמת סופי עד שהוא מופעל, ולכן הכלי לא יודע איזה קוד אפשר להסיר בבטחה. העדכון כנראה יסיר קוד שנעשה בו שימוש באופן דינמי באמצעות רפלקציה במהלך זמן הריצה, מה שגורם לקריסת האפליקציה אצל המשתמשים.
הרבה ספריות מודרניות משתמשות ביצירת קוד במקום בהשתקפות. מידע על נקודת כניסה נפוצה מופיע במאמר בנושא KSP, שמשמשת את Room, Hilt ועוד.
תמיכה במצב מלא של R8: הספרייה לא אמורה לקרוס כשמופעל מצב מלא של R8. המצב המלא של R8 הוא המצב המומלץ לשימוש ב-R8, והוא מוגדר כברירת מחדל מאז AGP 8.0, שהפכה ליציבה בשנת 2023. אם הספרייה קורסת ב-R8, הפתרון הוא לזהות את נקודת הכניסה הספציפית של השתקפות או JNI ולהוסיף כלל ממוקד, ולא לשמור את כל החבילה.
המלצות נוספות
בנוסף לדרישות האופטימיזציה, הנה המלצות נוספות.
- אל תשתמשו ב-
-repackageclassesבקובץ כללי השמירה של הצרכנים בספרייה. עם זאת, כדי לבצע אופטימיזציה של בניית הספרייה, אפשר להשתמש ב--repackageclassesעם שם חבילה פנימי, כמו<your.library.package>.internal, בקובץ כללי השמירה של הספרייה. הפעולה הזו יכולה לשפר את היעילות של הספרייה באפליקציות לא מותאמות. עם זאת, בדרך כלל אין צורך בכך, כי האפליקציות אמורות להיות מותאמות גם הן. - צריך להצהיר על כל המאפיינים שדרושים לספרייה כדי לפעול בקבצים של כללי השמירה של הספרייה, גם אם יש חפיפה עם המאפיינים שמוגדרים ב-
proguard-android-optimize.txt. - אם אתם צריכים את המאפיינים הבאים בהפצה של הספרייה, צריך לשמור אותם בקובץ הכללים לשמירה של הספרייה, ולא בקובץ הכללים לשמירה של הצרכן של הספרייה:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- מפתחי ספריות צריכים לשמור את המאפיין
RuntimeVisibleAnnotationsבכללי השמירה לצרכנים אם נעשה שימוש באנוטציות בזמן ריצה. - יוצרי ספריות לא צריכים להשתמש באפשרויות הגלובליות הבאות בכללי השמירה של הצרכן:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
מתי אפשר להשתמש בהשתקפות
אם אתם חייבים להשתמש בהשתקפות, אתם יכולים להשתקף רק באחת מהאפשרויות הבאות:
- סוגים ספציפיים של טירגוט (מיישמי ממשק ספציפיים או מחלקות משנה)
- קידוד באמצעות הערה ספציפית של זמן ריצה
שימוש ב-reflection באופן הזה מגביל את עלות זמן הריצה ומאפשר לכתוב כללי שמירה על נתונים של צרכנים שמיועדים למטרה מסוימת.
הדפוס הזה של reflection ספציפי וממוקד מופיע גם ב-framework של Android (לדוגמה, במהלך טעינת משאבי המערכת) וגם בספריות AndroidX (לדוגמה, כשיוצרים WorkManager
ListenableWorkers או RoomDatabases). לעומת זאת, ה-reflection הפתוח של Gson לא מתאים לשימוש באפליקציות ל-Android.
תפיסות מוטעות נפוצות
יש כמה תפיסות מוטעות נפוצות שעלולות לגרום לכם להגדיר את R8 בצורה שגויה. למשל:
הבנה לא נכונה של האופטימיזציות של R8: בניגוד למה שמקובל לחשוב, האופטימיזציות של R8 לא מוגבלות רק להסתרת קוד, אלא כוללות גם צמצום קוד ואופטימיזציות לוגיות עם טכניקות של הטמעת שיטות ומיזוג מחלקות. מידע נוסף זמין במאמר סקירה כללית על אופטימיזציה של R8.
דילוג על אופטימיזציה של ספריות שעברו טשטוש: שגיאה נפוצה היא השמטה של ספרייה מהאופטימיזציה, כי הספרייה עברה אופטימיזציה או טשטוש כשהיא קומפלה ל-AAR (ארכיון Android) או ל-JAR (ארכיון Java). האופטימיזציות במהלך משך זמן של תהליך build של הספרייה מוגבלות, ואסור לאפליקציה להשבית את האופטימיזציה של הספרייה על ידי הכללתה בכלל שמירה. מידע נוסף זמין במאמר סקירה כללית על אופטימיזציה ב-R8.
הבנה לא נכונה של האפשרות
-keepהכלל-keepמונע מ-R8 להריץ את מעברי האופטימיזציה שלו. מידע נוסף זמין במאמר בנושא בחירת אפשרות השמירה הנכונה.
אימות האיכות של כלל השמירה של הצרכן
כדי לוודא שהספרייה לא מונעת אופטימיזציה של יותר מדי קוד כשהיא מוטמעת באפליקציה, משתמשים בדוגמה של ספרייה או באפליקציית בדיקת שילוב. מפעילים את R8 ובודקים את האיכות הכוללת של הגדרת R8 באפליקציה באמצעות כלי ניתוח ההגדרות של R8.
אם כללי הצרכן מוצגים כדי להגביל את האופטימיזציה בחלקים גדולים של קוד הספרייה, זה סימן לכך שצריך לשפר את כללי הצרכן. כדאי לצמצם את כללי הצרכנים ולבדוק באפליקציה עם R8 מופעל את כל ההיבטים של הספרייה שמושפעים מהשינוי.
הגדרת אריזת הכללים
כדי לוודא שכללי השמירה של נתוני הצרכנים מוחלים בצורה נכונה, צריך לארוז אותם בצורה מתאימה בהתאם לפורמט של הספרייה.
ספריות AAR
כדי להוסיף כללים לצרכנים בספריית AAR, משתמשים באפשרות consumerProguardFiles בסקריפט הבנייה של מודול הספרייה של Android. מידע נוסף זמין בהנחיות שלנו ליצירת מודולים של ספריות.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
מגניב
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
ספריות JAR
כדי לארוז כללים עם ספריית Kotlin או Java שנשלחת כ-JAR, צריך להוסיף את קובץ הכללים לספרייה META-INF/proguard/ של ה-JAR הסופי, עם שם קובץ כלשהו.
לדוגמה, אם הקוד שלכם נמצא ב-<libraryroot>/src/main/kotlin, צריך להציב קובץ כללים של צרכן ב-<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro, והכללים יצורפו במיקום הנכון ב-JAR של הפלט.
כדי לוודא שהכללים של חבילות ה-JAR הסופיות נכונים, בודקים שהכללים נמצאים בספרייה META-INF/proguard.
אופטימיזציה של בניית ספריית AAR (מתקדם)
בדרך כלל לא צריך לבצע אופטימיזציה של בניית ספריות באופן ישיר, כי האופטימיזציות האפשריות בזמן משך זמן של תהליך build של הספריות מוגבלות מאוד. כמפתחי ספריות, אתם צריכים לחשוב על כמה שלבים של אופטימיזציה ולשמור על התנהגות, גם בזמן משך זמן של תהליך build של הספרייה וגם בזמן משך זמן של תהליך build של האפליקציה, לפני שאתם מבצעים אופטימיזציה של הספרייה.
אם עדיין רוצים לבצע אופטימיזציה של הספרייה בזמן הבנייה, אפשר לעשות זאת באמצעות Android Gradle Plugin.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
מגניב
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
שימו לב שההתנהגות של proguardFiles שונה מאוד מזו של consumerProguardFiles:
proguardFilesמשמשים בזמן הבנייה, לרוב יחד עםgetDefaultProguardFile("proguard-android-optimize.txt"), כדי להגדיר איזה חלק מהספרייה צריך לשמור במהלך בניית הספרייה. לפחות, זה ה-API הציבורי שלכם.- לעומת זאת,
consumerProguardFilesנארזים בספרייה כדי להשפיע על האופטימיזציות שיתבצעו בהמשך, במהלך ה-build של אפליקציה שמשתמשת בספרייה שלכם.
לדוגמה, אם הספרייה שלכם משתמשת ברפלקציה כדי ליצור מחלקות פנימיות, יכול להיות שתצטרכו להגדיר את כללי השמירה גם ב-proguardFiles וגם ב-consumerProguardFiles.
אם משתמשים ב--repackageclasses בגרסה של הספרייה, צריך לארוז מחדש את המחלקות לחבילת משנה בתוך החבילה של הספרייה. לדוגמה, צריך להשתמש ב--repackageclasses
'com.example.mylibrary.internal' במקום ב--repackageclasses 'internal'.
אופטימיזציה של הביצועים של ממשק המשתמש של כלי הכתיבה
אם הספרייה כוללת רכיבי ממשק משתמש של Compose, בצע אופטימיזציה גם לצמצום בינארי באמצעות R8 (כפי שמתואר למעלה) וגם ליעילות של רה-קומפוזיציה:
- תכנון של פונקציות composable יציבות: כדי לאפשר לקומפיילר לדלג בבטחה על פונקציות composable כשלא חלו שינויים בקלט, צריך לבנות את מודלי הנתונים ואת הגדרות הספריות כך שיפעלו לפי כללי היציבות. מידע נוסף זמין במאמר בנושא יציבות ב-Compose.
- בדיקת מדדי הקומפיילר: אפשר להשתמש במדדי הקומפיילר של Compose כדי לוודא שנקודות הכניסה של הספרייה מסומנות כניתנות להפעלה מחדש ולדילוג. כך מצמצמים את התקורה של הרכבה מחדש באפליקציות צרכניות. מידע נוסף מופיע במאמר אבחון בעיות יציבות.
תמיכה בגרסאות שונות של R8 (מתקדם)
אתם יכולים להתאים כללים לטירגוט גרסאות ספציפיות של R8. כך הספרייה שלכם תוכל לפעול בצורה אופטימלית בפרויקטים שמשתמשים בגרסאות חדשות יותר של R8, ועדיין תוכלו להשתמש בכללים קיימים בפרויקטים עם גרסאות ישנות יותר של R8.
כדי לציין כללי R8 ממוקדים, צריך לכלול אותם בספרייה META-INF/com.android.tools בתוך classes.jar של AAR או בספרייה META-INF/com.android.tools של JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
בספרייה META-INF/com.android.tools יכולות להיות כמה ספריות משנה עם שמות מהצורה r8-from-<X>-upto-<Y>, כדי לציין לאילו גרסאות של R8 נכתבו הכללים. כל תיקיית משנה יכולה להכיל קובץ אחד או יותר עם כללי R8, עם שמות קבצים וסיומות כלשהם.
הערה: החלקים -from-<X> ו--upto-<Y> הם אופציונליים, הגרסה <Y> היא בלעדית, וטווח הגרסאות הוא בדרך כלל רציף אבל יכול להיות גם חופף.
לדוגמה, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 ו-r8-from-8.2.0 הם שמות של ספריות שמייצגות קבוצה של כללי R8 שמיועדים לטירגוט. אפשר להשתמש בכל גרסאות R8 בכללים שבספרייה r8. אפשר להשתמש בכללים בספרייה r8-from-8.0.0-upto-8.2.0 ב-R8 מגרסה 8.0.0 עד גרסה 8.2.0, לא כולל.
הפלאגין של Android Gradle משתמש במידע הזה כדי לבחור את כל הכללים שאפשר להשתמש בהם בגרסה הנוכחית של R8. אם בספרייה לא מצוינים כללים מטורגטים של R8, הפלאגין של Android Gradle יבחר את הכללים מהמיקומים הקודמים (proguard.txt עבור AAR או META-INF/proguard/<ProGuard-rule-files> עבור JAR).