Dexpreopt 和 <uses-library> 檢查

機器人12具有構建系統更改DEX文件(dexpreopt)的AOT編譯對於具有的Java模塊<uses-library>依賴性。在某些情況下,這些構建系統更改可能會破壞構建。使用此頁面準備破損,並按照此頁面上的方法修復和減輕它們。

Dexpreopt 是提前編譯 Java 庫和應用程序的過程。 Dexpreopt發生在編譯時間(相對於dexopt,這恰好在設備上)的主機上。由Java模塊(庫或應用程序)中使用共享庫相關性的結構被稱為它的類加載器上下文(CLC)。為了保證 dexpreopt 的正確性,構建時和運行時 CLC 必須一致。構建時 CLC 是 dex2oat 編譯器在 dexpreopt 時使用的(它記錄在 ODEX 文件中),而運行時 CLC 是預編譯代碼加載到設備上的上下文。

出於正確性和性能的原因,這些構建時和運行時 CLC 必須一致。為了正確,有必要處理重複的類。如果運行時的共享庫依賴項與用於編譯的依賴項不同,則某些類的解析方式可能會有所不同,從而導致細微的運行時錯誤。運行時檢查重複類也會影響性能。

受影響的用例

第一次啟動是受這些更改影響的主要用例:如果 ART 檢測到構建時和運行時 CLC 之間的不匹配,它會拒絕 dexpreopt 工件並運行 dexopt。對於後續啟動,這很好,因為應用程序可以在後台進行 dexopted 並存儲在磁盤上。

Android 受影響的領域

這會影響對其他 Java 庫具有運行時依賴關係的所有 Java 應用程序和庫。 Android 有數千個應用程序,其中數百個使用共享庫。合作夥伴也受到影響,因為他們擁有自己的庫和應用程序。

重大變化

構建系統需要知道<uses-library>依賴關係之前它生成dexpreopt生成規則。但是,它不能直接訪問索引和讀取<uses-library>標籤,因為構建系統是不允許讀取任意文件時,它產生的構建規則(由於性能原因)。此外,清單可能打包在 APK 或預構建文件中。因此, <uses-library>信息必須存在於構建文件( Android.bpAndroid.mk )。

先前技術使用的忽略共享庫依賴一種解決方法(被稱為&-classpath )。這是不安全的並會導致細微的錯誤,因此在 Android 12 中刪除了解決方法。

其結果是,Java的模塊不提供正確<uses-library>在構建文件信息可能會導致構建失敗(由一個構建時的CLC不匹配)或首次啟動時回歸(引起的開機時間CLC不匹配後跟 dexopt)。

遷移路徑

請按照以下步驟修復損壞的構建:

  1. 通過設置全局禁用特定產品的構建時檢查

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    在產品生成文件中。此修復編譯錯誤(除特殊情況外,在上市定影破損部分)。但是,這是一種臨時解決方法,它可能會導致啟動時 CLC 不匹配,然後是 dexopt。

  2. 修復模塊之前,全球範圍內通過添加必需的禁用構建時檢查失敗<uses-library>信息構建文件(見修復破損的詳細信息)。對於大多數模塊這需要加入幾行Android.bp ,或Android.mk

  3. 在每個模塊的基礎上,針對有問題的情況禁用構建時檢查和 dexpreopt。禁用 dexpreopt,這樣您就不會在啟動時被拒絕的工件上浪費構建時間和存儲空間。

  4. 全局重新啟用通過解封構建時檢查PRODUCT_BROKEN_VERIFY_USES_LIBRARIES這是在步驟1中設置;在此更改後,構建不應失敗(因為第 2 步和第 3 步)。

  5. 修正在步驟3中,一次一個禁用模塊,然後重新啟用dexpreopt和<uses-library>檢查。如有必要,提交錯誤。

構建時<uses-library>檢查執行Android中12。

修復破損

以下部分告訴您如何修復特定類型的破損。

構建錯誤:CLC 不匹配

構建系統確實在信息之間的集結時間一致性檢查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編譯器過濾器,以verify在dexpreopt,這完全禁用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 )。
    • 該庫具有與其模塊名稱(在構建系統中)不同的庫名稱(在清單中)。

    要暫時解決此問題,添加provides_uses_lib: "<library-name>"Android.bp庫定義。對於長期解決方案,修復潛在問題:將庫轉換為 SDK 庫,或重命名其模塊。

  2. 如果前面的步驟沒有提供的分辨率,添加uses_libs: ["<library-module-name>"]對所需的庫,或optional_uses_libs: ["<library-module-name>"]為可選庫到Android.bp模塊的定義。這些屬性接受模塊名稱列表。列表中庫的相對順序必須與清單中的順序相同。

對於Android.mk模塊:

  1. 檢查庫是否具有與其模塊名稱(在構建系統中)不同的庫名稱(在清單中)。如果這樣做,解決這個暫時通過添加LOCAL_PROVIDES_USES_LIBRARY := <library-name>Android.mk庫的文件,或添加provides_uses_lib: "<library-name>"Android.bp庫的文件(這兩種情況下是可能的,因為一個Android.mk模塊可能依賴於一個Android.bp庫)。對於長期解決方案,請修復根本問題:重命名庫模塊。

  2. 添加LOCAL_USES_LIBRARIES := <library-module-name>對所需的庫;添加LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>為可選庫到Android.mk模塊的定義。這些屬性接受模塊名稱列表。列表中庫的相對順序必須與清單中的相同。

構建錯誤:未知的庫路徑

如果構建系統無法找到一個路徑<uses-library> DEX罐(主機上或設備上的安裝路徑或者是構建時的路徑),它通常會失敗構建。找不到路徑可能表示以某種意外方式配置了庫。通過禁用有問題的模塊的 dexpreopt 臨時修復構建。

Android.bp(模塊屬性):

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

Android.mk(模塊變量):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

提交錯誤以調查任何不受支持的情況。

構建錯誤:缺少庫依賴項

嘗試添加<uses-library>從清單模塊Y與Y的構建文件可能會導致生成錯誤X由於缺少相關性,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提供了一個<uses-library>名為com.android.X

這是一個例子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 命令。要修復它,請確保模塊的構建時檢查通過。如果這不起作用,那麼您的情況可能是構建系統不支持的特殊情況(例如加載另一個 APK 而不是庫的應用程序)。構建系統不能處理所有情況,因為在構建時不可能確定應用程序在運行時加載了什麼。

類加載器上下文

CLC 是一個描述類加載器層次結構的樹狀結構。構建系統採用CLC在狹義上(只覆蓋庫,而不是APK或自定義類加載器):這是圖書館的一棵樹,代表所有的傳遞閉包<uses-library>庫或應用程序的依賴關係。一個CLC的頂層元素是直接<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 CONFIGS列出上安裝的設備( /system/etc/permissions/platform.xml )。每個條目包含一個共享庫的名稱,其DEX jar文件的路徑,和依賴關係的列表(其他共享庫在運行時這一個用途,以及指定在<uses-library>在其清單標籤)。

換言之,有兩個信息源,允許PackageManager在運行時構建CLC: <uses-library>在清單中的標籤,並且在XML CONFIGS共享庫的依賴關係。

主機上(構建時)CLC

CLC 不僅在加載庫或應用程序時需要,在編譯時也需要。編譯可以在設備上 (dexopt) 或在構建期間 (dexpreopt) 進行。由於dexopt發生裝置上的,它有相同的信息PackageManager (清單和共享庫依賴性)。然而,Dexpreopt 發生在主機上和完全不同的環境中,它必須從構建系統中獲取相同的信息。

因此,構建時CLC使用dexpreopt和運行時CLC使用PackageManager都是一樣的東西,但在兩種不同的方式計算。

構建時和運行時必須社區學習中心一致,否則dexpreopt創建AOT編譯的代碼被拒絕。要檢查編譯時和運行時的大陸架界限委員會的平等,在dex2oat編譯器記錄構建時CLC在*.odex文件(在classpath的OAT文件頭字段)。要查找存儲的 CLC,請使用以下命令:

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

引導期間在 logcat 中報告構建時和運行時 CLC 不匹配。使用以下命令搜索它:

logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'

不匹配對性能不利,因為它迫使庫或應用程序要么被 dexopted,要么在沒有優化的情況下運行(例如,應用程序的代碼可能需要從 APK 的內存中提取,這是一項非常昂貴的操作)。

共享庫可以是可選的,也可以是必需的。從 dexpreopt 的角度來看,所需的庫必須在構建時存在(它的缺失是構建錯誤)。可選的庫可以在構建時存在或不存在:如果存在的話,它的加入CLC,傳遞給dex2oat,並記錄在*.odex文件。如果不存在可選庫,則會跳過它並且不會將其添加到 CLC。如果構建時和運行時狀態不匹配(可選庫在一種情況下存在,但在另一種情況下不存在),則構建時和運行時 CLC 不匹配,編譯後的代碼將被拒絕。

高級構建系統詳細信息(清單修復程序)

有時<uses-library>代碼從庫或應用程序的源清單丟失。這可能發生,例如,如果庫或應用程序的傳遞依賴的一個開始使用另一個<uses-library>標籤,以及庫或應用程序的清單不更新,包括它。

宋可以計算一些丟失的<uses-library>自動標籤為一個給定的庫或應用程序,如在庫或應用程序的傳遞依賴閉合的SDK庫。需要閉包是因為庫(或應用程序)可能依賴於依賴於 SDK 庫的靜態庫,並且可能再次通過另一個庫傳遞依賴。

並非所有的<uses-library>標籤可以計算這種方式,但可能的情況下,這是prefereable讓宋楚瑜自動添加清單條目;它不易出錯並簡化了維護。例如,當許多應用程序使用靜態庫,增加了新的<uses-library>相關性,所有的應用程序必須進行更新,這是難以維持。