針對程式庫作者進行最佳化

身為程式庫作者,您必須確保應用程式開發人員能輕鬆將程式庫併入應用程式,同時維持高品質的終端使用者體驗。也就是說,程式庫必須與 Android 最佳化功能 (R8) 相容,不需要開發人員額外設定,或註明程式庫可能不適合在 Android 上使用。請務必確保適用於 Android 的程式庫不會妨礙重要的應用程式最佳化作業,並遵守其他最佳化規定

這份文件適用於已發布程式庫的開發人員,但對於大型模組化應用程式的內部程式庫模組開發人員來說,也可能很有幫助。

如果您是應用程式開發人員,想瞭解如何最佳化 Android 應用程式,請參閱「啟用應用程式最佳化功能」。如要瞭解適合使用的程式庫,請參閱「明智地選擇程式庫」。

瞭解保留規則類型

程式庫的保留規則分為兩種:

  • 消費者保留規則必須指定規則,保留程式庫反射的任何內容。如果程式庫使用反射或 JNI 呼叫程式碼,或是用戶端應用程式定義的程式碼,這些規則必須說明需要保留的程式碼。程式庫應封裝消費者保留規則,這些規則與應用程式保留規則的格式相同。這些規則會與程式庫構件 (AAR 或 JAR) 綁在一起,並在您使用程式庫時,於 Android 應用程式最佳化期間自動套用。這些規則會保留在 build.gradle.kts (或 build.gradle) 檔案中,以 consumerProguardFiles 屬性指定的檔案中。詳情請參閱「撰寫消費者保留規則」。
  • 程式庫建構時會套用程式庫建構保留規則。如果您決定在建構期間部分最佳化程式庫,才需要這些檔案。他們必須確保程式庫的公用 API 不會遭到移除,否則程式庫發布時不會包含公用 API,應用程式開發人員就無法使用該程式庫。這些規則會保留在 build.gradle.kts (或 build.gradle) 檔案中,以 proguardFiles 屬性指定的檔案中。詳情請參閱「最佳化 AAR 程式庫建構作業」。

最佳化規定和指南

程式庫中的 R8 設定會對使用應用程式的最終二進位檔大小和效能造成全域影響。除了保留規則最佳做法外,程式庫作者也必須遵守特定規定,並考量其他指南。

遵守最佳化要求

程式庫效率不彰是造成應用程式膨脹、浪費記憶體、啟動緩慢和 ANR (應用程式無回應錯誤) 的主要原因。程式庫必須避免違反下列規定,以免大幅降低應用程式品質和使用者體驗。

  • 沒有廣泛或套件範圍的保留規則:程式庫不得包含廣泛的保留規則,保留程式庫或另一個程式庫中的大部分程式碼。廣泛保留規則或許能在短期內解決當機問題,但會導致使用您程式庫的所有應用程式大小膨脹。

    請勿為程式庫或其他參照程式庫中的套件加入套件範圍的保留規則 (例如 -keep class com.mylibrary.** {*; })。這類規則會限制所有使用程式庫的應用程式,對這些套件進行最佳化。

  • 請勿使用不當的通用規則:請勿使用 global 選項,例如 -dontobfuscate-allowaccessmodification

  • 盡可能使用程式碼生成而非反射:請盡可能使用程式碼生成 (codegen),而非反射。程式碼生成和反射都是避免設計程式時使用樣板程式碼的常見方法,但程式碼生成與 R8 等應用程式最佳化工具的相容性較高。

    codegen 會在建構程序期間分析及修改程式碼。 由於編譯時間後不會有重大修改,最佳化工具知道最終需要哪些程式碼,以及哪些程式碼可以安全移除。

    使用反射時,系統會在執行階段分析及操控程式碼。因為程式碼要到執行時才會真正完成,最佳化工具無法判斷哪些程式碼可以安全移除。這可能會移除在執行階段透過反射動態使用的程式碼,導致使用者應用程式當機。

    許多現代程式庫都使用程式碼生成,而非反射。如需常見的進入點,請參閱 KSPRoomDagger2 和許多其他項目都會使用這個進入點。

  • 支援 R8 完整模式:啟用 R8 完整模式時,程式庫不應當機。建議使用 R8 的完整模式,且自 2023 年發布的 AGP 8.0 穩定版起,完整模式即為預設模式。如果程式庫在 R8 下當機,解決方法是找出特定的反射或 JNI 進入點,並新增目標規則,而不是保留整個套件。

其他建議

除了最佳化規定外,我們也建議您採取下列做法。

  • 請勿在程式庫的消費者保留規則檔案中使用 -repackageclasses。不過,如要最佳化程式庫建構作業,您可以在程式庫的建構保留規則檔案中,使用 -repackageclasses 和內部套件名稱 (例如 <your.library.package>.internal)。這有助於提升未經最佳化應用程式的程式庫效率。不過,一般來說不需要這麼做,因為應用程式也應經過最佳化。
  • 即使可能與 proguard-android-optimize.txt 中定義的屬性重疊,您仍須在程式庫的保留規則檔案中,宣告程式庫運作所需的任何屬性。
  • 如果您需要在程式庫發布作業中加入下列屬性,請在程式庫的建構保留規則檔案中維護這些屬性,不要在程式庫的消費者保留規則檔案中維護:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • 如果在執行階段使用註解,程式庫作者應在消費者保留規則中保留 RuntimeVisibleAnnotations 屬性。
  • 程式庫作者不應在消費者保留規則中使用下列全域選項:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

適合使用反射的情況

如果必須使用反射,則只能反射到下列任一項目:

  • 特定目標類型 (特定介面實作工具或子類別)
  • 使用特定執行階段註解編寫程式碼

以這種方式使用反射可限制執行階段成本,並編寫目標消費者保留規則

這種特定且有目標的反射是您可以在 Android 框架 (例如,在加載活動、檢視畫面和可繪項目時) 和 AndroidX 程式庫 (例如,在建構 WorkManager ListenableWorkersRoomDatabases 時) 中看到的模式。相較之下,Gson 的開放式反射不適合在 Android 應用程式中使用

常見的誤解

有幾項常見的誤解可能會導致您設定 R8 時發生錯誤。包括:

  • 對 R8 的最佳化功能有誤解:一般認為 R8 的最佳化功能僅限於模糊處理,但其實也包括程式碼縮減和邏輯最佳化,以及方法內嵌和類別合併技術。詳情請參閱 R8 最佳化總覽

  • 略過混淆程式庫的最佳化程序:常見的錯誤是省略程式庫的最佳化程序,因為程式庫在編譯為 AAR (Android ARchive) 或 JAR (Java ARchive) 時已最佳化或混淆處理。程式庫建構期間的最佳化作業有限,應用程式不應將程式庫納入保留規則,藉此停用程式庫最佳化作業。詳情請參閱「最佳化 AAR 程式庫建構作業」。

  • -keep 選項的理解有誤-keep 規則會禁止 R8 執行任何最佳化階段。詳情請參閱「選擇合適的保留選項」。

設定規則封裝

為確保系統正確套用消費者保留規則,您必須根據程式庫格式適當封裝規則。

AAR 程式庫

如要為 AAR 程式庫新增消費者規則,請在 Android 程式庫模組的建構指令碼中使用 consumerProguardFiles 選項。詳情請參閱建立程式庫模組的指南

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

JAR 程式庫

如要將規則與以 JAR 形式發布的 Kotlin 或 Java 程式庫組合在一起,請將規則檔案放在最終 JAR 的 META-INF/proguard/ 目錄中,檔名可自行命名。舉例來說,如果您的程式碼位於 <libraryroot>/src/main/kotlin,請將消費者規則檔案放在 <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro,規則就會在輸出 JAR 中正確的位置進行組合。

請檢查規則是否位於 META-INF/proguard 目錄,確認最終 JAR 組合規則正確無誤。

最佳化 AAR 程式庫建構作業 (進階)

一般來說,您不需要直接最佳化程式庫建構作業,因為程式庫建構時可進行的優化非常有限。身為程式庫開發人員,您需要考量多個最佳化階段,並在最佳化程式庫之前,保留程式庫和應用程式建構時間的行為。

如果您仍想在建構時最佳化程式庫,Android Gradle 外掛程式支援這項做法。

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

請注意,proguardFiles 的行為與 consumerProguardFiles 大不相同:

  • proguardFiles 在建構期間使用,通常會與 getDefaultProguardFile("proguard-android-optimize.txt") 搭配,定義程式庫建構時應保留的程式庫部分。至少要提供公開 API。
  • consumerProguardFiles 則會封裝到程式庫中,用來決定使用該程式庫的應用程式建置時需要執行哪些優化。

舉例來說,如果程式庫使用反射建構內部類別,您可能需要在 proguardFilesconsumerProguardFiles 中定義保留規則。

如果您在程式庫的建構作業中使用 -repackageclasses,請將類別重新封裝至程式庫套件的子套件。例如,使用 -repackageclasses 'com.example.mylibrary.internal' 而不是 -repackageclasses 'internal'

支援不同的 R8 版本 (進階)

您可以自訂規則,指定 R8 的特定版本。這樣一來,您的程式庫就能在採用新版 R8 的專案中發揮最佳效用,同時允許在採用舊版 R8 的專案中繼續使用現有規則。

如要指定目標 R8 規則,您必須將這些規則納入 AAR 的 classes.jarMETA-INF/com.android.tools 目錄,或是 JAR 的 META-INF/com.android.tools 目錄。

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

META-INF/com.android.tools 目錄中,可以有多個子目錄,名稱格式為 r8-from-<X>-upto-<Y>,用來指出規則適用的 R8 版本。每個子目錄可有一或多個包含 R8 規則的檔案,檔案名稱和副檔名不限。

請注意,-from-<X>-upto-<Y> 部分為選用,<Y> 版本為專屬,版本範圍通常是連續的,但也可以重疊。

舉例來說,r8r8-upto-8.0.0r8-from-8.0.0-upto-8.2.0r8-from-8.2.0 是目錄名稱,代表一組目標 R8 規則。任何 R8 版本都可以使用 r8 目錄下的規則。R8 可使用 8.0.0 版到 8.2.0 版 (不含) r8-from-8.0.0-upto-8.2.0 目錄下的規則。

Android Gradle 外掛程式會使用這項資訊,選取目前 R8 版本可使用的所有規則。如果程式庫未指定目標 R8 規則,Android Gradle 外掛程式會從舊版位置選取規則 (AAR 為 proguard.txt,JAR 為 META-INF/proguard/<ProGuard-rule-files>)。