אימוץ אופטימיזציות באופן מצטבר

כברירת מחדל, R8 מבצע הרבה אופטימיזציות כדי לשפר את הביצועים והגודל, אבל יכול להיות שהאופטימיזציות לא יפעלו באפליקציה שלכם באופן מיידי. אם אתם מפעילים את R8 (או מפעילים את מצב מלא) באפליקציה גדולה בפעם הראשונה, כדאי להטמיע את האופטימיזציות באופן הדרגתי: להשבית זמנית את ההסתרה ולהפעיל את R8 לחלקים של הקוד בכל פעם, במקום לכל הקוד באפליקציה. מומלץ להשתמש בגישה ההדרגתית הזו במהלך פיתוח מקומי, אבל אפשר להשתמש בה גם במהלך בדיקות QA פנימיות או אפילו בסביבת הייצור כהשקה הדרגתית. השלבים המדויקים שתבצעו תלויים בלוח הזמנים הרצוי ובמידת הוודאות שלכם לגבי כיסוי הבדיקות טרום-השקה.

הגבלת האופטימיזציות

‫R8 מבצע סוגים רבים של אופטימיזציות, כולל הסרת קוד, שכתוב קוד והסרת משאבים. ריכזנו כאן תיאורים כלליים של סוגי האופטימיזציה:

  • כיווץ קוד (או tree shaking): הסרת קוד מיותר
  • ערפול קוד (או צמצום מזהים): מקצר את השמות של המחלקות והמתודות
  • אופטימיזציה: כתיבה מחדש של הקוד, למשל הטמעה

כדי לצמצם את הסיכוי לשגיאות, אפשר להתחיל בהפעלה של חלק מהאופטימיזציות האלה.

הפעלת tree shaking בלבד

כיווץ קוד, שנקרא גם tree shaking, מסיר קוד שנראה שאין אליו הפניות. מומלץ להתחיל רק עם tree shaking, כי זו השיטה הכי פשוטה.

כדי להפעיל רק את tree shaking, מוסיפים את מה שמופיע כאן למטה לקובץ proguard-rules.pro כדי להשבית את סוגי האופטימיזציה האחרים. חשוב להשבית את ערפול הקוד (obfuscation) כי זה מקל מאוד על קריאת דוחות הקריסות.

-dontobfuscate // Use temporarily to turn off identifier minification
-dontoptimize // Use temporarily to turn off optimization

בסופו של דבר, לא כדאי להשתמש בהגדרה הזו, כי היא מגבילה באופן משמעותי את היכולת של R8 לבצע אופטימיזציה של הקוד. עם זאת, זו נקודת התחלה מצוינת כשמשתמשים ב-R8 בפעם הראשונה בבסיס קוד גדול עם בעיות שצריך לתקן.

שימוש במצב תאימות

כברירת מחדל, R8 פועל במצב מלא. מצב מלא משפר באופן משמעותי את הביצועים ואת החיסכון בגודל, אבל אפשר להשבית אותו באופן זמני ולהשתמש במצב תאימות במקום זאת כשמפעילים את ההקטנה בפעם הראשונה.

כדי להשתמש במצב תאימות, צריך להשתמש בהגדרה הבאה בקובץ gradle.properties:

android.enableR8.fullMode = false // Use temporarily to disable full mode

הפעלת שאר האופטימיזציות

אחרי שמוודאים ש-tree shaking פועל באפליקציה, אפשר להסיר את ההגדרות הקודמות כדי להפעיל מחדש את הערפול, האופטימיזציה והמצב המלא של R8. חשוב לזכור שהערפול עלול להקשות על ניפוי הבאגים, ולכן מומלץ קודם לטפל בבעיות שקשורות ל-tree shaking.

מידע נוסף על ביטול ההסתרה של דוחות קריסות זמין במאמר בנושא שחזור דוחות הקריסות המקוריים.

הגבלת היקף האופטימיזציה

ב-build שעבר אופטימיזציה מלאה, המערכת מבצעת אופטימיזציה של כל הקוד בכל הספרייה והחבילה, ולכן נפוץ להיתקל בבעיות ב-R8 כשמפעילים אותו בפעם הראשונה. אם אתם נתקלים בבעיה באופטימיזציה בחלק מסוים של האפליקציה, אל תשביתו את R8 לגמרי, כי אז תאבדו את היתרונות שלו בכל שאר החלקים. במקום זאת, צריך להשבית את R8 באופן זמני רק בחלקים של האפליקציה שגורמים לבעיות.

שימוש בכללי שמירה שחלים על כל החבילה

מומלץ להשתמש בכללי שמירה ברמת החבילה כדי להשבית זמנית את R8 בחלקים של האפליקציה. תמיד צריך לחזור ולתקן את בעיות האופטימיזציה האלה מאוחר יותר. בדרך כלל זהו פתרון זמני כדי לעקוף אזורים בעייתיים.

לדוגמה, אם חלק מהאפליקציה משתמש ב-Gson באופן אינטנסיבי וגורם לבעיות באופטימיזציה, הפתרון האידיאלי הוא להוסיף עוד כללי שמירה ממוקדים או לעבור לפתרון של יצירת קוד. אבל כדי לבטל את החסימה של האופטימיזציה של שאר האפליקציה, אתם יכולים להציב את הקוד שמגדיר את סוגי ה-Gson בחבילת משנה ייעודית, ולהוסיף כלל כזה לקובץ proguard-rules.pro:

-keep class com.myapp.json.** { *; }

אם בספרייה שבה אתם משתמשים יש רפלקציה לרכיבים פנימיים, אתם יכולים להוסיף כלל שמירה לכל הספרייה. תצטרכו לבדוק את הקוד של הספרייה או את קובץ ה-JAR או ה-AAR כדי למצוא את החבילה המתאימה שצריך לשמור. שוב, לא מומלץ להשתמש בפתרון הזה לטווח ארוך, אבל הוא יכול לאפשר את האופטימיזציה של שאר האפליקציה:

-keep class com.somelibrary.** { *; }

הסרת כללי שמירה שחלים על כל החבילה

אחרי שהאפליקציה פועלת כמו שצריך עם כללי שמירה ברמת החבילה, צריך לחזור ולהוסיף כללי שמירה ממוקדים, או להסיר את השימוש ב-reflection או את הספרייה שמחייבת את כלל השמירה מלכתחילה.

לדוגמה, שמירה של כל הכללים שמרחיבים מחלקה מסוימת היא פעולה נפוצה מאוד ב-AndroidX כדי לשמור רק מחלקות רלוונטיות. באופן כללי, רפלקציה צריכה לטרגט רק מחלקות או שיטות שמרחיבות מחלקות מופשטות מסוימות, מטמיעות ממשקים מסוימים או מחלקות עם הערת זמן ריצה ספציפית. כל אחת מהשיטות האלה היא דרך נתמכת להגדיר כללי שמירה, כך שלא תצטרכו כללי שמירה שחלים על כל החבילה באפליקציה הסופית והממוטבת שלכם.

אם יש רפלקציה בבסיס הקוד שלכם, אתם יכולים להחליף את כללי השמירה ברמת החבילה ב-Jetpack Keep. הכללים שנוצרים הם מותנים: R8 שומר את היעד של קריאה רפלקטיבית רק אם הוא שומר גם את הקוד שמבצע את הקריאה. כדי להעביר חבילה, משתמשים ב-Jetpack Keep lint checks כדי למצוא את הקריאות הרפלקטיביות שלה ולהוסיף להן הערות, ואז מסירים את הכלל שחל על כל החבילה.