כשמפעילים אופטימיזציה של אפליקציות עם הגדרות ברירת המחדל, R8 מבצע אופטימיזציות נרחבות כדי למקסם את יתרונות הביצועים. R8 מבצע שינויים משמעותיים בקוד, כולל שינוי השם של שדות ושיטות של כיתות, העברה שלהם והסרה שלהם. אם הפעולה הזו גורמת לשגיאות, צריך לציין אילו חלקים בקוד לא לשנות על ידי כתיבת keep rules.
יכול להיות ש-R8 יסיר או ישנה קוד באופן שגוי במצבים הבאים:
- השתקפות (Reflection): קוד שגולשים ניגשים אליו באמצעות השתקפות, למשל באמצעות
Class.forName()
אוMethod.invoke()
. בדרך כלל, R8 לא יכול לדעת לאילו שיטות או כיתות תהיה גישה בדרך הזו. - שרשור (serialization): יכול להיות ש-R8 תראה ששימוש בקטגוריות או בשדות שנדרשים לשרשור ולביטול שרשור (deserialization) לא נדרש (זוהי צורה אחרת של רפלקציה).
- Java Native Interface (JNI): שיטות Java שנקראות מקוד מקורי. R8 לא מנתח את הקוד המקורי כדי לראות לאילו פונקציות הוא עשוי להפעיל חזרה ב-Java.
בדף הזה נסביר איך להגביל את היקף האופטימיזציות של R8. במאמר הוספת כללי שמירת נתונים למשאבים מוסבר איך להתאים אישית את המשאבים שרוצים לשמור.
איפה מוסיפים כללי שמירה
צריך להוסיף את הכללים לקובץ proguard-rules.pro
שנמצא בתיקיית השורש של המודול (יכול להיות שהקובץ כבר נמצא שם, אבל אם לא, צריך ליצור אותו). כדי להחיל את הכללים בקובץ, צריך להצהיר על הקובץ בקובץ build.gradle.kts
(או build.gradle
) ברמת המודול, כפי שמתואר בקוד הבא:
Kotlin
android { buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles( // Default file with default optimization rules. getDefaultProguardFile("proguard-android-optimize.txt"), // File with your custom rules. "proguard-rules.pro" ) ... } } ... }
Groovy
android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles( // Default file with default optimization rules. getDefaultProguardFile('proguard-android-optimize.txt'), // File with your custom rules. 'proguard-rules.pro' ) } } // ... }
כברירת מחדל, סקריפט ה-build כולל גם את הקובץ proguard-android-optimize.txt
. הקובץ הזה כולל כללים שנדרשים לרוב הפרויקטים ל-Android, לכן כדאי להשאיר אותו בסקריפט ה-build.
איך כותבים כללי שמירה
במהלך הידור האפליקציה, R8 מזהה איזה קוד צריך להישאר באפליקציה על ידי ניתוח תרשים הקריאות של האפליקציה, שמתחיל ברשומות המניפסט (כמו הפעילויות או השירותים) וממשיך דרך כל קריאה לפונקציה של האפליקציה והספרייה. R8 מסיר קוד שלא מופיעה לו הפניה ישירה באופן הזה, וזה עלול לגרום לבעיות אם הקוד שהופעל לא נכלל בתרשים הזה, למשל קוד שהופעל באמצעות רפלקציה. כשיוצרים כללי שמירה משלכם, אפשר להודיע ל-R8 על קוד שצריך להישאר באפליקציה.
כדי להוסיף כלל שמאפשר לשמור את הקובץ, מוסיפים שורה -keep
לקובץ proguard-rules.pro
.
שמירה על התחביר של הכללים
בדרך כלל, כללי Keep בנויים בפורמט הזה:
-<KeepOption> [OptionalModifier,...] <ClassSpecification> [{ OptionalMemberSpecification }]
לדוגמה, כדי לשמור על כיתה ספציפית ועל כל החברים שלה, משתמשים בקוד הבא:
-keep class com.myapp.MyClass { *; }
דוגמאות נוספות זמינות בקטע הדוגמאות.
כך פועלים הרכיבים של כלל השמירה:
בעזרת
<KeepOption>
אפשר לציין אילו היבטים של הכיתה לשמור:האפשרות 'שמירה' תיאור -keep
לשמור את הכיתה ואת התלמידים שמפורטים ב-
[{ OptionalMemberSpecification }]
.-keepclassmembers
מאפשרת לבצע אופטימיזציה של הכיתה. אם הכיתה נשמרת, גם המשתתפים שמפורטים ב-
[{ OptionalMemberSpecification }]
נשמרים.-keepnames
לאפשר הסרה של הכיתה והחברים, אבל לא לטשטש או לשנות אותם בדרכים אחרות.
-keepclassmembernames
לאפשר הסרה של הכיתה והחברים, אבל לא להסתיר או לשנות את החברים בדרכים אחרות.
-keepclasseswithmembers
אין הסרה או ערפול של כיתות אם המשתתפים תואמים לדפוס שצוין.
מומלץ להשתמש ב-
-keepclassmembers
בעיקר, כי הוא מאפשר את רוב האופטימיזציות, ואז ב--keepnames
אם יש צורך.-keep
לא מאפשר אופטימיזציה, לכן כדאי להשתמש בו במשורה.בעזרת
[OptionalModifier],...]
אפשר לרשום אפס או יותר משתני לשון של Java של כיתה, לדוגמהpublic
אוfinal
.בעזרת
<ClassSpecification>
אפשר לציין על איזו מחלקה (או על איזו מחלקה אב או על איזה ממשק מוטמע) יחול כלל השמירה. במקרה הפשוט ביותר, זוהי מחלקה אחת מוגדרת במלואה.[{ OptionalMemberSpecification }]
מאפשר לסנן את התנהגות השמירה כך שתחול רק על שיטות ועל כיתות שתואמות לדפוסים מסוימים. באופן כללי, מומלץ להשתמש באפשרות הזו ברוב כללי השמירה כדי למנוע שמירה של יותר מהמתוכנן.
שמירה של דוגמאות לכללים
ריכזנו כאן כמה דוגמאות לכללי שמירה שתוכננו היטב.
יצירה של מחלקה משנית
כלל השמירה הזה ארוז בתוך androidx.room:room-runtime
כדי לשמור על היכולת ליצור מופעים של מגדירי מסדי נתונים באמצעות רפלקציה.
-keep class * extends androidx.room.RoomDatabase { void <init>(); }
השתקפות מ-Android Framework ObjectAnimator
כלל השמירה הזה ארוז בתוך androidx.vectordrawable:vectordrawable-animated
כדי לאפשר ל-ObjectAnimator
לבצע קריאה ל-getters או ל-setters מקוד מקומי באמצעות JNI. הערה: מערכות אנימציה חדשות יותר ב-Compose לא דורשות כללי שמירה כאלה, זו רק דוגמה למבנה של כלל.
-keepclassmembers class androidx.vectordrawable.graphics.drawable.VectorDrawableCompat$* {
void set*(***);
*** get*();
}
רישום JNI
כלל השמירה הזה ארוז בתוך androidx.graphics:graphics-path
, ומשמשים לשמירת שיטות מקוריות. יכול להיות שתצטרכו לעשות זאת אם הספרייה שלכם רושמת באופן ידני שיטות מקוריות באמצעות env->RegisterNatives()
.
-keepclasseswithmembers class androidx.graphics.path.** {
native <methods>;
}