בפיתוח מודרני של אפליקציות ל-Android, משתמשים מצפים לקבל אפליקציה קטנה, מהירה ומאובטחת. הכלי העיקרי במערכת ה-build של Android להשגת המטרה הזו הוא R8 optimizer, הקומפיילר שמטפל בהסרה של קוד ומשאבים לא פעילים לצורך כיווץ, שינוי שם או מזעור של קוד ואופטימיזציה של האפליקציה.
הפעלת R8 היא שלב חשוב בהכנת אפליקציה לפרסום, אבל היא מחייבת את המפתחים לספק הנחיות בצורה של "כללי שמירה".
אחרי שתקראו את המאמר הזה, תוכלו לצפות בסרטון 'הדגשת ביצועים: שבוע' בנושא הפעלה, איתור באגים ופתרון בעיות בכלי האופטימיזציה R8 ב-YouTube.
למה צריך כללים לשמירה
הצורך בכתיבת כללי Keep נובע מסתירה בסיסית: R8 הוא כלי לניתוח סטטי, אבל אפליקציות ל-Android מסתמכות לעיתים קרובות על דפוסי ביצוע דינמיים כמו רפלקציה או קריאות לתוך קוד Native ומחוצה לו באמצעות JNI (Java Native Interface).
R8 יוצר גרף של קוד בשימוש על ידי ניתוח של קריאות ישירות. כשניגשים לקוד באופן דינמי, ניתוח סטטי של R8 לא יכול לחזות את זה, והוא יזהה את הקוד כלא בשימוש ויסיר אותו, מה שיוביל לקריסות בזמן הריצה.
כלל שמירה הוא הוראה מפורשת לקומפיילר R8, שבה מצוין: "המחלק, השיטה או השדה הספציפיים האלה הם נקודת כניסה שאליה תהיה גישה דינמית בזמן הריצה. אתם חייבים לשמור אותו, גם אם אתם לא מוצאים הפניה ישירה אליו".
פרטים נוספים על כללי השמירה זמינים במדריך הרשמי.
איפה כותבים כללים ב-Keep
כללי שמירה מותאמים אישית של Keep לאפליקציה נכתבים בקובץ טקסט. לפי המוסכמה, שם הקובץ הזה הוא proguard-rules.pro והוא נמצא בבסיס של האפליקציה או של מודול הספרייה. לאחר מכן מציינים את הקובץ הזה בקובץ build.gradle.kts של המודול בסוג build release.
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 Feature Drop. החל מגרסה 9.0 של פלאגין Android Gradle, אנחנו כבר לא תומכים בקובץ proguard-android.txt המיושן. לכן חשוב לשדרג לגרסה האופטימלית.
איך כותבים כללים ב-Keep
כלל שמירה מורכב משלושה חלקים עיקריים:
- אפשרות כמו
-keepאו-keepclassmembers - משנים אופציונליים כמו
allowshrinking - מפרט כיתה שמגדיר את הקוד להתאמה
לעיון בתחביר המלא ובדוגמאות, אפשר לעיין בהנחיות בנושא הוספת כללי שמירה.
Keep Rule anti-patterns
חשוב להכיר את השיטות המומלצות, אבל גם את האנטי-דפוסים. האנטי-דפוסים האלה נובעים לרוב מאי-הבנות או מקיצורי דרך לפתרון בעיות, והם עלולים לפגוע באופן משמעותי בביצועים של גרסת ייצור.
אפשרויות כלליות
הדגלים האלה הם מתגים גלובליים שאף פעם לא צריכים לשמש בגרסת 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 צריכים להיות מצומצמים וספציפיים ככל האפשר. הם צריכים לשמור רק את מה שנדרש, כדי לאפשר ל-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 להערות שכבר קיימות מתוך מסגרות שבהן אתם משתמשים.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}בחירת האפשרות המתאימה ב-Keep
האפשרות Keep (שמירה) היא החלק הכי חשוב בכלל. בחירה לא נכונה עלולה להשבית את האופטימיזציה ללא צורך.
| אפשרות 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
הוספת הגדרות גלובליות לאופטימיזציה נוספת
בנוסף לכללי השמירה הספציפיים (keep rule), אפשר להוסיף לדגל הגלובלי בקובץ ההגדרות של 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")
}
...
}למשתמשים בספרייה
סינון כללי שמירה בעייתיים ב-Keep
אם אתם חייבים להשתמש בספרייה שכוללת כללי שמירה בעייתיים, אתם יכולים לסנן אותם בקובץ 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. במקרים רבים, אפשר להשיג זאת על ידי בחירה בספריות מודרניות שמעדיפות יצירת קוד על פני רפלקציה. בעזרת יצירת קוד, הכלי לאופטימיזציה יכול לקבוע בקלות רבה יותר איזה קוד נמצא בשימוש בפועל בזמן הריצה ואיזה קוד אפשר להסיר. בנוסף, אם לא משתמשים בהשתקפות דינמית, אין נקודות כניסה 'מוסתרות', ולכן אין צורך בכללי שמירה. כשבוחרים ספרייה חדשה, עדיף תמיד לבחור פתרון שמשתמש ביצירת קוד ולא ברפלקציה.
מידע נוסף על בחירת ספריות זמין במאמר בחירה נכונה של ספריות.
ניפוי באגים ופתרון בעיות בהגדרות 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) עד לספרייה הספציפית שכוללת אותו.
שואלים את R8: למה שמרת את זה?
אם כיתה שציפיתם שהיא תוסר עדיין מופיעה באפליקציה, R8 יכול להסביר לכם למה. פשוט מוסיפים את הכלל הזה:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
במהלך הבנייה, R8 ידפיס את שרשרת ההפניות המדויקת שגרמה לו לשמור את המחלקה הזו, כך שתוכלו לעקוב אחרי ההפניה ולשנות את הכללים.
מדריך מלא זמין בקטע פתרון בעיות ב-R8.
השלבים הבאים
R8 הוא כלי יעיל לשיפור ביצועי האפליקציה ב-Android. היעילות שלו תלויה בהבנה נכונה של אופן הפעולה שלו כמנוע ניתוח סטטי.
כדי לשמור בדיוק את מה שצריך, אפשר לכתוב כללים ספציפיים ברמת החבר, להשתמש בנתוני צאצאים ובהערות, ולבחור בקפידה את אפשרויות השמירה הנכונות. השיטה המתקדמת ביותר היא לבחור ספריות מודרניות שמבוססות על יצירת קוד (codegen) במקום ספריות קודמות שמבוססות על רפלקציה, כדי שלא יהיה צורך בכללים בכלל.
במהלך שבוע ההדגשה של הביצועים, כדאי לצפות בסרטון של היום ב-YouTube ולהמשיך עם האתגר R8 שלנו. אם יש לכם שאלות לגבי הפעלה או פתרון בעיות ב-R8, תוכלו להשתמש בתג #optimizationEnabled. אנחנו פה לשירותך.
הגיע הזמן לראות את היתרונות בעצמכם.
אנחנו ממליצים להפעיל את המצב המלא של R8 באפליקציה עוד היום.
- כדי להתחיל, כדאי לעיין במדריכים למפתחים: הפעלת אופטימיזציה של אפליקציות.
- בודקים אם אתם עדיין משתמשים ב-
proguard-android.txtומחליפים אותו ב-proguard-android-optimize.txt. - לאחר מכן, מודדים את ההשפעה. אל תסתפקו בתחושה של הבדל, אמתו אותו. כדי למדוד את השיפור בביצועים, אפשר להתאים את הקוד מ אפליקציית הדוגמה Macrobenchmark ב-GitHub כדי למדוד את זמני ההפעלה לפני ואחרי.
אנחנו בטוחים שתראו שיפור משמעותי בביצועים של האפליקציה.
אתם מוזמנים להשתמש בתג #AskAndroid ברשתות החברתיות כדי לשאול שאלות. במהלך השבוע, המומחים שלנו בודקים את השאלות ועונים עליהן.
מחר נדבר על אופטימיזציה מודרכת של פרופילים באמצעות פרופילים של Baseline ופרופילים להפעלה, נסביר איך השתפרו ביצועי העיבוד של Compose בגרסאות האחרונות ונשתף שיקולים לגבי ביצועים של עבודה ברקע.
-
חדשות על מוצריםלמפתחי Android יש הרבה אפשרויות בכל הנוגע לסוכנים, למודלים גדולים של שפה (LLM), לכלים ולממשקי שורת פקודה (CLI) שבהם הם משתמשים לפיתוח אפליקציות. המטרה שלנו היא לעזור לכם לפתח אפליקציות יפות ואיכותיות ל-Android, לא משנה איך תבחרו לפתח אותן.
Simona Milanovic • משך הקריאה: 4 דקות -
חדשות על מוצריםב-Google Play, אנחנו מרחיבים כל הזמן את פלטפורמת המינויים שלנו כדי לעזור לכם להגדיל את הצמיחה, להתאים את עצמכם למודלים עסקיים חדשים ולהגיע למשתמשים שלכם בדיוק במקום שבו הם נמצאים.
Sheenam Mittal • משך הקריאה: 4 דקות -
חדשות על מוצריםבשנה שעברה, פתחנו את Android Studio לכל מודל AI. היום אנחנו עושים את הצעד הבא ומשיקים תמיכה בסוכני קידוד לפי בחירתכם.
Matthew Warner • משך הקריאה: 3 דקות
רוצים לקבל טיפים עדכניים לפיתוח Android ישירות לאימייל כל שבוע?