הפעלת אופטימיזציה של אפליקציות באמצעות R8

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

  • זמן הפעלה מהיר יותר
  • שימוש מופחת בזיכרון
  • שיפורים בביצועי העיבוד והזמן הריצה
  • פחות מקרי ANR

סקירה כללית על אופטימיזציה של R8

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

  • כיווץ קוד (שנקרא גם tree shaking): ‏ R8 מזהה ומסיר קוד שלא ניתן להגיע אליו מהאפליקציה ומהתלות שלה בספריות. על ידי ניתוח נקודות הכניסה של האפליקציה (כמו Activities או Services שמוגדרות במניפסט), ‏ R8 יוצר גרף של קוד עם הפניות ומסיר כל דבר שלא נכללת אליו הפניה.

  • אופטימיזציות לוגיות: R8 כותב מחדש את הקוד כדי לשפר את יעילות הביצוע ולהפחית את התקורה. הטכניקות העיקריות כוללות:

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

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

  • ערפול קוד (obfuscation) (נקרא גם minification): כדי להקטין את הגודל של קובץ ה-DEX,‏ R8 מקצר את השמות של המחלקות, השדות והשיטות (לדוגמה, com.example.MyActivity יכול להפוך ל-a.b.a).

החל מגרסה 8.12.0 של Android Gradle Plugin‏ (AGP), ‏ R8 גם מבצע אופטימיזציה של משאבים כחלק משלבי האופטימיזציה שלו. מידע נוסף זמין במאמר בנושא הקטנת משאבים בצורה אופטימלית.

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

כדי להפעיל אופטימיזציה של האפליקציה, צריך להגדיר את isMinifyEnabled = true (לאופטימיזציה של הקוד) ואת isShrinkResources = true (לאופטימיזציה של המשאבים) בסקריפט הבנייה ברמת האפליקציה של גרסת ההפצה, כמו שמוצג בקוד הבא. מומלץ להפעיל תמיד את שתי ההגדרות. מומלץ גם להפעיל אופטימיזציה של האפליקציה רק בגרסה הסופית של האפליקציה שאתם בודקים לפני הפרסום – בדרך כלל גרסת build להפצה – כי האופטימיזציות מאריכות את משך זמן של תהליך build של הפרויקט ויכולות להקשות על ניפוי הבאגים בגלל האופן שבו הן משנות את הקוד.

Kotlin

android {
    buildTypes {
        release {

            // Enables code-related app optimization.
            isMinifyEnabled = true

            // Enables resource shrinking.
            isShrinkResources = true

            proguardFiles(
                // Default file with automatically generated optimization rules.
                getDefaultProguardFile("proguard-android-optimize.txt"),

                ...
            )
            ...
        }
    }
    ...
}

Groovy

android {
    buildTypes {
        release {

            // Enables code-related app optimization.
            minifyEnabled true

            // Enables resource shrinking.
            shrinkResources true

            // Default file with automatically generated optimization rules.
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')

            ...
        }
    }
}

אופטימיזציה של כיווץ מקורות המידע כדי ליצור אפליקציות קטנות עוד יותר

בגרסה 8.12.0 של פלאגין של Android Gradle ‏ (AGP) מוצגת אופטימיזציה של כיווץ מקורות המידע, שמטרתה לשלב אופטימיזציה של משאבים וקוד כדי ליצור אפליקציות קטנות ומהירות עוד יותר.

לפני האופטימיזציה של כיווץ מקורות המידע, Android Asset Packaging Tool‏ (AAPT2) יצר כללי שמירה שטיפלו בכיווץ מקורות המידע בנפרד מהקוד, ולעתים קרובות שמרו קוד או משאבים שלא הייתה אליהם גישה, או שהייתה הפניה הדדית ביניהם.

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

הפעלת כיווץ מקורות המידע הממוטב

כדי להפעיל את צינור העיבוד החדש והמשופר לכיווץ מקורות המידע בגרסה של AGP לפני 9.0.0, מוסיפים את הקוד הבא לקובץ gradle.properties של הפרויקט:

android.r8.optimizedResourceShrinking=true

אם אתם משתמשים ב-AGP 9.0.0 או בגרסה חדשה יותר, אתם לא צריכים להגדיר את android.r8.optimizedResourceShrinking=true. כיווץ מקורות המידע האופטימלי מופעל באופן אוטומטי כשמפעילים את isShrinkResources = true בתצורת ה-build שלכם.

אימות והגדרה של הגדרות האופטימיזציה של R8

כדי לאפשר ל-R8 להשתמש ביכולות האופטימיזציה המלאות שלו, צריך להסיר את השורה הבאה מקובץ gradle.properties של הפרויקט, אם היא קיימת:

android.enableR8.fullMode=false # Remove this line from your codebase.

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

אם R8 מופעל, כדאי גם ליצור פרופילים להפעלה כדי לשפר עוד יותר את ביצועי ההפעלה.

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

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

שינויים בהתנהגות של גרסאות AGP ו-R8

בטבלה הבאה מפורטות התכונות העיקריות שהושקו בגרסאות שונות של פלאגין של Android Gradle‏ (AGP) ושל R8 compiler.

גרסת AGP תכונות חדשות
‫9.1 מחלקות שנארזות מחדש כברירת מחדל: R8 אורז מחדש מחלקות (מעביר אותן לחבילה ללא שם, ברמה העליונה) כדי לדחוס עוד יותר את קובץ ה-DEX, וכך אין צורך לציין את האפשרות -repackageclasses. מידע על אופן הפעולה של האפשרות הזו ועל אופן ההשבתה שלה זמין במאמר בנושא אפשרויות גלובליות.
9.0 הקטנת משאבים שעברה אופטימיזציה: מופעלת כברירת מחדל (נשלטת באמצעות android.r8.optimizedResourceShrinking). הקטנת משאבים שעברה אופטימיזציה עוזרת לשלב הקטנת משאבים עם צינור האופטימיזציה של הקוד, וכך ליצור אפליקציות קטנות ומהירות יותר. האופטימיזציה מתבצעת גם בקוד וגם בהפניות למשאבים בו-זמנית, כך שהיא מזהה ומסירה משאבים שההפניה אליהם היא רק מקוד שלא נמצא בשימוש. זהו שיפור משמעותי לעומת תהליכי האופטימיזציה הנפרדים הקודמים.

האפשרות הזו שימושית במיוחד לאפליקציות שמשתפות משאבים וקוד משמעותיים בין ענפים שונים של גורמי צורה, עם שיפורים שנמדדו של יותר מ-50% בגודל האפליקציה. הקטנת הגודל מאפשרת הורדות קטנות יותר, התקנות מהירות יותר וחוויית משתמש טובה יותר עם הפעלה מהירה יותר, רינדור משופר ופחות שגיאות ANR.

סינון כללים של ספריות: התמיכה באפשרויות גלובליות (לדוגמה, -dontobfuscate) בכללי צרכן של ספריות הוסרה, והאפליקציות יסננו אותן. מידע נוסף זמין במאמר בנושא הוספת אפשרויות גלובליות.

בדיקות של ערכי null ב-Kotlin: מבוצעות אופטימיזציה כברירת מחדל (נשלטות באמצעות -processkotlinnullchecks). בגרסה הזו בוצעו גם שיפורים משמעותיים במהירות ה-build. מידע נוסף זמין במאמר בנושא הגדרות גלובליות להוספת אופטימיזציה.

אופטימיזציה של חבילות ספציפיות: אפשר להשתמש ב-packageScope כדי לבצע אופטימיזציה של חבילות ספציפיות. התמיכה הזו ניסיונית. מידע נוסף זמין במאמר בנושא אופטימיזציה של חבילות שצוינו באמצעות packageScope.

אופטימיזציה כברירת מחדל: הפסקנו לתמוך ב-getDefaultProguardFile("proguard-android.txt") כי הוא כולל את -dontoptimize, ומומלץ להימנע משימוש בו. במקום זאת, צריך להשתמש ב-"proguard-android-optimize.txt". אם אתם צריכים להשבית את האופטימיזציה באפליקציה באופן גלובלי, צריך להוסיף את הדגל באופן ידני לקובץ Proguard.
8.12 כיווץ מקורות המידע: נוספה תמיכה ראשונית (ההגדרה מושבתת כברירת מחדל. הפעלה של שימוש ב-isShrinkResources). כיווץ מקורות המידע פועל בשילוב עם R8 כדי לזהות ולהסיר משאבים לא בשימוש בצורה יעילה.

Logcat retracing: Support for automatic retracing in the Android Studio Logcat window.
8.6 שיפורים באיתור מקורות: כולל איתור מקורות לפי שם הקובץ ומספר השורה כברירת מחדל לכל הרמות של minSdk (בעבר נדרשה רמה של minSdk 26 ומעלה בגרסה 8.2).

עדכון R8 עוזר לוודא שניתן לקרוא בקלות ובבירור את עקבות המחסנית (stack traces) מגרסאות obfuscated. בגרסה הזו שיפרנו את המיפוי של מספרי השורות וקבצי המקור, כדי שיהיה קל יותר לכלים כמו Logcat ב-Android Studio לשחזר באופן אוטומטי קריסות לקוד המקור המקורי.
8.0 מצב מלא כברירת מחדל: מצב מלא של R8 מספק אופטימיזציה חזקה משמעותית. ההגדרה הזו מופעלת כברירת מחדל. אפשר לבטל את ההצטרפות באמצעות android.enableR8.fullMode=false.
7.0 מצב מלא זמין: השקנו את המצב הזה כתכונה אופציונלית באמצעות android.enableR8.fullMode=true. במצב מלא, המערכת מבצעת אופטימיזציות חזקות יותר על ידי הנחות מחמירות יותר לגבי האופן שבו הקוד משתמש בהשתקפות ובתכונות דינמיות אחרות. השימוש ב-ProGuard מקטין את גודל האפליקציה ומשפר את הביצועים, אבל יכול להיות שיהיה צורך להוסיף כללי שמירה כדי למנוע את הסרת הקוד הנדרש.