Dexpreopt dan pemeriksaan <uses-library>

Android 12 memiliki perubahan sistem build ke kompilasi AOT file DEX (dexpreopt) untuk modul Java yang memiliki dependensi <uses-library> . Dalam beberapa kasus, perubahan sistem build ini dapat merusak build. Gunakan halaman ini untuk mempersiapkan kerusakan, dan ikuti resep di halaman ini untuk memperbaiki dan menguranginya.

Dexpreopt adalah proses kompilasi perpustakaan dan aplikasi Java sebelumnya. Dexpreopt terjadi di host pada waktu pembuatan (berlawanan dengan dexopt , yang terjadi di perangkat). Struktur dependensi pustaka bersama yang digunakan oleh modul Java (pustaka atau aplikasi) dikenal sebagai konteks pemuat kelas (CLC). Untuk menjamin kebenaran dexpreopt, CLC build-time dan run-time harus bertepatan. Build-time CLC adalah apa yang digunakan kompiler dex2oat pada waktu dexpreopt (dicatat dalam file ODEX), dan CLC run-time adalah konteks di mana kode yang telah dikompilasi dimuat pada perangkat.

CLC build-time dan run-time ini harus bertepatan untuk alasan kebenaran dan kinerja. Untuk kebenaran, perlu untuk menangani kelas duplikat. Jika dependensi pustaka bersama saat runtime berbeda dari yang digunakan untuk kompilasi, beberapa kelas mungkin diselesaikan secara berbeda, menyebabkan bug runtime yang tidak kentara. Kinerja juga dipengaruhi oleh pemeriksaan runtime untuk kelas duplikat.

Kasus penggunaan yang terpengaruh

Boot pertama adalah kasus penggunaan utama yang terpengaruh oleh perubahan ini: jika ART mendeteksi ketidakcocokan antara CLC waktu pembuatan dan waktu proses, ART akan menolak artefak dexpreopt dan menjalankan dexopt sebagai gantinya. Untuk boot berikutnya, ini baik-baik saja karena aplikasi dapat di-dexopted di latar belakang dan disimpan di disk.

Area Android yang terpengaruh

Ini memengaruhi semua aplikasi dan pustaka Java yang memiliki dependensi waktu proses pada pustaka Java lainnya. Android memiliki ribuan aplikasi, dan ratusan di antaranya menggunakan pustaka bersama. Mitra juga terpengaruh, karena mereka memiliki perpustakaan dan aplikasi mereka sendiri.

Melanggar perubahan

Sistem build perlu mengetahui dependensi <uses-library> sebelum menghasilkan aturan build dexpreopt. Namun, itu tidak dapat mengakses manifes secara langsung dan membaca <uses-library> di dalamnya, karena sistem build tidak diizinkan untuk membaca file arbitrer saat membuat aturan build (untuk alasan kinerja). Selain itu, manifes mungkin dikemas dalam APK atau bawaan. Oleh karena itu, informasi <uses-library> harus ada di file build ( Android.bp atau Android.mk ).

Sebelumnya ART menggunakan solusi yang mengabaikan dependensi pustaka bersama (dikenal sebagai &-classpath ). Ini tidak aman dan menyebabkan bug halus, jadi solusinya telah dihapus di Android 12.

Akibatnya, modul Java yang tidak memberikan informasi <uses-library> yang benar dalam file build mereka dapat menyebabkan kerusakan build (disebabkan oleh ketidakcocokan CLC waktu build) atau regresi waktu boot pertama (disebabkan oleh CLC waktu booting ketidakcocokan diikuti oleh dexopt).

Jalur migrasi

Ikuti langkah-langkah ini untuk memperbaiki build yang rusak:

  1. Nonaktifkan pemeriksaan waktu pembuatan secara global untuk produk tertentu dengan menyetel

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    dalam file make produk. Ini memperbaiki kesalahan build (kecuali untuk kasus khusus, yang tercantum di bagian Memperbaiki kerusakan ). Namun, ini adalah solusi sementara, dan dapat menyebabkan ketidakcocokan CLC waktu boot diikuti oleh dexopt.

  2. Perbaiki modul yang gagal sebelum Anda menonaktifkan pemeriksaan waktu build secara global dengan menambahkan informasi <uses-library> yang diperlukan ke file build mereka (lihat Memperbaiki kerusakan untuk detailnya). Untuk sebagian besar modul, ini memerlukan penambahan beberapa baris di Android.bp , atau di Android.mk .

  3. Nonaktifkan pemeriksaan waktu pembuatan dan dexpreopt untuk kasus yang bermasalah, berdasarkan per-modul. Nonaktifkan dexpreopt sehingga Anda tidak membuang waktu pembuatan dan penyimpanan pada artefak yang ditolak saat boot.

  4. Aktifkan kembali pemeriksaan waktu pembuatan secara global dengan menghapus setelan PRODUCT_BROKEN_VERIFY_USES_LIBRARIES yang disetel di Langkah 1; build tidak boleh gagal setelah perubahan ini (karena langkah 2 dan 3).

  5. Perbaiki modul yang Anda nonaktifkan di Langkah 3, satu per satu, lalu aktifkan kembali dexpreopt dan centang <uses-library> . File bug jika perlu.

Pemeriksaan <uses-library> waktu pembuatan diterapkan di Android 12.

Memperbaiki kerusakan

Bagian berikut memberi tahu Anda cara memperbaiki jenis kerusakan tertentu.

Kesalahan pembuatan: ketidakcocokan CLC

Sistem build melakukan pemeriksaan koherensi waktu build antara informasi dalam file Android.bp atau Android.mk dan manifes. Sistem build tidak dapat membaca manifes, tetapi dapat membuat aturan build untuk membaca manifes (mengekstraknya dari APK jika perlu), dan membandingkan <uses-library> dalam manifes dengan informasi <uses-library> di file build. Jika pemeriksaan gagal, kesalahannya terlihat seperti ini:

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

Seperti yang disarankan oleh pesan kesalahan, ada beberapa solusi, tergantung pada urgensinya:

  • Untuk perbaikan sementara di seluruh produk , setel PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true di makefile produk. Pemeriksaan koherensi waktu build masih dilakukan, tetapi kegagalan pemeriksaan tidak berarti kegagalan build. Sebaliknya, kegagalan pemeriksaan membuat sistem pembangunan menurunkan versi filter kompiler dex2oat untuk verify di dexpreopt, yang menonaktifkan kompilasi AOT sepenuhnya untuk modul ini.
  • Untuk perbaikan baris perintah global yang cepat , gunakan variabel lingkungan RELAX_USES_LIBRARY_CHECK=true . Ini memiliki efek yang sama seperti PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , tetapi dimaksudkan untuk digunakan pada baris perintah. Variabel lingkungan menimpa variabel produk.
  • Untuk solusi akar penyebab memperbaiki kesalahan, buat sistem build mengetahui <uses-library> dalam manifes. Pemeriksaan pesan kesalahan menunjukkan pustaka mana yang menyebabkan masalah (seperti halnya memeriksa AndroidManifest.xml atau manifes di dalam APK yang dapat diperiksa dengan ` aapt dump badging $APK | grep uses-library `).

Untuk modul Android.bp :

  1. Cari perpustakaan yang hilang di properti libs dari modul. Jika ada, Soong biasanya menambahkan perpustakaan tersebut secara otomatis, kecuali dalam kasus khusus ini:

    • Pustaka bukan pustaka SDK (ini didefinisikan sebagai java_library daripada java_sdk_library ).
    • Pustaka memiliki nama pustaka yang berbeda (dalam manifes) dari nama modulnya (dalam sistem pembangunan).

    Untuk memperbaikinya sementara, tambahkan provides_uses_lib: "<library-name>" dalam definisi pustaka Android.bp . Untuk solusi jangka panjang, perbaiki masalah mendasar: konversi pustaka menjadi pustaka SDK, atau ganti nama modulnya.

  2. Jika langkah sebelumnya tidak memberikan resolusi, tambahkan uses_libs: ["<library-module-name>"] untuk pustaka yang diperlukan, atau optional_uses_libs: ["<library-module-name>"] untuk pustaka opsional ke Android.bp definisi Android.bp dari modul. Properti ini menerima daftar nama modul. Urutan relatif library pada daftar harus sama dengan urutan dalam manifes.

Untuk modul Android.mk :

  1. Periksa apakah perpustakaan memiliki nama perpustakaan yang berbeda (dalam manifes) dari nama modulnya (dalam sistem pembangunan). Jika ya, perbaiki ini sementara dengan menambahkan LOCAL_PROVIDES_USES_LIBRARY := <library-name> di file Android.mk library, atau tambahkan provide_uses_lib provides_uses_lib: "<library-name>" di file Android.bp library (keduanya dimungkinkan karena modul Android.mk mungkin bergantung pada pustaka Android.bp ). Untuk solusi jangka panjang, perbaiki masalah mendasar: ganti nama modul perpustakaan.

  2. Tambahkan LOCAL_USES_LIBRARIES := <library-module-name> untuk perpustakaan yang diperlukan; tambahkan LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> untuk pustaka opsional ke definisi modul Android.mk . Properti ini menerima daftar nama modul. Urutan relatif pustaka pada daftar harus sama seperti di manifes.

Kesalahan pembuatan: jalur perpustakaan tidak dikenal

Jika sistem build tidak dapat menemukan jalur ke toples <uses-library> DEX (baik jalur waktu build di host atau jalur instal di perangkat), biasanya sistem build akan gagal. Kegagalan untuk menemukan jalur dapat menunjukkan bahwa perpustakaan dikonfigurasi dalam beberapa cara yang tidak terduga. Perbaiki sementara build dengan menonaktifkan dexpreopt untuk modul yang bermasalah.

Android.bp (properti modul):

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

Android.mk (variabel modul):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Ajukan bug untuk menyelidiki skenario yang tidak didukung.

Kesalahan pembuatan: ketergantungan perpustakaan tidak ada

Upaya untuk menambahkan <uses-library> X dari manifes modul Y ke file build untuk Y dapat mengakibatkan kesalahan build karena dependensi yang hilang, X.

Ini adalah contoh pesan kesalahan untuk modul Android.bp:

"Y" depends on undefined module "X"

Ini adalah contoh pesan kesalahan untuk modul 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

Sumber umum kesalahan tersebut adalah ketika pustaka diberi nama berbeda dari modul terkait yang dinamai dalam sistem pembangunan. Misalnya, jika entri <uses-library> manifes adalah com.android.X , tetapi nama modul library hanya X , ini akan menyebabkan kesalahan. Untuk mengatasi kasus ini, beri tahu sistem build bahwa modul bernama X menyediakan <uses-library> bernama com.android.X .

Ini adalah contoh untuk perpustakaan Android.bp (properti modul):

provides_uses_lib: “com.android.X”,

Ini adalah contoh untuk perpustakaan Android.mk (variabel modul):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Ketidakcocokan CLC waktu boot

Saat boot pertama, cari logcat untuk pesan yang terkait dengan ketidakcocokan CLC, seperti yang ditunjukkan di bawah ini:

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

Outputnya dapat memiliki pesan dari formulir yang ditunjukkan di sini:

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

Jika Anda mendapatkan peringatan ketidakcocokan CLC, cari perintah dexopt untuk modul yang salah. Untuk memperbaikinya, pastikan pemeriksaan waktu build untuk modul berlalu. Jika tidak berhasil, kasus Anda mungkin merupakan kasus khusus yang tidak didukung oleh sistem build (seperti aplikasi yang memuat APK lain, bukan library). Sistem build tidak menangani semua kasus, karena pada waktu build tidak mungkin untuk mengetahui dengan pasti apa yang dimuat aplikasi saat runtime.

Konteks pemuat kelas

CLC adalah struktur seperti pohon yang menggambarkan hierarki class-loader. Sistem pembangunan menggunakan CLC dalam arti sempit (hanya mencakup pustaka, bukan APK atau pemuat kelas khusus): ini adalah hierarki pustaka yang mewakili penutupan transitif dari semua dependensi <uses-library> dari pustaka atau aplikasi. Elemen tingkat atas CLC adalah dependensi <uses-library> langsung yang ditentukan dalam manifes (classpath). Setiap node dari pohon CLC adalah node <uses-library> yang mungkin memiliki sub-node <uses-library> sendiri.

Karena dependensi <uses-library> adalah grafik asiklik terarah, dan belum tentu pohon, CLC dapat berisi beberapa subpohon untuk pustaka yang sama. Dengan kata lain, CLC adalah grafik ketergantungan yang "terbuka" ke pohon. Duplikasi hanya pada tingkat logis; pemuat kelas yang mendasari sebenarnya tidak diduplikasi (saat runtime ada satu contoh pemuat kelas untuk setiap perpustakaan).

CLC mendefinisikan urutan pencarian pustaka saat menyelesaikan kelas Java yang digunakan oleh pustaka atau aplikasi. Urutan pencarian penting karena perpustakaan dapat berisi kelas duplikat, dan kelas diselesaikan ke kecocokan pertama.

Pada perangkat (run-time) CLC

PackageManager (dalam frameworks/base ) membuat CLC untuk memuat modul Java di perangkat. Ia menambahkan pustaka yang tercantum dalam <uses-library> dalam manifes modul sebagai elemen CLC tingkat atas.

Untuk setiap perpustakaan yang digunakan, PackageManager mendapatkan semua dependensi <uses-library> (ditentukan sebagai tag dalam manifes perpustakaan itu) dan menambahkan CLC bersarang untuk setiap dependensi. Proses ini berlanjut secara rekursif sampai semua simpul daun dari pohon CLC yang dibangun adalah perpustakaan tanpa dependensi <uses-library> .

PackageManager hanya mengetahui perpustakaan bersama. Definisi shared dalam penggunaan ini berbeda dari arti biasanya (seperti dalam shared vs. static). Di Android, pustaka bersama Java adalah pustaka yang terdaftar dalam konfigurasi XML yang diinstal pada perangkat ( /system/etc/permissions/platform.xml ). Setiap entri berisi nama pustaka bersama, jalur ke file jar DEX-nya, dan daftar dependensi (pustaka bersama lainnya yang digunakan saat runtime, dan ditentukan dalam <uses-library> dalam manifesnya).

Dengan kata lain, ada dua sumber informasi yang memungkinkan PackageManager membuat CLC saat runtime: <uses-library> dalam manifes, dan dependensi pustaka bersama dalam konfigurasi XML.

CLC di-host (waktu pembuatan)

CLC tidak hanya diperlukan saat memuat pustaka atau aplikasi, tetapi juga diperlukan saat mengompilasinya. Kompilasi dapat terjadi baik di perangkat (dexopt) atau selama build (dexpreopt). Karena dexopt berlangsung di perangkat, ia memiliki informasi yang sama dengan PackageManager (manifes dan dependensi perpustakaan bersama). Namun, Dexpreopt berlangsung di host dan di lingkungan yang sama sekali berbeda, dan harus mendapatkan informasi yang sama dari sistem pembangunan.

Jadi, CLC waktu pembuatan yang digunakan oleh dexpreopt dan CLC waktu proses yang digunakan oleh PackageManager adalah hal yang sama, tetapi dihitung dengan dua cara berbeda.

CLC build-time dan run-time harus bertepatan, jika tidak, kode yang dikompilasi AOT yang dibuat oleh dexpreopt akan ditolak. Untuk memeriksa kesetaraan CLC build-time dan run-time, kompiler dex2oat mencatat CLC waktu build dalam file *.odex (di bidang classpath dari header file OAT). Untuk menemukan CLC yang disimpan, gunakan perintah ini:

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

Ketidakcocokan CLC waktu build dan run-time dilaporkan di logcat selama boot. Cari dengan perintah ini:

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

Ketidakcocokan buruk untuk kinerja, karena memaksa pustaka atau aplikasi untuk di-dexopted, atau berjalan tanpa pengoptimalan (misalnya, kode aplikasi mungkin perlu diekstraksi dalam memori dari APK, operasi yang sangat mahal).

Pustaka bersama dapat berupa opsional atau wajib. Dari sudut pandang dexpreopt, perpustakaan yang diperlukan harus ada pada waktu pembuatan (ketidakhadirannya adalah kesalahan pembuatan). Pustaka opsional dapat ada atau tidak ada pada waktu pembuatan: jika ada, pustaka tersebut akan ditambahkan ke CLC, diteruskan ke dex2oat, dan direkam dalam file *.odex . Jika pustaka opsional tidak ada, pustaka tersebut akan dilewati dan tidak ditambahkan ke CLC. Jika ada ketidakcocokan antara status build-time dan run-time (library opsional hadir dalam satu kasus, tetapi tidak pada kasus lainnya), maka CLC build-time dan run-time tidak cocok dan kode yang dikompilasi akan ditolak.

Detail sistem build lanjutan (pemecah masalah manifes)

Terkadang <uses-library> hilang dari manifes sumber pustaka atau aplikasi. Ini bisa terjadi, misalnya, jika salah satu dependensi transitif library atau aplikasi mulai menggunakan <uses-library> lain, dan library atau manifes aplikasi tidak diperbarui untuk menyertakannya.

Soong dapat menghitung beberapa <uses-library> yang hilang untuk pustaka atau aplikasi tertentu secara otomatis, karena pustaka SDK dalam penutupan dependensi transitif dari pustaka atau aplikasi. Penutupan diperlukan karena pustaka (atau aplikasi) mungkin bergantung pada pustaka statis yang bergantung pada pustaka SDK, dan mungkin lagi bergantung secara transitif melalui pustaka lain.

Tidak semua <uses-library> dapat dihitung dengan cara ini, tetapi jika memungkinkan, sebaiknya Soong menambahkan entri manifes secara otomatis; itu kurang rawan kesalahan dan menyederhanakan perawatan. Misalnya, ketika banyak aplikasi menggunakan pustaka statis yang menambahkan dependensi <uses-library> baru, semua aplikasi harus diperbarui, yang sulit dipertahankan.