כמפתחי ספרייה, אתם צריכים לוודא שמפתחי אפליקציות יכולים לשלב בקלות את הספרייה שלכם באפליקציה שלהם, תוך שמירה על חוויית משתמש איכותית. כלומר, הספרייה צריכה להיות תואמת לאופטימיזציה של 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. יצירת קוד ורפלקציה הן שתי גישות נפוצות להימנעות מקוד שחוזר על עצמו (boilerplate) בזמן התכנות, אבל יצירת קוד מתאימה יותר לאופטימיזציה של אפליקציות כמו R8.
ב-codegen, הקוד מנותח ומשתנה במהלך תהליך ה-build. מכיוון שאין שינויים משמעותיים אחרי זמן הקימפול, כלי האופטימיזציה יודע איזה קוד נדרש בסופו של דבר ומה אפשר להסיר בבטחה.
באמצעות רפלקציה, הקוד מנותח ועובר מניפולציה בזמן הריצה. מכיוון שהקוד לא באמת סופי עד שהוא מופעל, הכלי לא יודע איזה קוד אפשר להסיר בבטחה. העדכון כנראה יסיר קוד שנעשה בו שימוש באופן דינמי באמצעות רפלקציה במהלך זמן הריצה, מה שגורם לקריסות של האפליקציה אצל המשתמשים.
הרבה ספריות מודרניות משתמשות ב-codegen במקום ב-reflection. במאמר בנושא KSP מוסבר על נקודת כניסה נפוצה שמשמשת את Room, את Hilt וספריות רבות אחרות.
תמיכה במצב מלא של R8: הספרייה לא אמורה לקרוס כשמופעל מצב מלא של R8. המצב המלא של R8 הוא המצב המומלץ לשימוש ב-R8, והוא מוגדר כברירת מחדל מאז AGP 8.0, שהפך ליציב ב-2023. אם הספרייה קורסת ב-R8, הפתרון הוא לזהות את נקודת הכניסה הספציפית של הרפלקציה או ה-JNI ולהוסיף כלל ממוקד, ולא לשמור את כל החבילה.
המלצות נוספות
בנוסף לדרישות האופטימיזציה, הנה המלצות נוספות.
- אל תשתמשו ב-
-repackageclassesבקובץ כללי השמירה של הצרכן בספרייה. עם זאת, כדי לבצע אופטימיזציה של גרסת ה-build של הספרייה, אתם יכולים להשתמש ב--repackageclassesעם שם חבילה פנימי, כמו<your.library.package>.internal, בקובץ כללי השמירה של גרסת ה-build של הספרייה. זה יכול לשפר את היעילות של הספרייה באפליקציות לא אופטימליות. עם זאת, בדרך כלל אין צורך בכך, כי גם האפליקציות צריכות להיות מותאמות. - צריך להצהיר על כל המאפיינים שדרושים לספרייה כדי לפעול בקבצים של כללי השמירה של הספרייה, גם אם יש חפיפה עם המאפיינים שמוגדרים ב-
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 בדרך הזו מגביל את עלות זמן הריצה, ומאפשר לכתוב כללי שמירה ממוקדים על נתונים של צרכנים.
ההשתקפות הספציפית והממוקדת הזו היא דפוס שניתן לראות גם ב-Android framework (לדוגמה, כשמנפחים פעילויות, תצוגות ופריטים שניתנים לציור) וגם בספריות AndroidX (לדוגמה, כשמבצעים בנייה של WorkManager
ListenableWorkers או RoomDatabases). לעומת זאת, ההשתקפות הפתוחה של Gson לא מתאימה לשימוש באפליקציות ל-Android.
תפיסות מוטעות נפוצות
יש כמה תפיסות מוטעות נפוצות שעלולות לגרום לכם להגדיר את R8 באופן שגוי. למשל:
הבנה שגויה של האופטימיזציות של R8: בניגוד למה שנהוג לחשוב, האופטימיזציות של R8 לא מוגבלות רק להסתרת קוד, אלא כוללות גם צמצום קוד ואופטימיזציות לוגיות באמצעות טכניקות של הטמעת שיטות ומיזוג מחלקות. מידע נוסף זמין במאמר סקירה כללית על אופטימיזציה של R8.
מעקף של אופטימיזציה של ספריות מעורפלות: טעות נפוצה היא להשמיט ספרייה מהאופטימיזציה, כי הספרייה עברה אופטימיזציה או ערפול כשהיא קומפלה ל-AAR (ארכיון Android) או ל-JAR (ארכיון Java). האופטימיזציות במהלך זמן ה-build של הספרייה מוגבלות, והאפליקציה לא אמורה להשבית את האופטימיזציה של הספרייה על ידי הכללה שלה בכלל שמירה. מידע נוסף זמין במאמר בנושא אופטימיזציה של build של ספריית AAR.
הבנה לא נכונה של האפשרות
-keepהכלל-keepמונע מ-R8 להפעיל את מעברי האופטימיזציה שלו. מידע נוסף זמין במאמר בחירת אפשרות השמירה המתאימה.
אימות האיכות של כלל השמירה של הצרכן
כדי לוודא שהספרייה לא מונעת אופטימיזציה של יותר מדי קוד כשהיא מוטמעת באפליקציה, משתמשים בדוגמה של ספרייה או באפליקציית בדיקת שילוב. מפעילים את R8 ובודקים את האיכות הכוללת של הגדרת R8 באפליקציה באמצעות כלי ניתוח ההגדרות של R8.
אם כללי הצרכן שלכם מוצגים כדי להגביל את האופטימיזציה בחלקים גדולים של הקוד בספרייה, זה סימן לכך שצריך לשפר את כללי הצרכן. צמצמו את כללי הצרכן ובדקו את כל ההיבטים המושפעים של הספרייה באפליקציה שבה R8 מופעל.
הגדרת חבילת הכללים
כדי לוודא שכללי השמירה של הצרכן מיושמים בצורה נכונה, צריך לארוז אותם בהתאם לפורמט של הספרייה.
אם אתם משתמשים ב-Jetpack Keep כדי להצהיר על השימוש של הספרייה ב-Reflection וב-JNI, הפלאגין של Gradle יכול לארוז את הכללים שנוצרו ככללי שמירה של הצרכן.
ספריות AAR
כדי להוסיף כללי צרכן לספריית AAR, משתמשים באפשרות consumerProguardFiles בסקריפט ה-build של מודול הספרייה של 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 תומך בכך.
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נארזים בספרייה כדי להשפיע על האופטימיזציות שיתבצעו בהמשך, במהלך יצירת אפליקציה שמשתמשת בספרייה שלכם.
לדוגמה, אם הספרייה משתמשת ברפלקציה כדי ליצור מחלקות פנימיות, יכול להיות שתצטרכו להגדיר את כללי השמירה גם ב-proguardFiles וגם ב-consumerProguardFiles.
אם אתם משתמשים ב--repackageclasses בגרסה של הספרייה, צריך לארוז מחדש את המחלקות לחבילת משנה בתוך החבילה של הספרייה. לדוגמה, צריך להשתמש ב--repackageclasses
'com.example.mylibrary.internal' במקום ב--repackageclasses 'internal'.
תמיכה בגרסאות שונות של 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).