Dexpreoptおよび<uses-library>チェック

Android 12では、 <uses-library>依存関係を持つJavaモジュール用のDEXファイル(dexpreopt)のAOTコンパイルに対するビルドシステムが変更されています。場合によっては、これらのビルドシステムの変更によりビルドが破損する可能性があります。このページを使用して破損の準備をし、このページのレシピに従ってそれらを修正および軽減します。

Dexpreoptは、Javaライブラリとアプリを事前にコンパイルするプロセスです。 Dexpreoptは、ビルド時にホスト上で発生します(デバイス上で発生するdexoptとは対照的です)。 Javaモジュール(ライブラリまたはアプリ)によって使用される共有ライブラリの依存関係の構造は、クラスローダーコンテキスト(CLC)と呼ばれます。 dexpreoptの正確性を保証するには、ビルド時と実行時のCLCが一致している必要があります。ビルド時CLCは、dex2oatコンパイラがdexpreopt時に使用するもの(ODEXファイルに記録されます)であり、ランタイムCLCは、プリコンパイルされたコードがデバイスにロードされるコンテキストです。

これらのビルド時とランタイムのCLCは、正確さとパフォーマンスの両方の理由から一致している必要があります。正確を期すために、重複するクラスを処理する必要があります。実行時の共有ライブラリの依存関係がコンパイルに使用される依存関係と異なる場合、一部のクラスの解決方法が異なり、実行時の微妙なバグが発生する可能性があります。パフォーマンスは、重複するクラスの実行時チェックによっても影響を受けます。

影響を受けるユースケース

最初の起動は、これらの変更の影響を受ける主な使用例です。ARTがビルド時と実行時のCLCの不一致を検出すると、dexpreoptアーティファクトを拒否し、代わりにdexoptを実行します。その後の起動では、アプリをバックグラウンドでデコプトしてディスクに保存できるため、これは問題ありません。

Androidの影響を受ける領域

これは、他のJavaライブラリにランタイム依存関係があるすべてのJavaアプリとライブラリに影響します。 Androidには数千のアプリがあり、そのうちの数百は共有ライブラリを使用しています。パートナーは独自のライブラリとアプリを持っているため、同様に影響を受けます。

重大な変更

ビルドシステムは、dexpreoptビルドルールを生成する前に、 <uses-library>依存関係を知る必要があります。ただし、ビルドシステムがビルドルールを生成するときに任意のファイルを読み取ることは許可されていないため(パフォーマンス上の理由から)、マニフェストに直接アクセスしてその中の<uses-library>タグを読み取ることはできません。さらに、マニフェストはAPK内にパッケージ化されているか、ビルド済みである可能性があります。したがって、 <uses-library>情報はビルドファイル( Android.bpまたはAndroid.mk )に存在する必要があります。

以前、ARTは共有ライブラリの依存関係( &-classpathとして知られている)を無視する回避策を使用していました。これは安全ではなく、微妙なバグを引き起こしたため、Android12で回避策が削除されました。

その結果、ビルドファイルに正しい<uses-library>情報を提供しないJavaモジュールは、ビルドの破損(ビルド時のCLCの不一致が原因)または初回起動時のリグレッション(起動時のCLCが原因)を引き起こす可能性があります。不一致の後にdexoptが続きます)。

移行パス

壊れたビルドを修正するには、次の手順に従います。

  1. 設定することにより、特定の製品のビルド時チェックをグローバルに無効にします

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    製品makefileで。これにより、ビルドエラーが修正されます(破損の修正セクションにリストされている特別な場合を除く)。ただし、これは一時的な回避策であり、起動時のCLCの不一致とそれに続くdexoptが発生する可能性があります。

  2. ビルドファイルに必要な<uses-library>情報を追加して、ビルド時チェックをグローバルに無効にする前に失敗したモジュールを修正します(詳細については、破損の修正を参照してください)。ほとんどのモジュールでは、 Android.bpまたはAndroid.mkに数行追加する必要があります。

  3. モジュールごとに、問題のあるケースのビルド時チェックとdexpreoptを無効にします。 dexpreoptを無効にして、起動時に拒否されるアーティファクトのビルド時間とストレージを無駄にしないようにします。

  4. 手順1で設定したPRODUCT_BROKEN_VERIFY_USES_LIBRARIESの設定を解除して、ビルド時チェックをグローバルに再度有効にします。この変更後、ビルドが失敗することはありません(ステップ2と3のため)。

  5. 手順3で無効にしたモジュールを一度に1つずつ修正してから、dexpreoptと<uses-library>チェックを再度有効にします。必要に応じてバグを報告します。

ビルド時の<uses-library>チェックはAndroid12で実施されます。

破損の修正

次のセクションでは、特定の種類の破損を修正する方法について説明します。

ビルドエラー:CLCの不一致

ビルドシステムは、 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

エラーメッセージが示すように、緊急性に応じて、複数の解決策があります。

  • 製品全体の一時的な修正については、製品のmakefileで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_sdk_libraryではなくjava_libraryとして定義されています)。
    • ライブラリのライブラリ名(マニフェスト内)は、モジュール名(ビルドシステム内)とは異なります。

    これを一時的に修正するには、 Android.bpライブラリ定義にprovides_uses_lib provides_uses_lib: "<library-name>"を追加します。長期的な解決策として、根本的な問題を修正します。ライブラリをSDKライブラリに変換するか、モジュールの名前を変更します。

  2. 前の手順で解決策が提供されなかった場合は、必須ライブラリの場合はuses_libs: ["<library-module-name>"] -name>"]を追加し、 Android.bpのオプションライブラリの場合はoptional_uses_libs: ["<library-module-name>"]を追加します。モジュールのAndroid.bp定義。これらのプロパティは、モジュール名のリストを受け入れます。リスト上のライブラリーの相対的な順序は、マニフェストの順序と同じでなければなりません。

Android.mkモジュールの場合:

  1. ライブラリのライブラリ名(マニフェスト内)がモジュール名(ビルドシステム内)と異なるかどうかを確認します。含まれている場合は、ライブラリのAndroid.mkファイルにLOCAL_PROVIDES_USES_LIBRARY := <library-name>を追加するか、ライブラリのAndroid.mkファイルにAndroid.bp provides_uses_lib: "<library-name>"を追加して、これを一時的に修正します(どちらの場合も) Android.mkモジュールはAndroid.bpライブラリに依存している可能性があるため、可能です)。長期的な解決策として、根本的な問題を修正します。ライブラリモジュールの名前を変更します。

  2. 必要なライブラリにLOCAL_USES_LIBRARIES := <library-module-name>を追加します。モジュールのAndroid.mk定義に、オプションのライブラリLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>を追加します。これらのプロパティは、モジュール名のリストを受け入れます。リスト上のライブラリーの相対的な順序は、マニフェストと同じでなければなりません。

ビルドエラー:不明なライブラリパス

ビルドシステムが<uses-library> DEX jarへのパス(ホスト上のビルド時パスまたはデバイス上のインストールパスのいずれか)を見つけられない場合、通常はビルドに失敗します。パスが見つからない場合は、ライブラリが予期しない方法で構成されていることを示している可能性があります。問題のあるモジュールのdexpreoptを無効にして、ビルドを一時的に修正します。

Android.bp(モジュールのプロパティ):

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

Android.mk(モジュール変数):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

サポートされていないシナリオを調査するには、バグを報告してください。

ビルドエラー:ライブラリの依存関係がありません

モジュールYのマニフェストからYのビルドファイルに<uses-library> 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という名前のモジュールが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コマンドを探してください。これを修正するには、モジュールのビルド時チェックに合格していることを確認してください。それが機能しない場合は、ビルドシステムでサポートされていない特殊なケースである可能性があります(ライブラリではなく、別のAPKをロードするアプリなど)。ビルド時にアプリが実行時に何をロードするかを確実に知ることは不可能であるため、ビルドシステムはすべてのケースを処理するわけではありません。

クラスローダーコンテキスト

CLCは、クラスローダー階層を記述するツリーのような構造です。ビルドシステムは狭義のCLCを使用します(APKやカスタムクラスローダーではなく、ライブラリのみを対象とします)。これは、ライブラリまたはアプリのすべての<uses-library>依存関係の推移閉包を表すライブラリのツリーです。 CLCの最上位要素は、マニフェスト(クラスパス)で指定された直接の<uses-library>依存関係です。 CLCツリーの各ノードは<uses-library>ノードであり、独自の<uses-library>サブノードを持つ場合があります。

<uses-library>依存関係は有向非巡回グラフであり、必ずしもツリーである必要はないため、CLCには同じライブラリの複数のサブツリーを含めることができます。言い換えると、CLCは、ツリーに「展開された」依存関係グラフです。複製は論理レベルでのみ行われます。実際の基礎となるクラスローダーは複製されません(実行時に、ライブラリごとに1つのクラスローダーインスタンスがあります)。

CLCは、ライブラリまたはアプリで使用されるJavaクラスを解決するときに、ライブラリのルックアップ順序を定義します。ライブラリには重複するクラスが含まれる可能性があり、クラスは最初の一致に解決されるため、ルックアップの順序は重要です。

デバイス上(ランタイム)CLC

PackageManagerframeworks/base内)は、デバイスにJavaモジュールをロードするためのCLCを作成します。モジュールのマニフェストの<uses-library>タグにリストされているライブラリをトップレベルのCLC要素として追加します。

使用されるライブラリごとに、 PackageManagerはすべての<uses-library>依存関係(そのライブラリのマニフェストでタグとして指定)を取得し、依存関係ごとにネストされたCLCを追加します。このプロセスは、構築されたCLCツリーのすべてのリーフノードが<uses-library>依存関係のないライブラリになるまで再帰的に続行されます。

PackageManagerは、共有ライブラリのみを認識します。この使用法での共有の定義は、通常の意味とは異なります(共有と静的の場合など)。 Androidでは、Java共有ライブラリは、デバイス( /system/etc/permissions/platform.xml )にインストールされているXML構成にリストされているライブラリです。各エントリには、共有ライブラリの名前、そのDEX jarファイルへのパス、および依存関係のリスト(これが実行時に使用し、マニフェスト<uses-library>タグで指定する他の共有ライブラリ)が含まれます。

つまり、 PackageManagerが実行時にCLCを構築できるようにする情報源は2つあります。マニフェストの<uses-library>タグと、XML構成の共有ライブラリの依存関係です。

オンホスト(ビルド時)CLC

CLCは、ライブラリやアプリを読み込むときに必要になるだけでなく、コンパイルするときにも必要になります。コンパイルは、デバイス上(dexopt)またはビルド中(dexpreopt)のいずれかで実行できます。 dexoptはデバイス上で実行されるため、 PackageManagerと同じ情報(マニフェストと共有ライブラリの依存関係)があります。ただし、Dexpreoptはオンホストでまったく異なる環境で実行されるため、ビルドシステムから同じ情報を取得する必要があります。

したがって、dexpreoptで使用されるビルド時CLCとPackageManagerで使用される実行時CLCは同じものですが、2つの異なる方法で計算されます。

ビルド時とランタイムのCLCは一致する必要があります。一致しない場合、dexpreoptによって作成されたAOTコンパイル済みコードは拒否されます。ビルド時とランタイムのCLCが等しいかどうかを確認するために、dex2oatコンパイラーはビルド時のCLCを*.odexファイル(OATファイルヘッダーのclasspathフィールド)に記録します。保存されているCLCを見つけるには、次のコマンドを使用します。

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

ビルド時と実行時のCLCの不一致は、起動時にlogcatで報告されます。次のコマンドで検索します。

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

不一致は、ライブラリまたはアプリを非選択にするか、最適化せずに実行する必要があるため、パフォーマンスに悪影響を及ぼします(たとえば、アプリのコードをAPKからメモリに抽出する必要がある場合がありますが、これは非常にコストのかかる操作です)。

共有ライブラリは、オプションまたは必須のいずれかです。 dexpreoptの観点からは、必要なライブラリがビルド時に存在する必要があります(存在しない場合はビルドエラーです)。オプションのライブラリは、ビルド時に存在する場合と存在しない場合があります。存在する場合は、CLCに追加され、dex2oatに渡され、 *.odexファイルに記録されます。オプションのライブラリがない場合、そのライブラリはスキップされ、CLCに追加されません。ビルド時と実行時のステータスに不一致がある場合(オプションのライブラリは一方の場合には存在しますが、他方には存在しません)、ビルド時と実行時のCLCは一致せず、コンパイルされたコードは拒否されます。

高度なビルドシステムの詳細(マニフェストフィクサー)

ライブラリまたはアプリのソースマニフェストに<uses-library>タグがない場合があります。これは、たとえば、ライブラリまたはアプリの推移的な依存関係の1つが別の<uses-library>タグの使用を開始し、ライブラリまたはアプリのマニフェストがそれを含むように更新されていない場合に発生する可能性があります。

Soongは、ライブラリまたはアプリの推移的な依存関係クロージャのSDKライブラリとして、特定のライブラリまたはアプリの欠落している<uses-library>タグの一部を自動的に計算できます。ライブラリ(またはアプリ)がSDKライブラリに依存する静的ライブラリに依存している可能性があり、また別のライブラリを介して推移的に依存している可能性があるため、クロージャが必要です。

すべての<uses-library>タグをこの方法で計算できるわけではありませんが、可能であれば、Soongにマニフェストエントリを自動的に追加させることをお勧めします。エラーが発生しにくく、メンテナンスが簡単です。たとえば、多くのアプリが新しい<uses-library>依存関係を追加する静的ライブラリを使用する場合、すべてのアプリを更新する必要があり、これを維持するのは困難です。