縮減、模糊處理及最佳化應用程式

為了盡量縮小應用程式的大小,建議您在發布子版本中啟用「縮減」,藉此移除未使用的程式碼和資源。啟用縮減時,您也可以受益於模糊處理,這項功能可縮短應用程式類別和成員的名稱,以及「最佳化」,後者會套用更積極的策略,進一步縮減應用程式大小。本頁將說明 R8 如何為專案執行這些編譯時間工作,以及如何自訂這些工作。

使用 Android Gradle 外掛程式 3.4.0 以上版本建立專案時,這個外掛程式將不再使用 ProGuard 執行編譯時間程式碼最佳化作業。不過,外掛程式會與 R8 編譯器搭配運作,以處理下列編譯時間工作:

  • 程式碼縮減 (或搖樹):偵測並安全地從應用程式及其程式庫依附元件中移除未使用的類別、欄位、方法和屬性 (使其成為解決 64k 參考資料上限的寶貴工具)。例如,如果您只使用程式庫依附元件中的幾個 API,則縮減可以識別應用程式使用的程式庫程式碼,並僅從應用程式中刪除該程式碼。詳情請參閱如何縮減程式碼
  • 資源縮減:從封裝應用程式中移除未使用的資源,包括應用程式程式庫依附元件中未使用的資源。這個程式碼會與程式碼縮減搭配使用,這樣只要移除未使用的程式碼,所有參照的資源都能安全移除。詳情請參閱如何縮減資源一節。
  • 模糊處理:縮短類別和成員的名稱,進而縮減 DEX 檔案大小。詳情請參閱「如何對程式碼進行模糊處理」一節。
  • 最佳化:檢查及重新編寫程式碼,進一步縮減應用程式的 DEX 檔案大小。例如,如果 R8 偵測到特定 if/else 陳述式的 else {} 分支從未被擷取,R8 就會移除 else {} 分支版本的程式碼。詳情請參閱程式碼最佳化一節。

建構應用程式的發布版本時,您可以設定 R8 來執行上述編譯時間工作。您也可以透過 ProGuard 規則檔案停用特定工作或自訂 R8 的行為。事實上,R8 適用於所有現有的 ProGuard 規則檔案,因此更新 Android Gradle 外掛程式並不需要變更現有規則。

啟用縮減、模糊處理和最佳化功能

使用 Android Studio 3.4 或 Android Gradle 外掛程式 3.4.0 及以上版本時,R8 是預設編譯器,可將專案的 Java 位元碼轉換為在 Android 平台上執行的 DEX 格式。不過,當您使用 Android Studio 建立新專案時,系統預設會啟用縮減、模糊化和程式碼最佳化功能。這是因為編譯時間最佳化功能可以增加專案的建構時間,如果沒有充分自訂要保留的程式碼,也可能會發生錯誤。

因此,建議您在建構最終的應用程式版本時,啟用這些編譯時間工作,再進行發布。如要啟用縮減、模糊處理和最佳化功能,請在專案層級建構指令碼中加入以下內容。

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `isDebuggable=false`.
            isMinifyEnabled = true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            isShrinkResources = true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    ...
}

Groovy

android {
    buildTypes {
        release {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `debuggable false`.
            minifyEnabled true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            shrinkResources true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}

R8 設定檔

R8 會使用 ProGuard 規則檔案修改預設行為,並進一步瞭解應用程式的結構,例如做為應用程式程式碼進入點的類別。雖然您可以修改部分規則檔案,但部分規則可能是由編譯時間工具 (例如 AAPT2) 或從應用程式的程式庫依附元件沿用而來。下表說明 R8 使用的 ProGuard 規則檔案來源。

來源 位置 說明
Android Studio <module-dir>/proguard-rules.pro 使用 Android Studio 建立新模組時,IDE 會在該模組的根目錄中建立 proguard-rules.pro 檔案。

根據預設,這個檔案不會套用任何規則。因此,請在此處加入自己的 ProGuard 規則,例如自訂保留規則

Android Gradle 外掛程式 在編譯期間由 Android Gradle 外掛程式產生。 Android Gradle 外掛程式會產生 proguard-android-optimize.txt,其中包含大多數 Android 專案都適用的規則,並啟用 @Keep* 註解

根據預設,使用 Android Studio 建立新模組時,模組層級的建構指令碼會在發布子版本中加入這項規則檔案。

注意:Android Gradle 外掛程式包含額外的預先定義的 ProGuard 規則檔案,但建議使用 proguard-android-optimize.txt

資料庫依附元件 AAR 程式庫:<library-dir>/proguard.txt

JAR 程式庫:<library-dir>/META-INF/proguard/

如果 AAR 程式庫是以其 ProGuard 規則檔案發布,且您將該 AAR 新增為編譯時間依附元件,R8 會在編譯您的專案時自動套用這些規則。

如果資料庫需要正常運作的特定保留規則 (也就是資料庫開發人員已執行疑難排解步驟),則使用以 AAR 程式庫封裝的規則檔案會非常實用。

不過請注意,由於 ProGuard 規則是累加性,因此系統無法移除內含 AAR 程式庫依附元件的特定規則,並可能會影響應用程式其他部分的編譯。舉例來說,如果程式庫包含停用程式碼最佳化的規則,該規則就會停用整個專案的最佳化功能。

Android 資產封裝工具 2 (AAPT2) 使用 minifyEnabled true 建立專案後:<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 會根據應用程式資訊清單、版面配置和其他應用程式資源中的類別,產生保留規則。例如,AAPT2 會為您在應用程式資訊清單中註冊的每個活動建立資料進入點。
自訂設定檔 根據預設,當您使用 Android Studio 建立新模組時,IDE 會建立 <module-dir>/proguard-rules.pro 以新增您自己的規則。 您可以新增其他設定,R8 會在編譯期間套用。

minifyEnabled 屬性設為 true 時,R8 會結合上述所有可用來源的規則。請注意使用 R8 進行疑難排解時,由於其他編譯時間依附元件 (例如程式庫依附元件) 可能導致您無法對 R8 行為進行任何變更。

如要輸出 R8 建構您的專案適用的所有規則,請在模組的 proguard-rules.pro 檔案中加入下列內容:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

新增其他設定

使用 Android Studio 建立新專案或模組時,IDE 會建立 <module-dir>/proguard-rules.pro 檔案,以納入自己的規則。您也可以加入其他檔案的其他規則,只要在模組的建構指令碼中將規則新增至 proguardFiles 屬性即可。

例如,您可以在對應的 productFlavor 區塊中加入另一個 proguardFiles 屬性,以新增特定於每個建構變化版本的規則。下列 Gradle 檔案會新增 flavor2-rules.proflavor2 變種版本。現在 flavor2 會使用三個 ProGuard 規則,因為系統會套用 release 區塊中的規則。

此外,您可以新增 testProguardFiles 屬性,指定只包含在測試 APK 中的 ProGuard 檔案清單:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                "proguard-rules.pro"
            )
            testProguardFiles(
                // The proguard files listed here are included in the
                // test APK only.
                "test-proguard-rules.pro"
            )
        }
    }
    flavorDimensions.add("version")
    productFlavors {
        create("flavor1") {
            ...
        }
        create("flavor2") {
            proguardFile("flavor2-rules.pro")
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
            testProguardFiles
                // The proguard files listed here are included in the
                // test APK only.
                'test-proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

縮減程式碼

在預設情況下,如果您將 minifyEnabled 屬性設為 true,即可啟用使用 R8 的程式碼縮減。

程式碼縮減 (也稱為搖樹) 是指移除執行階段不需要 R8 判定的程式碼的程序。例如,如果您的應用程式含有許多程式庫依附元件,但僅使用了一小部分功能,那麼這項程序可以大幅縮減應用程式的大小。

如要縮減應用程式的程式碼,R8 會先根據合併的設定檔組合判定應用程式程式碼中的所有進入點。這些進入點包括 Android 平台用於開啟應用程式活動或服務的所有類別。從每個進入點開始,R8 會檢查應用程式的程式碼,以建構應用程式在執行階段中可能存取的所有方法、成員變數和其他類別。未連接至該圖表的程式碼會被視為無法連上,且可能會從應用程式中移除。

圖 1 顯示執行階段程式庫依附元件的應用程式。在檢查應用程式的程式碼時,R8 判定可從 MainActivity.class 進入點連線至 foo()faz()bar() 方法。不過,應用程式從未在執行階段使用 OkayApi.class 類別或 baz() 方法,而且在縮減應用程式時,R8 則會移除該程式碼。

圖 1. 在編譯期間,R8 會根據專案的合併保留規則建構圖形,判斷無法存取的程式碼。

R8 會在專案的 R8 設定檔中,透過 -keep 規則決定進入點。換言之,保留規則指定 R8 縮減應用程式時不應捨棄的類別,且 R8 會將這些類別視為應用程式的可能進入點。Android Gradle 外掛程式和 AAPT2 會自動產生大多數應用程式專案所需的保留規則,例如應用程式的活動、檢視畫面和服務。不過,如果您需要用額外的保留規則來自訂此預設行為,請參閱自訂要保留的程式碼一節。

如果您只是想縮減應用程式資源的大小,請直接閱讀「如何縮減資源」一節。

自訂要保留的程式碼

在大多數情況下,預設的 ProGuard 規則檔案 (proguard-android- optimize.txt) 足以讓 R8 移除未使用的程式碼。不過,在某些情況下,R8 可能無法正確分析,而且可能會移除應用程式的實際程式碼。以下列舉一些錯誤移除程式碼的範例:

  • 應用程式透過 Java Native Interface (JNI) 呼叫方法
  • 應用程式在執行階段查詢程式碼 (例如使用反射)

測試應用程式應能找出因不當移除程式碼而引發的任何錯誤,但您也可以產生已移除的程式碼報表,檢查已移除的程式碼。

如要修正錯誤並強制 R8 保留特定程式碼,請在 ProGuard 規則檔案中加入 -keep 程式碼行。例如:

-keep public class MyClass

或者,您也可以在想保留的程式碼中加入 @Keep 註解。在類別中新增 @Keep 後,整個類別都會保持原樣;在方法或欄位中新增該類別時,方法/欄位 (及其名稱) 和類別名稱都會完整保留。請注意,只有使用 AndroidX 註解程式庫,且加入已隨附於 Android Gradle 外掛程式的 ProGuard 規則檔案時,您才能使用該註解,例如相關說明請參閱啟用縮減功能一節。

使用 -keep 選項時,您需要考量許多因素;如要進一步瞭解如何自訂規則檔案,請參閱 ProGuard 手冊疑難排解部分概略說明程式碼移除時可能會遇到的其他常見問題。

刪除原生資料庫

根據預設,系統會在應用程式的發布子版本中移除原生程式碼程式庫。這項移除作業包括移除符號表格,以及應用程式所用原生資料庫內含的偵錯資訊。去除原生程式碼程式庫會導致應用程式大幅減少,但由於缺少資訊 (例如類別和函式名稱),因此無法診斷 Google Play 管理中心的當機問題。

原生程式碼支援

Google Play 管理中心會報告 Android Vitals 下方的原生程式碼發生錯誤事件。只需執行幾個步驟,即可為應用程式產生並上傳原生偵錯符號檔案。這個檔案可在 Android Vitals 中啟用符號化的原生當機堆疊追蹤 (包括類別和函式名稱),協助您在實際工作環境中對應用程式進行偵錯。這些步驟取決於專案中使用的 Android Gradle 外掛程式版本,以及專案的建構輸出內容。

Android Gradle 外掛程式 4.1 以上版本

如果您在專案中建構 Android App Bundle,可以自動加入原生偵錯符號檔。如要在發布版本中加入這個檔案,請將以下內容新增至應用程式的 build.gradle.kts 檔案:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

請從下列清單中選取偵錯符號級別:

  • 使用 SYMBOL_TABLE 取得 Play 管理中心符號化堆疊追蹤中的函式名稱。這個級別支援「空值標記」
  • 使用 FULL 在 Play 管理中心的符號化堆疊追蹤中取得函式名稱、檔案和行數。

如果您在專案中建構的是 APK,請使用較早顯示的 build.gradle.kts 版本設定,分別產生原生偵錯符號檔案。手動將原生偵錯符號檔案上傳至 Google Play 管理中心。在建構程序中,Android Gradle 外掛程式會在下列專案位置輸出這個檔案:

app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip

Android Gradle 外掛程式 4.0 以下版本 (以及其他建構系統)

在建構程序中,Android Gradle 外掛程式會在專案目錄中保留未移除的程式庫副本。這個目錄結構與下列內容相似:

app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── arm64-v8a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── x86/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
└── x86_64/
    ├── libgameengine.so
    ├── libothercode.so
    └── libvideocodec.so
  1. 壓縮以下目錄的內容:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. 手動上傳 symbols.zip 檔案至 Google Play 管理中心。

縮減資源

資源縮減功能僅適用於程式碼縮減。程式碼縮減程式移除所有未使用的程式碼後,資源縮減器就能識別應用程式仍在使用的資源。如果您新增含有資源的程式碼庫,這個方法就更顯而易見。您必須移除未使用的程式庫程式碼,如此才能避免資源庫參照,進而由資源縮減工具移除。

如要啟用資源縮減功能,請將建構指令碼中的 shrinkResources 屬性設為 true (以及用於程式碼縮減的 minifyEnabled)。例如:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'
        }
    }
}

如果您尚未使用 minifyEnabled 建構應用程式來縮減程式碼,請先啟用 shrinkResources,因為您可能需要編輯 proguard-rules.pro 檔案,才能保留在開始移除資源之前動態建立或叫用的類別或方法。

自訂要保留的資源

如要保留或捨棄特定資源,請在專案中使用建立帶有 <resources> 標記的 XML 檔案,並在 tools:keep 屬性中指定要保留的每個資源,並在 tools:discard 屬性中指定要捨棄的每個資源。這兩個屬性都接受以半形逗號分隔的資源名稱清單。您可以使用星號字元做為萬用字元。

例如:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

請將這個檔案儲存在專案資源中,例如在 res/raw/keep.xml 中。建構作業不會將這個檔案封裝至應用程式中。

指定應捨棄的資源似乎毫無用處,因為您可以進行刪除,但這在建構變數中很實用。舉例來說,您可以將所有資源放入通用專案目錄中,然後為每個建構變數建立不同的 keep.xml 檔案,但如果您知道特定資源會用於程式碼中 (因此不會遭縮減器移除),但您知道該資源實際上不會用於指定的建構變數,請為每個建構變數建立不同的 keep.xml 檔案。也有可能是建構工具在需要時錯誤識別資源,原因可能是編譯器新增了內嵌資源 ID,因此資源分析器可能不知道實際參照的資源與程式碼中出現相同值之間的整數值之間的差異。

啟用嚴格參考資料檢查功能

一般而言,資源縮減器能夠準確判斷資源是否被使用。不過,如果您的程式碼會呼叫 Resources.getIdentifier() (或是您的任何程式庫,則使用 AppCompat 程式庫),代表您的程式碼是根據動態產生的字串查詢資源名稱。這樣做時,資源縮減器預設會穩定運作,並將所有具有相符名稱格式的資源標示為可能使用且無法移除。

例如,下列程式碼會讓所有具有 img_ 前置字串的資源標示為使用中。

Kotlin

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Java

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

資源縮減器也會檢查程式碼中的所有字串常數,以及各種 res/raw/ 資源,藉此尋找與 file:///android_res/drawable//ic_plus_anim_016.png 類似的格式網址。如果我們發現這類字串可用來建立這類網址,就不會將其移除。

以下為預設啟用的安全縮減模式。但是,您可以關閉這項「防患於未然」控制代碼,並且指定資源縮減器僅保留特定資源。方法是在 keep.xml 檔案中將 shrinkMode 設為 strict,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

如果您確實啟用嚴格縮減模式,且程式碼也參照了動態產生字串的資源 (如上所示),則必須使用 tools:keep 屬性手動保留這些資源。

移除未使用的替代資源

Gradle 資源縮減器只會移除應用程式程式碼參照的資源,因此不會移除不同裝置設定的 替代資源。如有需要,您可以使用 Android Gradle 外掛程式的 resConfigs 屬性,移除應用程式不需要的其他資源檔案。

例如,如果您使用的程式庫包含語言資源 (例如 AppCompat 或 Google Play 服務),則您的應用程式會包含這些程式庫中訊息的所有翻譯語言字串,無論您的應用程序的其餘部分是否被翻譯成相同的語言。如果您只想保留應用程式官方支援的語言,可以使用 resConfig 屬性指定。系統會移除任何未指定語言的資源。

下列程式碼片段說明如何將語言資源限制為英文和法文:

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

Groovy

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

使用 Android App Bundle 格式發布應用程式時,根據預設,只有應用程式安裝在使用者裝置上設定的語言。同樣地,只有與裝置的螢幕密度相符的資源,以及與裝置的 ABI 匹配的原生資料庫才包含在下載作業中。詳情請參閱 Android App Bundle 設定的相關說明。

如果舊版應用程式使用 APK 發布 (2021 年 8 月前建立),您可以透過建構多個 APK (每個 APK 針對不同的裝置設定),以自訂要包含在 APK 中的螢幕密度或 ABI 資源。

合併重複的資源

根據預設,Gradle 也會合併名稱相同的資源,例如可能位於不同資源資料夾中的同名可繪項目。這個行為不受 shrinkResources 屬性控管,而且無法停用,因為如果有多個資源符合您的程式碼查詢名稱,就必須避免發生錯誤。

只有在兩個以上的檔案共用相同的資源名稱、類型和限定詞時,系統才會合併資源。Gradle 會根據下方所述的優先順序,選擇要在重複項目中選取最合適的檔案,並且只將這項資源傳遞給 AAPT,以便在最終構件中發布。

Gradle 會在下列位置尋找重複的資源:

  • 與主要來源集相關聯的主要資源,通常位於 src/main/res/
  • 子類疊加層,來自建構類型和建構版本。
  • 程式庫專案依附元件。

Gradle 會根據下列優先順序合併重複的資源:

依附元件 → 主要 → 版本版本 → 建構類型

例如,如果主要資源和建構版本出現在重複的資源,Gradle 會選取建構版本中的資源。

如果相同的資源出現在相同的來源集中,Gradle 就無法合併資源,且會發出資源合併錯誤。如果在 build.gradle.kts 檔案的 sourceSet 屬性中定義了多個來源集,例如 src/main/res/src/main/res2/ 都含有相同的資源,就會發生這種情況。

模糊處理程式碼

模糊處理的目的是縮短應用程式類別、方法和欄位名稱,藉此縮減應用程式的大小。以下是使用 R8 進行模糊處理的範例:

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
    android.content.Context mContext -> a
    int mListItemLayout -> O
    int mViewSpacingRight -> l
    android.widget.Button mButtonNeutral -> w
    int mMultiChoiceItemLayout -> M
    boolean mShowTitle -> P
    int mViewSpacingLeft -> j
    int mButtonPanelSideLayout -> K

雖然模糊處理不會移除應用程式中的程式碼,但在具有索引的許多類別、方法和欄位的 DEX 檔案中可以看到顯著的大小節省。不過,由於程式碼會重新命名程式碼的不同部分,因此某些工作 (例如檢查堆疊追蹤) 需要額外工具。如要瞭解經過模糊處理後的堆疊追蹤,請參閱「如何解碼模糊化的堆疊追蹤」。

此外,如果您的程式碼必須使用可預測的應用程式方法和類別命名,例如使用反射時,請將這些簽名視為進入點,並為其指定保留規則,如「自訂要保留的程式碼」一節所述。這些保留規則會指示 R8 將程式碼保留在應用程式的最終 DEX 中,同時保留原本的命名。

解碼模糊化的堆疊追蹤

在 R8 模糊處理程式碼後,由於類別和方法的名稱可能已變更,因此瞭解堆疊追蹤並不容易。如要取得原始堆疊追蹤,請反向追蹤堆疊追蹤

程式碼最佳化

為了進一步縮減應用程式大小,R8 會更深入地檢查程式碼,移除未使用的程式碼,或盡可能重新編寫程式碼,讓內容變得較不複雜。以下是這類最佳化的幾個範例:

  • 如果您的程式碼從未取得特定 if/else 陳述式的 else {} 分支版本,R8 可能會移除 else {} 分支版本的程式碼。
  • 如果程式碼只在一個位置呼叫方法,R8 可能會移除此方法,並內嵌於單一呼叫網站上。
  • 如果 R8 判定某個類別只有一個專屬子類別,而該類別本身並未執行個體化 (例如,僅由單一具體實作類別使用的抽象基本類別),則 R8 可以結合這兩個類別,並從應用程式中移除一個類別。
  • 詳情請參閱 Jake Wharton 撰寫的 R8 最佳化網誌文章

R8 不允許停用或啟用離散最佳化,或是修改最佳化的行為。事實上,R8 會忽略任何嘗試修改預設最佳化項目的 ProGuard 規則,例如 -optimizations- optimizationpasses。此限制非常重要,因為 R8 會持續改善,因此維護標準的標準行為有助 Android 團隊輕鬆排解問題,並解決您可能遇到的問題。

請注意,啟用最佳化會變更應用程式的堆疊追蹤。舉例來說,內嵌會移除堆疊框架。如要瞭解如何取得原始堆疊追蹤,請參閱「反向追蹤」。

啟用更積極的最佳化功能

R8 包含一組額外的最佳化作業 (稱為「完整模式」),讓兩者的行為與 ProGuard 不同。自 Android Gradle 外掛程式 8.0.0 版起,系統會預設啟用這些最佳化功能。

您可以在專案的 gradle.properties 檔案中加入以下內容,即可停用這些額外最佳化功能:

android.enableR8.fullMode=false

由於額外的最佳化作業讓 R8 與 ProGuard 的運作方式不同,因此如果您使用了專為 ProGuard 設計的規則,可能需要加入額外的 ProGuard 規則,以避免執行階段問題。例如,假設您的程式碼透過 Java Reflection API 參照類別。不使用「完整模式」時,R8 會假設您打算在執行階段檢查及操控該類別的物件 (即使程式碼實際上不存在),並自動保留類別及其靜態初始化器。

不過,使用「完整模式」時,R8 不會假設這個假設,如果 R8 宣告程式碼絕不會在執行階段使用類別,就會從應用程式的最終 DEX 中移除類別。也就是說,如要保留類別及其靜態初始化器,您必須在規則檔案中加入 Keep 規則,以完成此動作。

如果您在使用 R8 的「完整模式」時遇到任何問題,請參閱「R8 常見問題頁面」中的可能解決方案。如果無法解決問題,請報告錯誤

反向堆疊追蹤

R8 處理的程式碼會以各種方式變更,讓堆疊追蹤更難理解,因為堆疊追蹤不會明確對應至原始碼。例如,如果沒有保留偵錯資訊,就可能會出現行數變更的情況。其中可能的原因包括內嵌和大綱製作最佳化。經常會出現這種情況的原因是模糊處理,而這是因為就連類別和方法的名稱都會變更。

如要復原原始堆疊追蹤,R8 提供反向追蹤指令列工具,此工具包含在指令列工具套件中。

為了支援應用程式堆疊追蹤的反向追蹤功能,建議您在模組的 proguard-rules.pro 檔案中加入下列規則,確保版本保留足夠的資訊以反向追蹤:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

LineNumberTable 屬性會保留方法中的位置資訊,讓這些位置顯示在堆疊追蹤中。SourceFile 屬性可確保所有潛在的執行階段都能實際列印位置資訊。-renamesourcefileattribute 指令會將堆疊追蹤中的來源檔案名稱設為僅限 SourceFile。進行反向追蹤時並不需要實際的原始來源檔案名稱,因為對應檔案包含原始來源檔案。

R8 每次執行時都會建立一個 mapping.txt 檔案,此檔案包含反向對應堆疊追蹤至原始堆疊追蹤所需的資訊。Android Studio 會將檔案儲存在 <module-name>/build/outputs/mapping/<build-type>/ 目錄中。

在 Google Play 發布應用程式時,您可以上傳各個應用程式版本的 mapping.txt 檔案。使用 Android App Bundle 發布時,這個檔案會自動納入應用程式套件內容中。然後 Google Play 會反向追蹤收到的使用者回報問題堆疊追蹤,讓您可以在 Play 管理中心內查看。詳情請參閱如何針對當機時的堆疊追蹤去模糊化相關說明中心文章。

使用 R8 進行疑難排解

本節說明使用 R8 啟用縮減、模糊處理和最佳化時,疑難排解的一些策略。如果下方未列出您的問題,另請參閱 R8 常見問題頁面ProGuard 的疑難排解指南

產生已移除 (或保留) 程式碼的報表

為協助您排解某些 R8 問題,建議您查看 R8 從應用程式中移除的所有程式碼。請分別為要產生這份報表的每個模組,將 -printusage <output-dir>/usage.txt 加入自訂規則檔案中。當您啟用 R8 並建構應用程式時,R8 會使用您指定的路徑和檔案名稱輸出報表。程式碼移除後,看起來會像這樣:

androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
    public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
    public boolean hasWindowFeature(int)
    public void setHandleNativeActionModesEnabled(boolean)
    android.view.ViewGroup getSubDecor()
    public void setLocalNightMode(int)
    final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
    public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
    private static final boolean DEBUG
    private static final java.lang.String KEY_LOCAL_NIGHT_MODE
    static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...

但如果您想查看 R8 根據專案的保留規則決定的進入點報表,請在自訂規則檔案中加入 -printseeds <output-dir>/seeds.txt。當您啟用 R8 並建構應用程式時,R8 會使用您指定的路徑和檔案名稱輸出報表。保留進入點的報表看起來會像這樣:

com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...

資源縮減疑難排解

縮減資源時,「Build」 視窗會顯示從應用程式移除的資源摘要 (您必須先按一下視窗左側的「Toggle view」,即可顯示 Gradle 提供的詳細文字輸出內容)。例如:

:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle 也會在 <module-name>/build/outputs/mapping/release/ 中建立名為 resources.txt 的診斷檔案 (與 ProGuard 的輸出檔案位於同一個資料夾)。這個檔案內含哪些資源會參照其他資源,以及如何運用或移除資源。

例如,如要找出 @drawable/ic_plus_anim_016 仍在應用程式中的原因,請開啟 resources.txt 檔案並搜尋檔案名稱。您可能會發現,它從其他資源中進行了參照,如下所示:

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016

您現在需要知道 @drawable/add_schedule_fab_icon_anim 可以存取的原因,如果您向上搜尋,就會看到資源列於「The root reachable resources are:」下方。這表示 add_schedule_fab_icon_anim 有程式碼參照 (也就是說,可在可連接的程式碼中找到其 R.drawable ID)。

如果您並未使用嚴格檢查,若有字串常數可用來建立動態載入資源的資源名稱常數,就可以將資源 ID 標示為可連項目。在這種情況下,如果您搜尋資源名稱的建構輸出內容,可能會看到如下的訊息:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because it format-string matches string pool constant ic_plus_anim_%1$d.

如果您看到其中一個字串,而且確定該字串並未用於動態載入指定資源,您可以使用 tools:discard 屬性通知建構系統將其移除,如「自訂要保留的資源」一節所述。