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

為了盡量縮小應用程式的大小並加快速度,您應使用 isMinifyEnabled = true 最佳化及縮減發布子版本。

這樣做可啟用縮減功能,移除未使用的程式碼;啟用模糊處理功能,縮短應用程式類別和成員的名稱;以及啟用最佳化功能,套用改良的程式碼最佳化策略,進一步縮減應用程式大小並提升效能。本頁面說明 R8 如何為專案執行這些編譯時間工作,以及如何自訂這些工作。

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

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

建構應用程式的發布版本時,您可以設定 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

            proguardFiles(
                // 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.
                getDefaultProguardFile("proguard-android-optimize.txt"),

                // Includes a local, custom Proguard rules file
                "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 程式庫中:
proguard.txt

在 JAR 程式庫中:
META-INF/proguard/<ProGuard-rules-file>

除了這些位置之外,Android Gradle 外掛程式 3.6 以上版本也支援指定縮減規則

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

除了傳統的 ProGuard 規則外,Android Gradle 外掛程式 3.6 以上版本也支援指定縮減規則。這些規則會指定特定縮減器 (R8 或 ProGuard) 和特定縮減器版本。

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

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

Android 資產封裝工具 2 (AAPT2) 使用 minifyEnabled true 建立專案後:<module-dir>/build/intermediates/aapt_proguard_file/.../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 Gradle 外掛程式 3.6 以上版本支援指定縮減器 (R8 或 ProGuard) 和特定縮減器版本的程式庫規則。這可讓程式庫開發人員調整規則,以便在使用新壓縮器版本的專案中發揮最佳效能,同時讓現有規則繼續用於採用舊版壓縮器的專案。

如要指定目標縮減規則,程式庫開發人員必須在 AAR 或 JAR 程式庫的特定位置加入這些規則,如下所述。

In an AAR library:
    proguard.txt (legacy location)
    classes.jar
    └── META-INF
        └── com.android.tools (targeted shrink rules location)
            ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
            └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rules-file> (legacy location)
    └── com.android.tools (targeted shrink rules location)
        ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
        └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

這表示指定的縮減規則會儲存在 JAR 的 META-INF/com.android.tools 目錄中,或是 AAR 的 classes.jarMETA-INF/com.android.tools 目錄中。

在該目錄下,可以有多個以 r8-from-<X>-upto-<Y>proguard-from-<X>-upto-<Y> 格式命名的目錄,用於指出目錄內規則的編寫對象。請注意,-from-<X>-upto-<Y> 部分為選用,<Y> 版本為排他性,且版本範圍必須連續。

例如,r8-upto-8.0.0r8-from-8.0.0-upto-8.2.0r8-from-8.2.0 可形成有效的指定縮減規則組合。R8 會使用 r8-from-8.0.0-upto-8.2.0 目錄下的規則,從 8.0.0 版起至 8.2.0 版 (不包括 8.2.0 版)。

有了這些資訊,Android Gradle 外掛程式 3.6 以上版本就會從相符的 R8 目錄中選取規則。如果程式庫未指定指定的縮減規則,Android Gradle 外掛程式會從舊版位置 (AAR 為 proguard.txt,JAR 為 META-INF/proguard/<ProGuard-rules-file>) 選取規則。

程式庫開發人員可以選擇在程式庫中加入指定縮減規則或舊版 ProGuard 規則,如果想維持與 Android Gradle 外掛程式 (版本低於 3.6) 或其他工具的相容性,則可以選擇兩者皆加入。

新增其他設定

使用 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 會自動產生大多數應用程式專案所需的保留規則,例如應用程式的活動、檢視畫面和服務。不過,如果您需要用額外的保留規則來自訂此預設行為,請參閱自訂要保留的程式碼一節。

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

請注意,如果程式庫專案縮減,則依附該程式庫的應用程式會包含縮減的程式庫類別。如果程式庫 APK 中缺少類別,您可能需要調整程式庫保留規則。如果您以 AAR 格式建構及發布程式庫,程式庫所依附的本機 JAR 檔案不會在 AAR 檔案中縮減。

自訂要保留的程式碼

在大多數情況下,預設的 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/my.package.keep.xml 中。建構作業不會將這個檔案封裝至應用程式中。

注意:請務必為 keep 檔案使用不重複的名稱。當不同程式庫連結在一起時,其保留規則會發生衝突,導致忽略規則或保留不必要的資源的潛在問題。

指定應捨棄的資源似乎毫無用處,因為您可以進行刪除,但這在建構變化版本中很實用。舉例來說,如果您知道特定資源似乎會用於程式碼 (因此不會遭到縮減器移除),但您知道該資源實際上不會用於特定建構變數,則可以將所有資源放入通用專案目錄,然後為每個建構變數建立不同的 my.package.build.variant.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 將可改善程式碼的執行階段效能 (包括 UI 執行緒的啟動和影格時間),最多可提升 30%。停用任何一項功能,都會大幅限制 R8 使用的最佳化組合。

如果已啟用 R8,您也應建立啟動設定檔,進一步提升啟動效能。

啟用改善的最佳化功能

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: 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] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     @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 屬性通知建構系統將其移除,如「自訂要保留的資源」一節所述。