בפיתוח מודרני של אפליקציות ל-Android, המשתמשים מצפים לקבל אפליקציה קטנה, מהירה ומאובטחת. הכלי העיקרי של מערכת ה-build של Android להשגת המטרה הזו הוא הכלי לאופטימיזציה R8 , המהדר שמטפל בהסרת קוד ומשאבים לא פעילים לצורך כיווץ, שינוי שם של קוד או צמצום קוד ואופטימיזציה של האפליקציה.
הפעלת R8 היא שלב קריטי בהכנת אפליקציה להפצה, אבל המפתחים צריכים לספק הנחיות בצורת "כללי שמירה".
אחרי שתקראו את המאמר הזה, כדאי לצפות בסרטון של Performance Spotlight Week על הפעלה, ניפוי באגים ופתרון בעיות של כלי האופטימיזציה R8 ב-YouTube.
למה צריך כללי שמירה
הצורך לכתוב כללי Keep נובע מקונפליקט בסיסי: R8 הוא כלי ניתוח סטטי, אבל אפליקציות ל-Android מסתמכות לעיתים קרובות על דפוסי הפעלה דינמיים כמו השתקפות או קריאות לקוד Native וממנו באמצעות JNI (ממשק Java Native).
R8 יוצר תרשים של הקוד שבשימוש על ידי ניתוח קריאות ישירות. כשניגשים לקוד באופן דינמי, הניתוח הסטטי של R8 לא יכול לחזות את זה, והוא יזהה את הקוד הזה כלא בשימוש ויסיר אותו, מה שיוביל לקריסות בזמן הריצה.
כלל שמירה הוא הוראה מפורשת לקומפיילר R8, שאומרת: "הכיתה, השיטה או השדה הספציפיים האלה הם נקודת כניסה שתהיה אליה גישה באופן דינמי בזמן הריצה. צריך להשאיר אותו, גם אם לא מוצאים הפניה ישירה אליו".
במדריך הרשמי מפורטים פרטים נוספים על כללי Keep.
איפה כותבים כללי שמירה
כללי שמירה בהתאמה אישית לאפליקציה נכתבים בקובץ טקסט. מקובל לקרוא לקובץ הזה proguard-rules.pro והוא נמצא ברמה הבסיסית (root) של האפליקציה או של מודול הספרייה. לאחר מכן, הקובץ הזה מצוין בסוג ה-build release של קובץ build.gradle.kts של המודול.
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}שימוש בקובץ ברירת המחדל הנכון
השיטה getDefaultProguardFile מייבאת קבוצת כללים שמוגדרת כברירת מחדל ב-Android SDK. אם משתמשים בקובץ הלא נכון, יכול להיות שלא תתבצע אופטימיזציה באפליקציה. חשוב להשתמש ב-proguard-android-optimize.txt. הקובץ הזה מספק את כללי השמירה שמוגדרים כברירת מחדל לרכיבי Android רגילים, ו מפעיל את אופטימיזציות הקוד של R8. הגרסה המיושנת של proguard-android.txt מספקת רק את כללי השמירה הספציפיים (keep rule), אבל לא מפעילה את האופטימיזציות של R8.
מכיוון שמדובר בבעיה חמורה בביצועים, אנחנו מתחילים להזהיר את המפתחים מפני שימוש בקובץ הלא נכון, החל מהשקת התכונות ב-Android Studio Narwhal 3. החל מגרסה 9.0 של הפלאגין של Android Gradle, אנחנו לא תומכים יותר בקובץ proguard-android.txt המיושן. לכן, חשוב לשדרג לגרסה המשופרת.
איך כותבים כללי שמירה
כלל שמירה מורכב משלושה חלקים עיקריים:
- אפשרות כמו
-keepאו-keepclassmembers - מגבילים אופציונליים כמו
allowshrinking - מפרט כיתה שמגדיר את הקוד להתאמה
כדי לראות את התחביר המלא ודוגמאות, אפשר לעיין בהנחיות להוספת כללי שמירה.
תבניות אנטי-דפוס של כללי שמירה
חשוב להכיר את השיטות המומלצות, אבל גם את דפוסי האנטי-תבנית. התבניות האלה נוצרות בדרך כלל בגלל אי-הבנות או קיצורי דרך בפתרון בעיות, והן יכולות לפגוע באופן משמעותי בביצועים של גרסת ה-production.
אפשרויות כלליות
הדגלים האלה הם מתגים גלובליים שאף פעם לא צריך להשתמש בהם בגרסת build להפצה. הם מיועדים רק לניפוי באגים זמני כדי לבודד בעיה.
שימוש ב--dontotptimize משבית למעשה את האופטימיזציות של הביצועים ב-R8, וכתוצאה מכך האפליקציה פועלת לאט יותר.
כשמשתמשים ב--dontobfuscate משביתים את כל שינויי השמות, וכשמשתמשים ב--dontshrink משביתים את ההסרה של קוד מת. שני הכללים הגלובליים האלה מגדילים את נפח האפליקציה.
כדי לשפר את חוויית המשתמש באפליקציה, מומלץ להימנע ככל האפשר משימוש בדגלים הגלובליים האלה בסביבת ייצור.
כללי שמירה רחבים מדי
הדרך הכי קלה לבטל את היתרונות של R8 היא לכתוב כללי שמירה רחבים מדי. כללי שמירה כמו הכלל הבא מורים לכלי האופטימיזציה R8 לא לכווץ, לא לטשטש ולא לבצע אופטימיזציה של אף מחלקה בחבילה הזו או של אף אחת מחבילות המשנה שלה. הפעולה הזו מסירה לחלוטין את היתרונות של R8 לכל החבילה. במקום זאת, נסו לכתוב כללי שמירה מצומצמים וספציפיים.
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMSאופרטור ההיפוך (!)
אופרטור ההיפוך (!) נראה כמו דרך יעילה להחריג חבילה מכלל. אבל זה לא כל כך פשוט. לדוגמה:
-keep class !com.example.my_package.** { *; } // USE WITH CAUTIONיכול להיות שאתם חושבים שהכלל הזה אומר "אל תשמרו מחלקות ב-com.example.package", אבל למעשה הוא אומר "שמרו כל מחלקה, שיטה ומאפיין בכל האפליקציה שלא נמצאים ב-com.example.package". אם זה מפתיע אתכם, כדאי לבדוק אם יש שלילות בהגדרות של R8.
כללים מיותרים לרכיבי Android
טעות נפוצה נוספת היא הוספה ידנית של כללי שמירה ל-Activities, ל-Services או ל-BroadcastReceivers של האפליקציה. זה מיותר. קובץ ברירת המחדל proguard-android-optimize.txt כבר כולל את הכללים הרלוונטיים כדי שרכיבי Android הרגילים האלה יפעלו באופן מיידי.
בנוסף, ספריות רבות כוללות כללי Keep משלהן. לכן, לא תצטרכו לכתוב כללים משלכם בשבילם. אם יש בעיה בכללי השמירה בספרייה שבה אתם משתמשים, מומלץ לפנות ליוצר הספרייה כדי לברר מה הבעיה.
שיטות מומלצות לכללי Keep
עכשיו, אחרי שהסברנו מה לא לעשות, נדבר על השיטות המומלצות.
כתיבת כללי Keep מצומצמים
כללי שמירה טובים צריכים להיות מצומצמים וספציפיים ככל האפשר. הם צריכים לשמר רק את מה שצריך, כדי לאפשר ל-R8 לבצע אופטימיזציה לכל השאר.
| כלל | איכות |
|---|---|
| נמוכה: שמירה של חבילה שלמה ושל חבילות המשנה שלה |
| נמוכה: שמירה על מחלקה שלמה, שסביר להניח שהיא עדיין רחבה מדי |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | גבוהה: רק שיטות ומאפיינים רלוונטיים ממחלקה ספציפית נשמרים |
שימוש באבות קדמונים משותפים
במקום לכתוב כללי שמירה נפרדים לכמה מודלים שונים של נתונים, כותבים כלל אחד שמטרגט מחלקה בסיסית או ממשק משותפים. הכלל הבא מורה ל-R8 לשמור את כל החברים במחלקות שמטמיעות את הממשק הזה, והוא ניתן להרחבה בקלות.
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}שימוש בהערות כדי לטרגט כמה כיתות
יוצרים הערה בהתאמה אישית (למשל, @Serialize) ומשתמשים בה כדי "לתייג" מחלקות שצריך לשמר את השדות שלהן. זהו עוד דפוס נקי, הצהרתי וניתן להרחבה. אתם יכולים גם ליצור כללי שמירה להערות שכבר קיימות ממסגרות שאתם משתמשים בהן.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}בחירת האפשרות המתאימה לשמירה
האפשרות "שמירה" היא החלק הכי חשוב בכלל. אם תבחרו באפשרות הלא נכונה, האופטימיזציה תושבת ללא צורך.
| אפשרות Keep | מה זה עושה |
-keep | מונעת הסרה או שינוי שם של המחלקה והחברים שמוזכרים בהצהרה . |
-keepclassmembers | מונעת הסרה או שינוי שם של החברים שצוינו, אבל מאפשרת להסיר את המחלקה עצמה, ורק במחלקות שלא הוסרו בדרך אחרת. |
-keepclasseswithmembers | שילוב: שמירה על המחלקה וגם על החברים שלה, רק אם כל החברים שצוינו קיימים. |
מידע נוסף על אפשרות השמירה זמין במסמכי התיעוד של אפשרויות השמירה.
אופטימיזציה באמצעות משתנים משנים
משתנים כמו allowshrinking ו-allowobfuscation מרככים כלל -keep רחב, ומחזירים את יכולת האופטימיזציה ל-R8. לדוגמה, אם ספרייה מדור קודם מאלצת אתכם להשתמש ב--keep במחלקה שלמה, יכול להיות שתוכלו לשחזר חלק מהאופטימיזציה על ידי התרת כיווץ וערפול קוד:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
הגדרות גלובליות להוספת אופטימיזציה
בנוסף לכללי השמירה הספציפיים, אתם יכולים להוסיף דגלים גלובליים לקובץ התצורה של R8 כדי לשפר עוד יותר את האופטימיזציה.
-repackageclasses היא אפשרות מתקדמת שמורה ל-R8 להעביר את כל המחלקות המעורפלות לחבילה אחת. כך אפשר לחסוך מקום משמעותי בקובץ ה-DEX על ידי הסרת מחרוזות מיותרות של שמות חבילות.
-allowaccessmodification מאפשרת ל-R8 להרחיב את הגישה (למשל, private ל-public) כדי לאפשר הטבעה אגרסיבית יותר. האפשרות הזו מופעלת עכשיו כברירת מחדל כשמשתמשים ב-proguard-android-optimize.txt.
אזהרה: יוצרי ספריות לא יכולים להוסיף את דגלי האופטימיזציה הגלובליים האלה לכללי הצרכן שלהם, כי הם יוחלו באופן כפוי על כל האפליקציה.
כדי להבהיר את זה עוד יותר, בגרסה 9.0 של הפלאגין של Android Gradle נתחיל להתעלם לחלוטין מדגלי אופטימיזציה גלובליים מספריות.
שיטות מומלצות לספריות
כל אפליקציה ל-Android מסתמכת על ספריות בדרך כזו או אחרת. אז בואו נדבר על השיטות המומלצות לספריות.
למפתחי ספריות
אם הספרייה שלכם משתמשת ב-reflection או ב-JNI, אתם אחראים לספק את כללי השמירה הנדרשים לצרכנים שלה. הכללים האלה ממוקמים בקובץ consumer-rules.pro, שנארז באופן אוטומטי בתוך קובץ ה-AAR של הספרייה.
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}למשתמשים בספרייה
סינון כללי שמירה בעייתיים
אם אתם חייבים להשתמש בספרייה שכוללת כללי שמירה בעייתיים, אתם יכולים לסנן אותם בקובץ build.gradle.kts החל מ-AGP 9.0. כך R8 יתעלם מהכללים שמגיעים מיחסי תלות ספציפיים.
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}הכלל הכי טוב הוא לא להשתמש בכלל שמירה
אסטרטגיית ההגדרה האולטימטיבית של R8 היא להסיר לחלוטין את הצורך בכתיבת כללי Keep. באפליקציות רבות אפשר לעשות זאת על ידי בחירה בספריות מודרניות שמעדיפות יצירת קוד על פני השתקפות. באמצעות יצירת קוד, הכלי לאופטימיזציה יכול לקבוע בקלות רבה יותר באיזה קוד נעשה שימוש בפועל בזמן הריצה ואיזה קוד אפשר להסיר. בנוסף, אם לא משתמשים ברפלקציה דינמית, אין נקודות כניסה "נסתרות", ולכן לא צריך כללי Keep. כשבוחרים ספרייה חדשה, תמיד עדיף פתרון שמשתמש ביצירת קוד על פני רפלקציה.
מידע נוסף על בחירת ספריות זמין במאמר בחירת ספרייה בחוכמה.
ניפוי באגים ופתרון בעיות בהגדרות R8
אם R8 מסיר קוד שהיה צריך להישאר, או אם קובץ ה-APK גדול מהצפוי, אפשר להשתמש בכלים האלה כדי לאבחן את הבעיה.
חיפוש כללי שמירה כפולים וגלובליים
מכיוון ש-R8 ממזג כללים מעשרות מקורות, יכול להיות קשה לדעת מהי קבוצת הכללים "הסופית". הוספת הדגל הזה לקובץ proguard-rules.pro יוצרת דוח מלא:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
אתם יכולים לחפש בקובץ הזה כדי למצוא כללים מיותרים או לאתר כלל בעייתי (כמו -dontoptimize) בספרייה הספציפית שכללה אותו.
שאלה 8: למה אתם שומרים את זה?
אם מחלקה שציפיתם שתהיה מוסרת עדיין נמצאת באפליקציה, R8 יכול להסביר למה. פשוט מוסיפים את הכלל הזה:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
במהלך ה-build, R8 ידפיס את שרשרת ההפניות המדויקת שגרמה לו לשמור את המחלקה הזו, וכך תוכלו לעקוב אחרי ההפניה ולשנות את הכללים.
בקטע פתרון בעיות ב-R8 יש מדריך מלא.
השלבים הבאים
R8 הוא כלי יעיל לשיפור ביצועי האפליקציה ב-Android. היעילות שלו תלויה בהבנה נכונה של הפעולה שלו כמנוע ניתוח סטטי.
כדי לשמור בדיוק את מה שצריך, אתם יכולים לכתוב כללים ספציפיים ברמת החבר, להשתמש בפריטים קודמים ובהערות ולבחור בקפידה את אפשרויות השמירה הנכונות. השיטה המתקדמת ביותר היא לבטל לחלוטין את הצורך בכללים על ידי בחירה בספריות מודרניות שמבוססות על codegen במקום בספריות הקודמות שמבוססות על השתקפות.
במסגרת שבוע ההתמקדות בביצועים, כדאי לצפות בסרטון של היום ב-YouTube ולהמשיך באתגר R8. אם יש לכם שאלות לגבי הפעלה או פתרון בעיות של R8, אתם יכולים להשתמש בתג #optimizationEnabled. אנחנו פה לשירותך.
הגיע הזמן לראות את היתרונות בעצמך.
אנחנו מאתגרים אתכם להפעיל את המצב המלא של R8 באפליקציה שלכם כבר היום.
- כדי להתחיל, כדאי לעיין במדריכים למפתחים: הפעלת אופטימיזציה של האפליקציה.
- בודקים אם עדיין משתמשים ב-
proguard-android.txtומחליפים אותו ב-proguard-android-optimize.txt. - לאחר מכן, מודדים את ההשפעה. אל תסתפקו בתחושה שיש הבדל, תאמתו את זה. כדי למדוד את השיפור בביצועים, אתם יכולים להתאים את הקוד מ האפליקציה לדוגמה Macrobenchmark ב-GitHub ולמדוד את זמני ההפעלה לפני ואחרי.
אנחנו בטוחים שתראו שיפור משמעותי בביצועי האפליקציה.
בזמן שאתם שם, אתם יכולים להשתמש בתג #AskAndroid ברשתות החברתיות כדי לשאול שאלות. במהלך השבוע, המומחים שלנו עוקבים אחרי השאלות שלכם ועונים עליהן.
מחר נדבר על אופטימיזציה מבוססת-פרופיל (PGO) עם פרופילים של Baseline ופרופילים להפעלה, נסביר איך ביצועי הרינדור של Compose השתפרו בגרסאות האחרונות ונשתף שיקולים שקשורים לביצועים של עבודה ברקע.
-
חדשות על המוצרהיום, במפגש של מפתחי Android, אנחנו מכריזים על הזמינות הרחבה של הרבה כלים וממשקי API שנועדו להסיר את החיכוך היומיומי ולתת לכם יותר גמישות ליצור אפליקציות Android נהדרות.
Matthew McCullough • משך הקריאה: 4 דקות -
חדשות על המוצרגרסת Android Studio Rabbit 2 יציבה ומוכנה לשימוש בסביבת הייצור. הגרסה הזו כוללת תכונות חשובות שמשפרות את הפיתוח, כמו Bring Your Own Agent, תכונות חדשות של אמולטור XR, עדכון של LeakCanary, כלי חדש לניהול מיומנויות של סוכנים, סקר קוד באמצעות AI ועוד.
Matthew Warner • משך הקריאה: 3 דקות -
חדשות על המוצראפליקציות שמטרגטות את הגרסה הבאה של Android לא יוכלו יותר לכתוב ישירות לאנשי קשר באמצעות ההרשאה WRITE_CONTACTS.
Roxanna Aliabadi Walker • משך הקריאה: 6 דקות
רוצים לקבל טיפים עדכניים לפיתוח Android ישירות לאימייל כל שבוע?