R8 מאפשר להוסיף כללים שמשפיעים על האופטימיזציה של האפליקציה, בנוסף לכללי keep. מוסיפים את הכללים האלה לאותו קובץ proguard-rules.pro שבו מוגדרים כללי השמירה.
הכללים מחולקים לקטגוריות הבאות:
- הנחות
-assumevalues-assumenosideeffects
- אופטימיזציות אחרות
-convertchecknotnull-maximumremovedandroidloglevel
הנחות
הכללים האלה אומרים ל-R8 שהוא יכול להניח הנחות ספציפיות לגבי התנהגות של קוד ספציפי בזמן הריצה.
-assumevalues
הכלל -assumevalues אומר ל-R8 שהערך של שדה או ערך ההחזרה של שיטה הוא תמיד קבוע ספציפי או שהוא נמצא בטווח מוגדר בזמן הריצה. המאפיין -assumevalues מיועד לדברים כמו ערכי דגל, שידועים בזמן הבנייה כבעלי ערכים ספציפיים בזמן הריצה.
יכול להיות שהניתוח הסטטי הרגיל של R8 לא יוכל לקבוע את ערכי זמן הריצה של חברים. באמצעות -assumevalues, אתם אומרים ל-R8 להניח את הערך או הטווח שצוינו כשמבצעים אופטימיזציה של הקוד. כך R8 יכול לבצע אופטימיזציות אגרסיביות.
התחביר של -assumevalues דומה לתחביר של שמירת member_specification, אבל הוא כולל גם return clause באופן הבא:
<member_specification> return <value> | <range>
הארגומנטים <value> ו-<range> תומכים בערכים ובסוגים הבאים:
- ערכים מיוחדים:
true, false, null, @NonNull - ערכים פרימיטיביים:
int - הפניות לשדות סטטיים (כולל שדות enum)
כדי להגדיר טווח, משתמשים בפורמט min..max כולל. לדוגמה, בקטע הקוד הבא אפשר לראות שהמשתנה CUSTOM_VAL מקבל ערכים מ-26 עד 2147483647:
-assumevalues public class com.example.Foo {
public static int CUSTOM_VAL return 26..2147483647;
}
אפשר להשתמש בכלל הזה במקרים הבאים:
- לספריות: כדי לוודא שכשמבצעים אופטימיזציה לאפליקציות, כל ה-hooks של ניפוי הבאגים המקומיים מוסרים מקוד הספרייה הציבורית.
- באפליקציות: כדי להסיר פריטים כמו קוד ניפוי באגים מאפליקציה שמוכנה להפצה. מומלץ להשתמש בווריאציות של build ובווריאציות של קבוצות ספציפיות של קוד מקור או קבועים, אבל אם קבוצות של קוד מקור לווריאציות לא מתאימות לתרחיש שלכם, או אם אתם צריכים לוודא שכל נתיבי הקוד הוסרו, השתמשו ב-
-assumevalues.
בדוגמה הבאה מוצג מחלקה שבה R8 מסיר כלי ניפוי באגים מהגרסה שעברה אופטימיזציה של אפליקציה:
package com.example;
public class MyConfig {
// This field is initialized to false but is overwritten by a resource
// value or other mechanism in the final build process. R8's static analysis
// might see the initial 'false' but the runtime value is known to be
// 'true'.
public static final boolean IS_OPTIMIZED_VERSION = false;
}
// In another class:
public void initFeatures() {
if (MyConfig.IS_OPTIMIZED_VERSION) {
System.out.println("Starting optimized features...");
android.util.Log.d(TAG, "Starting optimized features...");
initOptimizedService();
} else {
android.util.Log.d(TAG, "Starting debug/logging features...");
initDebugTools();
}
}
בדוגמה הבאה אפשר לראות איך אומרים ל-R8 שהמשתנה IS_OPTIMIZED_VERSION תמיד צריך להיות מוגדר לערך true.
-assumevalues class com.example.MyConfig {
public static final boolean IS_OPTIMIZED_VERSION return true;
}
-assumenosideeffects
הכלל -assumenosideeffects אומר ל-R8 שהוא יכול להניח שלחברים שצוינו אין תופעות לוואי. R8 יכול להסיר לחלוטין קריאות לשיטות כאלה שלא מחזירות ערכים או שמחזירות ערך קבוע.
התחביר של -assumenosideeffects דומה לתחביר של שמירת member_specification.
בדוגמה הבאה מוצג איך אומרים ל-R8 שלכל המתודות public static בשם log במחלקה DebugLogger לא צריכות להיות תופעות לוואי, וכך אפשר להסיר קריאות למתודות האלה.
-assumenosideeffects class com.example.DebugLogger {
public static void log(...);
}
אופטימיזציות אחרות
אלה אופטימיזציות מתקדמות יותר שלא מופעלות כברירת מחדל. כשמפעילים אותן, מאפשרים ל-R8 לבצע אופטימיזציה של הקוד לפי ההוראות, בנוסף לאופטימיזציות שמוגדרות כברירת מחדל.
-convertchecknotnull
אפשר להשתמש בכלל -convertchecknotnull כדי לבצע אופטימיזציה של בדיקות ערך null. הדרישה הזו חלה על כל שיטה שמקבלת פרמטר אובייקט ויוצרת חריגה אם האובייקט הוא null, בדומה לאישור רגיל של Kotlin. סוג החריגה וההודעה לא בהכרח זהים, אבל התנהגות הקריסה המותנית זהה.
אם -convertchecknotnull כלל תואם לשיטה נתונה, כל קריאה לשיטה הזו מוחלפת בקריאה ל-getClass() בארגומנט הראשון. הקריאות ל-getClass() משמשות כבדיקת null חלופית ומאפשרות ל-R8 להסיר את כל הארגומנטים הנוספים של בדיקת ה-null המקורית, כמו הקצאות מחרוזות יקרות.
התחביר של -convertchecknotnull הוא:
-convertchecknotnull <class_specification> {
<member_specification>;
}
לדוגמה, אם יש לכם את המחלקה Preconditions עם השיטה checkNotNull באופן הבא:
class Preconditions {
fun <T> checkNotNull(value: T?): T {
if (value == null) {
throw NullPointerException()
} else {
return value
}
}
}
משתמשים בכלל הבא:
-convertchecknotnull class com.example.package.Preconditions {
void checkNotNull(java.lang.Object);
}
הכלל ממיר את כל השיחות אל checkNotNull() לשיחות אל getClass בארגומנט הראשון. בדוגמה הזו, קריאה ל-checkNotNull(bar) מוחלפת ב-bar.getClass(). אם bar היה null, הפונקציה bar.getClass() הייתה מחזירה NullPointerException, וכך הייתה משיגה אפקט דומה של בדיקת ערך null, אבל בצורה יעילה יותר.
-maximumremovedandroidloglevel
סוג הכלל הזה מסיר הצהרות רישום ביומן של Android (כמו
Log.w(...) ו-Log.isLoggable(...)) ברמה מסוימת של יומן או מתחתיה.
התחביר של maximumremovedandroidloglevel הוא:
-maximumremovedandroidloglevel <log_level> [<class_specification>]
אם לא מספקים את האפשרות class_specification, R8 מסיר את היומנים מכל האפליקציה.
רמות היומן הן:
תווית היומן |
רמת היומן |
מפורט |
2 |
ניפוי באגים |
3 |
מידע |
4 |
אזהרה |
5 |
שגיאה |
6 |
ASSERT |
7 |
לדוגמה, אם יש לכם את הקוד הבא:
class Foo {
private static final String TAG = "Foo";
void logSomething() {
if (Log.isLoggable(TAG, WARNING)) {
Log.e(TAG, "Won't be logged");
}
Log.w(TAG, "Won't be logged");
Log.e(TAG, "Will be logged");
}
}
עם הכלל הבא:
# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }
הקוד שעבר אופטימיזציה הוא:
class Foo {
private static final String TAG = "Foo";
void logSomething() {
Log.e(TAG, "Will be logged");
}
}
אם תציינו כמה רמות מקסימליות של יומן לאותה שיטה, R8 ישתמש ברמה המינימלית. לדוגמה, אם הגדרתם את הכללים הבאים:
-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }
אז רמת היומן המקסימלית שהוסרה עבור foo() היא 4.