אם אתם יוצרים ספריות, אתם צריכים לוודא שמפתחי אפליקציות יוכלו לשלב את הספרייה באפליקציה שלהם בקלות, תוך שמירה על חוויית משתמש איכותית. צריך לוודא שהספרייה תואמת לאופטימיזציה של Android בלי הגדרה נוספת, או לציין שהספרייה לא מתאימה לשימוש ב-Android.
התיעוד הזה מיועד למפתחים של ספריות שפורסמו, אבל הוא יכול להיות שימושי גם למפתחים של מודולים פנימיים של ספריות באפליקציה גדולה ומודולרית.
מפתחי אפליקציות שרוצים לדעת איך לבצע אופטימיזציה לאפליקציה ל-Android יכולים לקרוא את המאמר בנושא הפעלת אופטימיזציה של אפליקציות. במאמר בחירה מושכלת של ספריות מוסבר אילו ספריות מתאימות לשימוש.
שימוש ב-codegen במקום ב-reflection
כשאפשר, מומלץ להשתמש ביצירת קוד (codegen) במקום ברפלקציה. גם codegen וגם reflection הן גישות נפוצות להימנעות מקוד boilerplate במהלך תכנות, אבל codegen תואם יותר לאופטימיזציה של אפליקציות כמו R8:
- ב-codegen, הקוד מנותח ומשתנה במהלך תהליך ה-build. מכיוון שאין שינויים משמעותיים אחרי זמן ההידור, האופטימיזציה יודעת איזה קוד נדרש בסופו של דבר ואיזה קוד אפשר להסיר בבטחה.
- באמצעות רפלקציה, הקוד מנותח ועובר מניפולציה בזמן הריצה. הקוד לא באמת סופי עד שהוא מופעל, ולכן הכלי לא יודע איזה קוד אפשר להסיר בבטחה. העדכון כנראה יסיר קוד שמשמש באופן דינמי באמצעות רפלקציה במהלך זמן הריצה, וגורם לקריסת האפליקציה אצל המשתמשים.
הרבה ספריות מודרניות משתמשות ביצירת קוד במקום בהשתקפות. אפשר לעיין בKSP כדי לראות נקודת כניסה נפוצה שמשמשת את Room, Dagger2 ועוד.
מתי אפשר להשתמש בהשתקפות
אם אתם חייבים להשתמש ב-reflection, אתם צריכים להשתמש בו רק באחד מהמקרים הבאים:
- סוגים ספציפיים של טירגוט (מיישמי ממשק ספציפיים או מחלקות משנה)
- קידוד באמצעות הערת זמן ריצה ספציפית
שימוש ב-reflection באופן הזה מגביל את עלות זמן הריצה ומאפשר לכתוב כללי שמירה על נתונים של צרכנים שממוקדים במטרה.
ההשתקפות הספציפית והממוקדת הזו היא דפוס שניתן לראות גם ב-Android framework (לדוגמה, כשמנפחים פעילויות, תצוגות ופריטים שניתנים לציור) וגם בספריות AndroidX (לדוגמה, כשמבצעים בנייה של WorkManager
ListenableWorkers או RoomDatabases). לעומת זאת, ההשתקפות הפתוחה של Gson לא מתאימה לשימוש באפליקציות ל-Android.
סוגים של כללי שמירה בספריות
יש שני סוגים שונים של כללי שמירה שאפשר להגדיר בספריות:
- בכללי שמירה על נתונים של צרכנים צריך לציין כללים לשמירה של כל מה שהספרייה משקפת. אם ספרייה משתמשת ב-reflection או ב-JNI כדי לקרוא לקוד שלה, או לקוד שהוגדר על ידי אפליקציית לקוח, הכללים האלה צריכים לתאר איזה קוד צריך לשמור. בספריות צריך לארוז כללי שמירה על נתונים של צרכנים, שמשתמשים באותו פורמט כמו כללי שמירה על נתונים של אפליקציות. הכללים האלה נכללים בארטיפקטים של הספריות (AAR או JAR) ונעשה בהם שימוש אוטומטי במהלך האופטימיזציה של אפליקציית Android כשמשתמשים בספרייה. הכללים האלה מתעדכנים בקובץ שצוין באמצעות המאפיין
consumerProguardFilesבקובץbuild.gradle.kts(אוbuild.gradle). מידע נוסף זמין במאמר בנושא כתיבת כללי שמירה לצרכנים. - כללי השמירה של הספרייה מופעלים כשבונים את הספרייה. הם נדרשים רק אם מחליטים לבצע אופטימיזציה חלקית של הספרייה בזמן הבנייה.
הם צריכים לוודא שממשק ה-API הציבורי של הספרייה לא יוסר, אחרת ממשק ה-API הציבורי לא יהיה זמין בהפצת הספרייה, כלומר מפתחי אפליקציות לא יוכלו להשתמש בספרייה. הכללים האלה מתעדכנים בקובץ שצוין במאפיין
proguardFilesבקובץbuild.gradle.kts(אוbuild.gradle). מידע נוסף זמין במאמר בנושא אופטימיזציה של בניית ספריית AAR.
כתיבת כללי שמירה של צרכנים
בנוסף לשיטות המומלצות הכלליות לגבי כללי שמירה, הנה המלצות שרלוונטיות במיוחד ליוצרי ספריות.
- אל תשתמשו בכללים גלובליים לא מתאימים – אל תציבו הגדרות גלובליות כמו
-dontobfuscateאו-allowaccessmodificationבקובץ הכללים של הספרייה, כי הן משפיעות על כל האפליקציות שמשתמשות בספרייה. - אל תכללו כללי שמירה שחלים על כל החבילה (כמו
-keep class com.mylibrary.** { *; }) לחבילות בספרייה או בספריות אחרות שמופיעות בהפניה. כללים כאלה מגבילים את האופטימיזציה של החבילות האלה בכל האפליקציות שמשתמשות בספרייה שלכם. - אל תשתמשו ב-
-repackageclassesבקובץ כללי השמירה של הספרייה. עם זאת, כדי לבצע אופטימיזציה של בניית הספרייה, אפשר להשתמש ב--repackageclassesעם שם חבילה פנימי, כמו<your.library.package>.internal, בקובץ כללי השמירה של הספרייה. השינוי הזה יכול לשפר את היעילות של הספרייה באפליקציות לא מותאמות. עם זאת, בדרך כלל אין צורך בכך, כי האפליקציות צריכות להיות מותאמות גם הן. - צריך להצהיר על כל המאפיינים שדרושים לספרייה כדי לפעול בקובצי כללי השמירה של הספרייה, גם אם יש חפיפה עם המאפיינים שמוגדרים ב-
proguard-android-optimize.txt. - אם אתם צריכים את המאפיינים הבאים בהפצה של הספרייה, צריך לשמור אותם בקובץ הכללים לשמירה של הספרייה, ולא בקובץ הכללים לשמירה של הצרכן של הספרייה:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- אם נעשה שימוש בהערות בזמן הריצה, מחברי הספריות צריכים להשאיר את המאפיין
RuntimeVisibleAnnotationsבכללי שמירת הנתונים של הצרכנים. - מחברי ספריות לא צריכים להשתמש באפשרויות הגלובליות הבאות בכללי השמירה של הצרכן:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-overloadaggressively-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
ספריות AAR
כדי להוסיף כללי צריכה לספריית AAR, משתמשים באפשרות consumerProguardFiles בסקריפט הבנייה של מודול הספרייה של Android. מידע נוסף זמין בהנחיות שלנו ליצירת מודולים של ספריות.
Kotlin
android { defaultConfig { consumerProguardFiles("consumer-proguard-rules.pro") } ... }
Groovy
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 (מתקדם)
בדרך כלל, אין צורך לבצע אופטימיזציה ישירות של בניית ספרייה, כי האפשרויות לאופטימיזציה בזמן בניית הספרייה מוגבלות מאוד. רק במהלך בניית האפליקציה, כשספרייה נכללת כחלק מהאפליקציה, R8 יכול לדעת איך נעשה שימוש בכל השיטות של הספרייה ואילו פרמטרים מועברים. כמפתחים של ספריות, אתם צריכים לחשוב על כמה שלבים של אופטימיזציה ולשמור על ההתנהגות, גם בזמן בניית הספרייה וגם בזמן בניית האפליקציה, לפני שאתם מבצעים אופטימיזציה של הספרייה.
אם עדיין רוצים לבצע אופטימיזציה של הספרייה בזמן הבנייה, אפשר לעשות זאת באמצעות Android Gradle Plugin.
Kotlin
android { buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } configureEach { consumerProguardFiles("consumer-rules.pro") } } }
Groovy
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 ב-build של הספרייה, צריך לארוז מחדש את המחלקות לחבילת משנה בתוך החבילה של הספרייה. לדוגמה, צריך להשתמש ב--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, לא כולל גרסה 8.2.0.
הפלאגין של Android Gradle משתמש במידע הזה כדי לבחור את כל הכללים שאפשר להשתמש בהם בגרסת R8 הנוכחית. אם בספרייה לא מצוינים כללים מטורגטים של R8, הפלאגין Android Gradle יבחר את הכללים מהמיקומים הקודמים (proguard.txt עבור AAR או META-INF/proguard/<ProGuard-rule-files> עבור JAR).