כיווץ ואופטימיזציה של קוד Unreal Engine באמצעות R8

למרות שלוגיקת המשחק העיקרית ב-Unreal Engine כתובה ב-C++‎ או ב-Blueprints, האריזה של Android מקמפלת את קוד הבייט של Java ו-Kotlin‏ (classes.dex) כדי לטפל באירועים במחזור החיים של GameActivity, ב-Google Play Services, בחיוב, באודיו וביישומי פלאגין של ניתוח נתונים.

‫R8, הכלי שמשמש כברירת מחדל לצמצום קוד ולאופטימיזציה ב-פלאגין של Android Gradle‏ (AGP), מסיר מחלקות ושיטות שלא בשימוש ומבצע אופטימיזציה של בייטקוד. כך לא רק מקטינים את גודל ההורדה של החבילה, אלא גם את הזיכרון שבשימוש בזמן הריצה של דפי DEX שממופים בזיכרון (mmap) ואת התקורה של תהליך Java בסיסי. זה מפחית את העומס על הזיכרון הפיזי ואת מספר הסגירות של האפליקציה על ידי מנגנון הסגירה במצב של מחסור בזיכרון (LMK). מערכת Google Play עוקבת גם אחרי רמות האופטימיזציה של קובצי ה-DEX במשחק באמצעות המדד אופטימיזציה של קוד DEX בתפקוד האפליקציה.

התנהגות ברירת המחדל ותמיכה בגרסאות של Unreal Engine

החל מגרסה UE 4.22.0, כברירת מחדל, UEDeployAndroid.cs של Unreal Engine מוסיף את minifyEnabled true לתצורת ה-build של גרסת Android להפצה (buildAdditions.gradle). ב-UE 4.25, ה-toolchain שכלול בחבילה שודרג ל-AGP 4.0.0, ולכן R8 מופעל כברירת מחדל (true) בכל גרסאות ה-Release (הפצה ומשלוח) ב-UE 4.25 ואילך, בלי הגדרה נוספת.

גרסת Unreal Engine ‫AGP בחבילה ברירת המחדל של הגרסה ברירת המחדל לניפוי באגים התנהגות
‫UE 4.21 וגרסאות קודמות Gradle או Ant מדור קודם מושבת (false) מושבת (false) המנוע לא הוסיף הגדרות של minifyEnabled.
UE 4.22 עד 4.24 גרסאות קודמות ל-3.4 מופעל (ProGuard) מושבת (false) ‫minifyEnabled true מופעל כברירת מחדל בגרסאות build להפצה (מפעיל את ProGuard).
UE 4.25 עד 5.3 4.0.0 מופעל (R8) מושבת (false) בגרסאות AGP 4.0 ואילך, R8 פועל כברירת מחדל בגרסאות build להפצה.
UE 5.4 עד 5.7 7.4.2 מופעל (R8) מושבת (false) ב-AGP 7.x, אפשרויות useProguard מדור קודם הוסרו, ו-R8 פועל באופן בלעדי.
UE 5.8 (5.8.3+) 8.13.0 מופעל (R8) מושבת (false) כברירת מחדל, משתמש ב-AGP 8.x ובצינורות עיבוד נתונים מודרניים של אופטימיזציה של R8.

ההבדלים בין גרסת build לפרסום לבין גרסת build לניפוי באגים

‫UnrealBuildTool‏ (UEDeployAndroid.cs) מגדיר את הקובץ buildAdditions.gradle שנוצר באופן שונה לגרסאות build של Release ו-גרסת build לניפוי באגים:

buildTypes {
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    debug {
        debuggable true
        signingConfig signingConfigs.debug
    }
}
  • גרסת הפצה (משלוח או הפצה): מגדירה את minifyEnabled true ומחילה את proguard-rules.pro כברירת מחדל, ומפעילה כיווץ קוד וערפול קוד של R8.
  • ניפוי באגים (פיתוח): מפעיל את debuggable true ומשמיט את minifyEnabled (ברירת המחדל היא false) כדי לשמור על איטרציה מהירה של גרסאות build וניפוי באגים של סמלים.

הוספת כללי שמירה באמצעות Unreal Plugin Language‏ (UPL)

מכיוון ש-R8 פועל באופן אוטומטי בגרסאות build להפצה ב-UE 4.25 ואילך, צריך לשמור על מחלקות Java שאפשר להגיע אליהן רק דרך C++ JNI (JNIEnv->FindClass, GetMethodID) או המניפסט. מערכת R8 מתייחסת למחלקות בלי אתרי קריאה ב-Java כלא בשימוש ומסירה אותן, מה שעלול לגרום לתכונות שפועלות בגרסאות פיתוח להיכשל עם ClassNotFoundException או NoSuchMethodError בגרסאות הפצה.

משתמשים בתג <proguardAdditions> בקובץ XML של Unreal Plugin Language (UPL) כדי למזג כללי שמירה מותאמים אישית לתוך proguard-rules.pro במהלך האריזה. מוסיפים את הבלוק הבא לקובץ *_UPL.xml של הפרויקט או הפלאגין:

<root xmlns:android="http://schemas.android.com/apk/res/android">
    <proguardAdditions>
        <insert>
            -keep class com.epicgames.unreal.** { *; }
            -keep class com.example.plugin.** { public *; }
            -dontwarn com.example.plugin.**
        </insert>
    </proguardAdditions>
</root>

פתרון בעיות נפוצות

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

השבתה זמנית של R8 לצורך ניפוי באגים

כדי לבודד קריסה שמתרחשת רק בגרסת ה-Release ולבדוק אם היא נגרמת בגלל ש-R8 מסיר מחלקות נדרשות, מוסיפים את DISABLE_MINIFY=1 מתחת ל-<gradleProperties> בקובץ ה-UPL. כשהוא קיים, UEDeployAndroid.cs יוצר את minifyEnabled false:

<gradleProperties>
    <insert>
        DISABLE_MINIFY=1
    </insert>
</gradleProperties>

Missing classes detected while running R8 שגיאות אריזה

בגרסה UE 5.4 ואילך (AGP 7.4.2 ואילך), R8 אוכף בדיקות מחמירות של מחלקות חסרות. אם האריזה נכשלת עם השגיאה הזו, בודקים את missing_rules.txt בספריית ה-build הזמנית (ב-Intermediate/Android/.../gradle/app/build/outputs/mapping/release/missing_rules.txt) ומוסיפים את יחסי התלות החסרים בספרייה או את הכללים הנדרשים ל-<proguardAdditions> בקובץ ה-UPL.

תאימות של מצב מלא ב-R8 עם ערכות SDK של צד שלישי

ב-UE מגרסה 5.8 ואילך (AGP 8.13.0 ואילך), מצב מלא של R8 מופעל כברירת מחדל כדי לבצע אופטימיזציות אגרסיביות יותר. אם ערכת Android SDK מדור קודם של צד שלישי נכשלת בזמן הריצה בגלל בעיות ב-reflection, צריך לעדכן את כללי ProGuard של הפלאגין או להחזיר את R8 למצב תאימות על ידי הוספת המאפיין הבא לקובץ ה-UPL:

<gradleProperties>
    <insert>
        android.enableR8.fullMode=false
    </insert>
</gradleProperties>