Dexpreopt 和 <uses-library> 檢查

Android 12 的建構系統已針對具有 <uses-library> 依附元件的 Java 模組,對 DEX 檔案的 AOT 編譯 (dexpreopt) 進行變更。在某些情況下,這些建構系統變更可能會導致建構作業中斷。請使用這個頁面來準備修正方式,並按照這個頁面上的食譜修正及減輕問題。

Dexpreopt 是 Java 程式庫和應用程式的提前編譯程序。DEX 會在建構期間在主機上發生 (相對於 dexopt), 是在裝置端進行)。Java 使用的共用程式庫依附元件結構 模組 (程式庫或應用程式) 稱為其類別載入器結構定義 (CLC)。目的地: 確保 DRE 、建構時間和執行階段 CLC 的正確性 。建構時間 CLC 是 dex2oat 編譯器在 dexpreopt 時間點使用的內容 (會記錄在 ODEX 檔案中),而執行時間 CLC 則是裝置上載入預先編譯程式碼的內容。

基於正確性和效能考量,這些建構時間和執行階段 CLC 必須一致。為正確起見,需要處理重複的類別。如果執行階段的共用程式庫依附元件與用於編譯的依附元件不同,部分類別可能會以不同的方式解析,導致細微的執行階段錯誤。執行階段檢查重複的類別也會影響效能。

受影響的用途

首次啟動是受這些變更影響的主要用途:如果 ART 偵測到建構時間和執行時間 CLC 不相符,就會拒絕 dexpreopt 構件,改為執行 dexopt。這對後續的開機來說是個不錯的做法,因為 應用程式可在背景執行 dex 處理,並儲存在磁碟上。

Android 受影響的部分

這會影響所有 Java 應用程式和程式庫,這些應用程式和程式庫會對其他 Java 程式庫產生執行階段依附元件。Android 提供數千款應用程式,以及數百款使用的應用程式 共用程式庫合作夥伴也會受到影響,因為合作夥伴自有 程式庫和應用程式

破壞性變更

建構系統必須先瞭解 <uses-library> 依附元件,才能產生 dexpreopt 建構規則。不過,由於產生建構規則時 (基於效能考量),建構系統不得讀取任意檔案,因此無法直接存取資訊清單並讀取其中的 <uses-library> 標記。此外,資訊清單 封裝於 APK 或預先建構因此,建構檔案 (Android.bpAndroid.mk) 中必須包含 <uses-library> 資訊。

先前 ART 使用略過共用程式庫依附元件的解決方法 (已知 做為 &-classpath)。這不安全,而且造成一些細微錯誤 已於 Android 12 中移除

因此,如果 Java 模組未在建構檔案中提供正確的 <uses-library> 資訊,就可能導致建構中斷 (建構期間 CLC 不相符) 或首次啟動時間回歸 (啟動期間 CLC 不相符,接著進行 dexopt)。

遷移路徑

請按照下列步驟修正故障的版本:

  1. 透過設定

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    。此操作可修正建構錯誤 (特殊情況除外) 列在「修正服務中斷」一節中)。不過 這是暫時性的解決方法,而且可能會導致啟動時間 CLC 不符 後面是 dexopt。

  2. 修正在全域停用建構時間檢查前失敗的模組 新增必要的 <uses-library> 資訊 (詳情請參閱修正中斷情形)。 對大部分的模組而言,這需要在 Android.bpAndroid.mk

  3. 針對有問題的情況,針對個別模組停用建構時間檢查和 dexpreopt。停用 dexpreopt,省去建構時間的麻煩, 針對在開機時遭拒的構件儲存儲存空間

  4. 取消設定,全面重新啟用建構時間檢查 在步驟 1 中設定的 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES;建構 之後就不會失敗 (因為步驟 2 和 3)。

  5. 逐一修正您在步驟 3 中停用的模組,然後重新啟用 dexpreopt 和 <uses-library> 檢查。視需要回報錯誤。

系統會在 Android 12 中強制執行建構時間 <uses-library> 檢查。

修正服務中斷情形

以下各節將說明如何修正特定類型的毀損情形。

建構錯誤:CLC 不符

建構系統會針對 YAML 檔案中的資訊 Android.bpAndroid.mk 檔案以及資訊清單。建構系統無法讀取資訊清單,但可以產生建構規則來讀取資訊清單 (必要時從 APK 中擷取資訊清單),並比較資訊清單中的 <uses-library> 標記與建構檔案中的 <uses-library> 資訊。如果檢查失敗 錯誤訊息如下:

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

錯誤訊息指出,根據 急迫性:

  • 如要暫時修正所有產品,請設定 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true。系統仍會執行建構時間一致性檢查,但檢查失敗並不代表 建構作業失敗相反地,檢查失敗會導致建構系統將 dex2oat 編譯器篩選器降級為 dexpreopt 中的 verify,這會完全停用此模組的 AOT 編譯。
  • 如要快速修正全域指令列問題,請使用環境變數 RELAX_USES_LIBRARY_CHECK=true。這與 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES 的效果相同,但可用於指令列。 環境變數會覆寫產品變數。
  • 如要找出錯誤的根本原因並修正,請讓建構系統瞭解資訊清單中的 <uses-library> 標記。檢查錯誤訊息 顯示問題所在的程式庫 AndroidManifest.xml 或可檢查 APK 中的資訊清單 與 `aapt dump badging $APK | grep uses-library`)。

Android.bp 模組:

  1. 在模組的 libs 屬性中尋找缺少的程式庫。如果是 Soong 通常會自動新增這類程式庫 特殊情況:

    • 程式庫不是 SDK 程式庫 (定義為 java_library,而非 java_sdk_library)。
    • 程式庫的程式庫名稱 (在資訊清單中) 與模組名稱 (在建構系統中) 不同。

    如要暫時修正這個問題,請在 Android.bp 程式庫定義中新增 provides_uses_lib: "<library-name>"。如要長期解決問題,請修正基礎問題:將程式庫轉換為 SDK 程式庫,或重新命名其模組。

  2. 如果上一步未提供解析度,請新增 必要程式庫的 uses_libs: ["<library-module-name>"], 或使用 optional_uses_libs: ["<library-module-name>"],以 模組的 Android.bp 定義。這些屬性會接受模組名稱清單。清單中程式庫的相對順序必須與資訊清單中的順序相同。

Android.mk 模組:

  1. 檢查程式庫是否與資訊清單中的程式庫名稱不同 (在資訊清單中) 模組名稱 (位於建構系統中)。如果是這樣,請在程式庫的 Android.mk 檔案中新增 LOCAL_PROVIDES_USES_LIBRARY := <library-name>,或在程式庫的 Android.bp 檔案中新增 provides_uses_lib: "<library-name>",以便暫時修正這個問題 (由於 Android.mk 模組可能會依附 Android.bp 程式庫,因此兩種情況都有可能)。如果是長期的解決方案,請修正 基礎問題:重新命名程式庫模組

  2. 新增 LOCAL_USES_LIBRARIES := <library-module-name> 以用於必要程式庫;將 LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> 新增至模組的 Android.mk 定義,用於選用程式庫。這些 屬性接受模組名稱清單。各家程式庫中,程式庫的相對順序 清單必須與資訊清單中的名稱相同

建構錯誤:不明的程式庫路徑

如果建構系統找不到 <uses-library> DEX jar 的路徑 (主機上的建構時間路徑或裝置上的安裝路徑),通常會導致建構失敗。如果找不到路徑,可能表示程式庫是以某種非預期的方式進行設定。停用有問題的重建功能,暫時修正建構作業 後續課程我們將逐一介紹 預先訓練的 API、AutoML 和自訂訓練

Android.bp (模組屬性):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (模組變數):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

請回報錯誤,調查所有不受支援的情境。

建構錯誤:缺少程式庫依附元件

嘗試從模組 Y 的資訊清單將 <uses-library> X 新增至建構作業 Y 檔可能會因為缺少依附元件 X 而導致建構錯誤。

以下是 Android.bp 模組的錯誤訊息範例:

"Y" depends on undefined module "X"

以下是 Android.mk 模組的錯誤訊息範例:

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

這類錯誤的常見原因是,程式庫的名稱與建構系統中對應的模組名稱不同。舉例來說 <uses-library> 項目為 com.android.X,但程式庫模組的名稱為 只有 X,就會造成錯誤。如要解決這個問題,請向建構系統告知 名為 X 的模組提供名為 com.android.X<uses-library>

以下是 Android.bp 程式庫 (模組屬性) 的範例:

provides_uses_lib: “com.android.X”,

以下是 Android.mk 程式庫 (模組變數) 的範例:

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

開機時 CLC 不相符

初次啟動時,搜尋 logcat 以搜尋與 CLC 不相符相關的訊息,如下所示:

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

輸出結果可能包含下列格式的訊息:

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

如果收到 CLC 不符的警告,請找出錯誤的 dexopt 指令, 後續課程我們將逐一介紹 預先訓練的 API、AutoML 和自訂訓練如要修正這個問題,請確認模組的建構時間檢查通過。如果這樣做無法解決問題,表示您的情況可能是建構系統不支援的特殊情況 (例如應用程式會載入其他 APK,而非程式庫)。建構系統無法處理所有情況,因為在建構期間,無法確定應用程式在執行階段載入的內容。

類別載入器結構定義

CLC 是描述類別載入器階層的樹狀結構。建構系統會以狹義方式使用 CLC (僅涵蓋程式庫,不涵蓋 APK 或自訂類別載入器):這是一個程式庫樹狀結構,代表程式庫或應用程式的所有 <uses-library> 依附元件的間接閉包。CLC 的頂層元素是資訊清單 (classpath) 中指定的直接 <uses-library> 依附元件。CLC 樹狀結構的每個節點都是 <uses-library> 節點,可能會有自己的 <uses-library> 子節點。

因為 <uses-library> 依附元件是有向非循環圖,而非 CLC 應該是樹狀結構,所以同一個程式庫可以包含多個子樹狀結構。換句話說,CLC 是依附元件圖表「展開」為樹狀結構。重複內容 只在邏輯層級實際的基礎類別載入器並非 重複 (在執行階段,每個程式庫都有一個類別載入器執行個體)。

CLC 會定義在解析所用 Java 類別時,程式庫的查詢順序 程式庫或應用程式程式庫可包含 多個重複類別,而類別會解析為第一個相符項目。

裝置端 (執行階段) CLC

PackageManager (在 frameworks/base 中) 會建立 CLC,以載入 Java 模組 應用程式。它會將模組資訊清單中 <uses-library> 標記中列出的程式庫新增為頂層 CLC 元素。

對於每個使用的程式庫,PackageManager 會取得所有 <uses-library> 依附元件 (在該程式庫的資訊清單中指定為標記),並為每個依附元件新增巢狀 CLC。這個程序會持續遞迴,直到建構的 CLC 樹狀結構的所有葉節點都是沒有 <uses-library> 依附元件的程式庫為止。

PackageManager 只知道共用程式庫。這裡的「共用」定義與一般意義不同 (例如共用與靜態)。在 Android 中 Java 共用資料庫是已安裝的 XML 設定中所列的程式庫 在裝置上 (/system/etc/permissions/platform.xml)。每個項目都包含 共用程式庫的 DEX jar 檔案路徑,以及依附元件清單 (此程式庫在執行階段使用的其他共用程式庫,且會在 <uses-library> 標記)。

換句話說,有兩個資訊來源可讓 PackageManager 在執行階段建構 CLC:資訊清單中的 <uses-library> 標記,以及 XML 設定中的共用程式庫依附元件。

託管 (建構時間) CLC

除了載入程式庫或應用程式時需要 CLC,編譯時也需要 CLC。編譯作業可能發生在裝置端 (進行脫去) 或執行 建構 (重建)由於 dexopt 是在裝置上執行,因此與 PackageManager 相同 (資訊清單和共用程式庫依附元件)。不過,Dexpreopt 在主機上運作,與過去完全不同 而且還必須從建構系統取得相同的資訊。

因此,deXpreopt 使用的建構時間 CLC 和 PackageManager 使用的執行時間 CLC 是相同的,但計算方式不同。

建構時間和執行階段 CLC 必須一致,否則為 AOT 編譯的程式碼 就會遭到拒絕。如要檢查建構時間的相等性 執行階段 CLC,Dex2oat 編譯器會在 *.odex 檔案中記錄建構時間 CLC (位於 OAT 檔案標頭的 classpath 欄位中)。如要找出儲存的 CLC,請使用 執行下列指令:

oatdump --oat-file=<FILE> | grep '^classpath = '

在啟動期間,logcat 會回報建構時間和執行時間 CLC 不相符。搜尋 執行下列指令:

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

不相符會影響效能,因為這會迫使程式庫或應用程式進行 dexopt,或在沒有最佳化情況下執行 (例如,應用程式的程式碼可能需要從 APK 中擷取記憶體,這是非常耗費資源的作業)。

共用程式庫可以是選用或必要的。從 dexpreopt 的角度來看,必須在建構期間提供必要的程式庫 (否則會發生建構錯誤)。選用的程式庫可以存在或不存在 會將其新增至 CLC,並傳遞至 dex2oat, 在 *.odex 檔案中記錄。如果沒有選用資料庫,系統會略過該程式庫 而不會加進 CLC如果建構時間和執行時間狀態不相符 (在某種情況下有提供選用程式庫,但在另一種情況下則沒有),則建構時間和執行時間 CLC 不相符,系統會拒絕已編譯的程式碼。

進階建構系統詳細資料 (資訊清單修正工具)

有時候,<uses-library> 程式庫或應用程式舉例來說,如果其中一個遞移依附元件 開始使用另一個 <uses-library> 標記,而 未更新程式庫或應用程式的資訊清單。

Soong 可以針對特定程式庫計算一些缺少的 <uses-library> 標記 自動或應用程式功能,如同遞移依附元件關閉中的 SDK 程式庫 程式庫或應用程式的程式庫程式庫 (或應用程式) 可能會 依附於一個依附於 SDK 程式庫的靜態程式庫 再次透過另一個程式庫 以遞迴方式運作

並非所有 <uses-library> 標記都能以這種方式計算,但在可行情況下,建議讓 Soong 自動新增資訊清單項目,這樣不易出錯,也能簡化維護作業。舉例來說,如果許多應用程式 新增 <uses-library> 依附元件的程式庫,所有應用程式都必須 因此難以維護