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.bp
或 Android.mk
) 中必須包含 <uses-library>
資訊。
先前 ART 使用略過共用程式庫依附元件的解決方法 (已知
做為 &-classpath
)。這不安全,而且造成一些細微錯誤
已於 Android 12 中移除
因此,如果 Java 模組未在建構檔案中提供正確的 <uses-library>
資訊,就可能導致建構中斷 (建構期間 CLC 不相符) 或首次啟動時間回歸 (啟動期間 CLC 不相符,接著進行 dexopt)。
遷移路徑
請按照下列步驟修正故障的版本:
透過設定
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
。此操作可修正建構錯誤 (特殊情況除外) 列在「修正服務中斷」一節中)。不過 這是暫時性的解決方法,而且可能會導致啟動時間 CLC 不符 後面是 dexopt。
修正在全域停用建構時間檢查前失敗的模組 新增必要的
<uses-library>
資訊 (詳情請參閱修正中斷情形)。 對大部分的模組而言,這需要在Android.bp
或Android.mk
。針對有問題的情況,針對個別模組停用建構時間檢查和 dexpreopt。停用 dexpreopt,省去建構時間的麻煩, 針對在開機時遭拒的構件儲存儲存空間
取消設定,全面重新啟用建構時間檢查 在步驟 1 中設定的
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
;建構 之後就不會失敗 (因為步驟 2 和 3)。逐一修正您在步驟 3 中停用的模組,然後重新啟用 dexpreopt 和
<uses-library>
檢查。視需要回報錯誤。
系統會在 Android 12 中強制執行建構時間 <uses-library>
檢查。
修正服務中斷情形
以下各節將說明如何修正特定類型的毀損情形。
建構錯誤:CLC 不符
建構系統會針對 YAML 檔案中的資訊
Android.bp
或 Android.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
模組:
在模組的
libs
屬性中尋找缺少的程式庫。如果是 Soong 通常會自動新增這類程式庫 特殊情況:- 程式庫不是 SDK 程式庫 (定義為
java_library
,而非java_sdk_library
)。 - 程式庫的程式庫名稱 (在資訊清單中) 與模組名稱 (在建構系統中) 不同。
如要暫時修正這個問題,請在
Android.bp
程式庫定義中新增provides_uses_lib: "<library-name>"
。如要長期解決問題,請修正基礎問題:將程式庫轉換為 SDK 程式庫,或重新命名其模組。- 程式庫不是 SDK 程式庫 (定義為
如果上一步未提供解析度,請新增 必要程式庫的
uses_libs: ["<library-module-name>"]
, 或使用optional_uses_libs: ["<library-module-name>"]
,以 模組的Android.bp
定義。這些屬性會接受模組名稱清單。清單中程式庫的相對順序必須與資訊清單中的順序相同。
Android.mk
模組:
檢查程式庫是否與資訊清單中的程式庫名稱不同 (在資訊清單中) 模組名稱 (位於建構系統中)。如果是這樣,請在程式庫的
Android.mk
檔案中新增LOCAL_PROVIDES_USES_LIBRARY := <library-name>
,或在程式庫的Android.bp
檔案中新增provides_uses_lib: "<library-name>"
,以便暫時修正這個問題 (由於Android.mk
模組可能會依附Android.bp
程式庫,因此兩種情況都有可能)。如果是長期的解決方案,請修正 基礎問題:重新命名程式庫模組新增
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>
依附元件的程式庫,所有應用程式都必須
因此難以維護