Pemeriksaan Dexpreopt dan <uses-library>

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

Dexpreopt adalah proses kompilasi Java library dan aplikasi ahead-of-time. Dexpreopt terjadi di host pada waktu build (berbeda dengan dexopt, yang terjadi di perangkat). Struktur dependensi library bersama yang digunakan oleh modul Java (library atau aplikasi) dikenal sebagai konteks loader class (CLC). Untuk menjamin kebenaran dexpreopt, CLC waktu build dan runtime harus bertepatan. CLC waktu build adalah yang digunakan oleh compiler dex2oat pada waktu dexpreopt (dicatat dalam file ODEX), dan CLC runtime adalah konteks tempat kode yang telah dikompilasi dimuat di perangkat.

CLC waktu build dan runtime ini harus bertepatan karena alasan kebenaran dan performa. Untuk kebenaran, Anda harus menangani class duplikat. Jika dependensi library bersama saat runtime berbeda dengan yang digunakan untuk kompilasi, beberapa class mungkin diselesaikan secara berbeda, sehingga menyebabkan bug runtime yang tidak kentara. Performa juga terpengaruh oleh pemeriksaan runtime untuk class duplikat.

Kasus penggunaan yang terpengaruh

Boot pertama adalah kasus penggunaan utama yang terpengaruh oleh perubahan ini: jika ART mendeteksi ketidakcocokan antara CLC waktu build dan runtime, ART akan menolak artefak dexpreopt dan menjalankan dexopt. Untuk boot berikutnya, hal ini tidak masalah karena aplikasi dapat di-dexopt di latar belakang dan disimpan di disk.

Area Android yang terpengaruh

Hal ini memengaruhi semua aplikasi dan library Java yang memiliki dependensi runtime pada library Java lainnya. Android memiliki ribuan aplikasi, dan ratusan di antaranya menggunakan library bersama. Partner juga terpengaruh, karena mereka memiliki library dan aplikasi sendiri.

Perubahan yang dapat menyebabkan gangguan

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

Sebelumnya, ART menggunakan solusi sementara yang mengabaikan dependensi library bersama (dikenal sebagai &-classpath). Hal ini tidak aman dan menyebabkan bug yang tidak kentara, sehingga solusi sementara dihapus di Android 12.

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

Jalur migrasi

Ikuti langkah-langkah berikut untuk memperbaiki build yang rusak:

  1. Nonaktifkan pemeriksaan waktu build secara global untuk produk tertentu dengan menetapkan

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    dalam makefile produk. Tindakan ini akan memperbaiki error build (kecuali untuk kasus khusus, yang tercantum di bagian Memperbaiki kerusakan). Namun, ini adalah solusi sementara, dan dapat menyebabkan ketidakcocokan CLC waktu boot yang 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-nya (lihat Memperbaiki kerusakan untuk mengetahui detailnya). Untuk sebagian besar modul, hal ini memerlukan penambahan beberapa baris di Android.bp, atau di Android.mk.

  3. Nonaktifkan pemeriksaan waktu build dan dexpreopt untuk kasus yang bermasalah, berdasarkan per modul. Nonaktifkan dexpreopt agar Anda tidak membuang waktu build dan penyimpanan untuk artefak yang ditolak saat boot.

  4. Aktifkan kembali pemeriksaan waktu build secara global dengan menghapus setelan PRODUCT_BROKEN_VERIFY_USES_LIBRARIES yang ditetapkan 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 pemeriksaan <uses-library>. Laporkan bug jika perlu.

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

Memperbaiki kerusakan

Bagian berikut menjelaskan cara memperbaiki jenis kerusakan tertentu.

Error build: 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 menghasilkan aturan build untuk membaca manifes (mengekstrak dari APK jika diperlukan), dan membandingkan tag <uses-library> dalam manifes dengan informasi <uses-library> dalam file build. Jika pemeriksaan gagal, error akan 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 error, ada beberapa solusi, bergantung pada urgensinya:

  • Untuk perbaikan sementara di seluruh produk, tetapkan PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true dalam makefile produk. Pemeriksaan koherensi waktu build masih dilakukan, tetapi kegagalan pemeriksaan tidak berarti kegagalan build. Sebaliknya, kegagalan pemeriksaan akan membuat sistem build menurunkan filter compiler dex2oat ke verify di dexpreopt, yang akan menonaktifkan kompilasi AOT sepenuhnya untuk modul ini.
  • Untuk perbaikan command line global yang cepat, gunakan variabel lingkungan RELAX_USES_LIBRARY_CHECK=true. Tindakan ini memiliki efek yang sama dengan PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, tetapi dimaksudkan untuk digunakan di command line. Variabel lingkungan akan mengganti variabel produk.
  • Untuk solusi perbaikan penyebab utama error, buat sistem build mengetahui tag <uses-library> dalam manifes. Pemeriksaan pesan error akan menunjukkan library 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 library yang hilang di properti libs modul. Jika ada, Soong biasanya menambahkan library tersebut secara otomatis, kecuali dalam kasus khusus berikut:

    • Library bukan library SDK (ditentukan sebagai java_library, bukan java_sdk_library).
    • Library memiliki nama library yang berbeda (dalam manifes) dari nama modulnya (dalam sistem build).

    Untuk memperbaiki masalah ini sementara, tambahkan provides_uses_lib: "<library-name>" dalam definisi library Android.bp. Untuk solusi jangka panjang, perbaiki masalah yang mendasarinya: konversi library ke library SDK, atau ganti nama modulnya.

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

Untuk modul Android.mk:

  1. Periksa apakah library memiliki nama library yang berbeda (dalam manifes) dari nama modulnya (dalam sistem build). Jika ya, perbaiki masalah ini sementara dengan menambahkan LOCAL_PROVIDES_USES_LIBRARY := <library-name> dalam file Android.mk library, atau tambahkan provides_uses_lib: "<library-name>" dalam file Android.bp library (kedua kasus ini memungkinkan karena modul Android.mk mungkin bergantung pada library Android.bp). Untuk solusi jangka panjang, perbaiki masalah yang mendasarinya: ganti nama modul library.

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

Error build: jalur library tidak diketahui

Jika sistem build tidak dapat menemukan jalur ke jar DEX <uses-library> (baik jalur waktu build di host maupun jalur penginstalan di perangkat), biasanya build akan gagal. Kegagalan menemukan jalur dapat menunjukkan bahwa library dikonfigurasi dengan cara yang tidak terduga. Perbaiki build untuk sementara 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

Laporkan bug untuk menyelidiki skenario yang tidak didukung.

Error build: dependensi library tidak ada

Upaya untuk menambahkan <uses-library> X dari manifes modul Y ke file build untuk Y dapat menyebabkan error build karena dependensi X tidak ada.

Berikut adalah contoh pesan error untuk modul Android.bp:

"Y" depends on undefined module "X"

Berikut adalah contoh pesan error 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 error tersebut adalah saat library diberi nama yang berbeda dengan nama modul yang sesuai dalam sistem build. Misalnya, jika entri manifes <uses-library> adalah com.android.X, tetapi nama modul library hanya X, hal ini akan menyebabkan error. Untuk mengatasi kasus ini, beri tahu sistem build bahwa modul bernama X menyediakan <uses-library> bernama com.android.X.

Berikut adalah contoh untuk library Android.bp (properti modul):

provides_uses_lib: “com.android.X”,

Berikut adalah contoh untuk library Android.mk (variabel modul):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Ketidakcocokan CLC waktu boot

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

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

Output dapat memiliki pesan dalam bentuk 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 lulus. 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 mengetahui secara pasti apa yang dimuat aplikasi saat runtime.

Konteks loader class

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

Karena <uses-library> dependensi adalah directed acyclic graph, dan tidak harus hierarki, CLC dapat berisi beberapa sub-hierarki untuk library yang sama. Dengan kata lain, CLC adalah grafik dependensi yang "dibuka" ke hierarki. Duplikasi hanya pada tingkat logis; loader class yang mendasarinya tidak diduplikasi (saat runtime, ada satu instance loader class untuk setiap library).

CLC menentukan urutan pencarian library saat menyelesaikan class Java yang digunakan oleh library atau aplikasi. Urutan pencarian penting karena library dapat berisi class duplikat, dan class diselesaikan ke kecocokan pertama.

CLC di perangkat (runtime)

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

Untuk setiap library yang digunakan, PackageManager akan mendapatkan semua <uses-library> dependensi (ditentukan sebagai tag dalam manifes library tersebut) dan menambahkan CLC bertingkat untuk setiap dependensi. Proses ini berlanjut secara rekursif hingga semua node daun dari hierarki CLC yang dibuat adalah library tanpa <uses-library> dependensi.

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

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

CLC di host (waktu build)

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

Dengan demikian, CLC waktu build yang digunakan oleh dexpreopt dan CLC runtime yang digunakan oleh PackageManager adalah hal yang sama, tetapi dihitung dengan dua cara yang berbeda.

CLC waktu build dan runtime harus bertepatan, jika tidak, kode yang dikompilasi AOT yang dibuat oleh dexpreopt akan ditolak. Untuk memeriksa kesamaan CLC waktu build dan runtime, compiler dex2oat mencatat CLC waktu build dalam file *.odex (di kolom classpath header file OAT). Untuk menemukan CLC yang disimpan, gunakan perintah ini:

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

Ketidakcocokan CLC waktu build dan runtime dilaporkan di logcat selama boot. Telusuri dengan perintah ini:

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

Ketidakcocokan akan berdampak buruk pada performa, karena memaksa library atau aplikasi untuk di-dexopt, atau berjalan tanpa pengoptimalan (misalnya, kode aplikasi mungkin perlu diekstrak dalam memori dari APK, yang merupakan operasi yang sangat mahal).

Library bersama dapat bersifat opsional atau wajib. Dari sudut pandang dexpreopt, library yang diperlukan harus ada pada waktu build (tidak adanya library tersebut adalah error build). Library opsional dapat ada atau tidak ada pada waktu build: jika ada, library tersebut akan ditambahkan ke CLC, diteruskan ke dex2oat, dan dicatat dalam file *.odex. Jika library opsional tidak ada, library tersebut akan dilewati dan tidak ditambahkan ke CLC. Jika ada ketidakcocokan antara status waktu build dan runtime (library opsional ada dalam satu kasus, tetapi tidak ada di kasus lainnya), CLC waktu build dan runtime tidak cocok dan kode yang dikompilasi akan ditolak.

Detail sistem build lanjutan (perbaikan manifes)

Terkadang, tag <uses-library> tidak ada dalam manifes sumber library atau aplikasi. Hal ini dapat terjadi, misalnya, jika salah satu dependensi transitif library atau aplikasi mulai menggunakan tag <uses-library> lain, dan manifes library atau aplikasi tidak diperbarui untuk menyertakannya.

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

Tidak semua tag <uses-library> dapat dihitung dengan cara ini, tetapi jika memungkinkan, sebaiknya izinkan Soong menambahkan entri manifes secara otomatis; hal ini lebih sedikit error dan menyederhanakan pemeliharaan. Misalnya, jika banyak aplikasi menggunakan library statis yang menambahkan dependensi <uses-library> baru, semua aplikasi harus diperbarui, yang sulit dipertahankan.