Android 12 memiliki perubahan sistem build pada 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 bersiap menghadapi kerusakan, dan ikuti resep di halaman ini untuk memperbaiki dan memitigasinya.
Dexpreopt adalah proses kompilasi terlebih dahulu library dan aplikasi Java. 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 class loader (CLC). Untuk menjamin kebenaran dexpreopt, CLC waktu build dan waktu proses harus bertepatan. CLC waktu build adalah yang digunakan oleh kompilator dex2oat pada waktu dexpreopt (dicatat dalam file ODEX), dan CLC waktu proses adalah konteks tempat kode yang telah dikompilasi sebelumnya dimuat di perangkat.
CLC waktu build dan waktu proses 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 terlihat jelas. Performa juga terpengaruh oleh pemeriksaan runtime untuk kelas duplikat.
Kasus penggunaan yang terpengaruh
Booting pertama adalah kasus penggunaan utama yang terpengaruh oleh perubahan ini: jika ART mendeteksi ketidakcocokan antara CLC waktu build dan waktu runtime, ART akan menolak artefak dexpreopt dan menjalankan dexopt. Untuk booting 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 pustaka dan aplikasi sendiri.
Perubahan yang dapat menyebabkan gangguan
Sistem build perlu mengetahui dependensi <uses-library>
sebelum
membuat aturan build dexpreopt. Namun, sistem tidak dapat mengakses manifes secara langsung
dan membaca tag <uses-library>
di dalamnya, karena sistem build tidak diizinkan untuk membaca file arbitrer saat
membuat aturan build (karena alasan performa). Selain itu, manifes dapat dikemas di dalam APK atau prebuilt. Oleh karena itu, informasi <uses-library>
harus ada dalam file build (Android.bp
atau Android.mk
).
Sebelumnya, ART menggunakan solusi yang mengabaikan dependensi library bersama (dikenal
sebagai &-classpath
). Hal ini tidak aman dan menyebabkan bug yang tidak terlihat, sehingga solusi tersebut dihapus di Android 12.
Akibatnya, modul Java yang tidak memberikan informasi <uses-library>
yang benar dalam file build-nya dapat menyebabkan kerusakan build (disebabkan oleh
ketidakcocokan CLC waktu build) atau regresi waktu booting pertama (disebabkan oleh
ketidakcocokan CLC waktu booting yang diikuti dengan dexopt).
Jalur migrasi
Ikuti langkah-langkah berikut untuk memperbaiki build yang rusak:
Menonaktifkan pemeriksaan waktu build secara global untuk produk tertentu dengan menyetel
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
di makefile produk. Hal ini memperbaiki error build (kecuali untuk kasus khusus, yang tercantum di bagian Memperbaiki kerusakan). Namun, ini adalah solusi sementara, dan dapat menyebabkan ketidakcocokan CLC waktu booting yang diikuti dengan dexopt.
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 diAndroid.bp
, atau diAndroid.mk
.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 booting.
Aktifkan kembali pemeriksaan waktu pembuatan secara global dengan membatalkan setelan
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
yang ditetapkan di Langkah 1; build tidak akan gagal setelah perubahan ini (karena langkah 2 dan 3).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: CLC tidak cocok
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 tag <uses-library>
dalam manifes
dengan informasi <uses-library>
dalam file build. Jika pemeriksaan gagal,
errornya 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. Sebagai gantinya, kegagalan pemeriksaan akan membuat sistem build menurunkan filter compiler dex2oat keverify
di dexpreopt, yang menonaktifkan kompilasi AOT sepenuhnya untuk modul ini. - Untuk perbaikan command line global yang cepat, gunakan variabel lingkungan
RELAX_USES_LIBRARY_CHECK=true
. Perintah ini memiliki efek yang sama denganPRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, tetapi ditujukan untuk digunakan di command line. Variabel lingkungan menggantikan variabel produk. - Untuk solusi perbaikan akar masalah error, buat sistem build menyadari tag
<uses-library>
dalam manifes. Pemeriksaan pesan error menunjukkan library mana yang menyebabkan masalah (seperti halnya pemeriksaanAndroidManifest.xml
atau manifes di dalam APK yang dapat diperiksa dengan `aapt dump badging $APK | grep uses-library
`).
Untuk Android.bp
modul:
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
, bukanjava_sdk_library
). - Library memiliki nama library yang berbeda (dalam manifes) dengan nama modulnya (dalam sistem build).
Untuk memperbaikinya sementara, tambahkan
provides_uses_lib: "<library-name>"
dalam definisi libraryAndroid.bp
. Untuk solusi jangka panjang, perbaiki masalah yang mendasarinya: konversi library menjadi library SDK, atau ganti nama modulnya.- Library bukan library SDK (ditentukan sebagai
Jika langkah sebelumnya tidak memberikan solusi, tambahkan
uses_libs: ["<library-module-name>"]
untuk library yang diperlukan, atauoptional_uses_libs: ["<library-module-name>"]
untuk library opsional ke definisiAndroid.bp
modul. Properti ini menerima daftar nama modul. Urutan relatif library dalam daftar harus sama dengan urutan dalam manifes.
Untuk Android.mk
modul:
Periksa apakah library memiliki nama library yang berbeda (dalam manifes) dari nama modulnya (dalam sistem build). Jika ya, perbaiki masalah ini untuk sementara dengan menambahkan
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
di fileAndroid.mk
library, atau tambahkanprovides_uses_lib: "<library-name>"
di fileAndroid.bp
library (kedua kasus ini mungkin terjadi karena modulAndroid.mk
mungkin bergantung pada libraryAndroid.bp
). Untuk solusi jangka panjang, perbaiki masalah yang mendasarinya: ganti nama modul library.Tambahkan
LOCAL_USES_LIBRARIES := <library-module-name>
untuk library yang diperlukan; tambahkanLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
untuk library opsional ke definisiAndroid.mk
modul. Properti ini menerima daftar nama modul. Urutan relatif library dalam daftar harus sama dengan urutan dalam manifes.
Error build: jalur library tidak diketahui
Jika sistem build tidak dapat menemukan jalur ke file jar DEX <uses-library>
(baik jalur waktu build di host maupun jalur penginstalan di perangkat), build biasanya 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 berbeda dengan
modul yang sesuai di sistem build. Misalnya, jika entri
<uses-library>
manifes adalah com.android.X
, tetapi nama modul library hanya
X
, hal ini akan menyebabkan error. Untuk menyelesaikan kasus ini, beri tahu sistem build bahwa
modul bernama X
menyediakan <uses-library>
bernama com.android.X
.
Berikut 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 booting
Saat booting pertama, telusuri logcat untuk menemukan pesan terkait 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 ditampilkan 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 rusak. Untuk memperbaikinya, pastikan pemeriksaan waktu build untuk modul lulus. Jika cara tersebut tidak berhasil, maka 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 secara pasti apa yang dimuat aplikasi saat runtime.
Konteks pemuat kelas
CLC adalah struktur seperti hierarki yang menjelaskan hierarki class loader. Sistem
build menggunakan CLC dalam arti sempit (hanya mencakup library, bukan APK atau
pemuat class kustom): ini adalah hierarki library yang merepresentasikan penutupan
transitif semua dependensi <uses-library>
dari library atau aplikasi. Elemen tingkat
teratas CLC adalah dependensi <uses-library>
langsung yang ditentukan
dalam manifes (classpath). Setiap node pohon CLC adalah node
<uses-library>
yang mungkin memiliki sub-node <uses-library>
sendiri.
Karena dependensi <uses-library>
adalah grafik asiklik terarah, dan tidak
harus berupa pohon, CLC dapat berisi beberapa subpohon untuk library yang sama. Dengan
kata lain, CLC adalah grafik dependensi yang "dibuka" menjadi hierarki. Duplikasi
hanya ada di tingkat logis; pemuat class yang mendasarinya tidak
diduplikasi (saat runtime, ada satu instance pemuat 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 (waktu proses)
PackageManager
(di frameworks/base
) membuat CLC untuk memuat modul Java di perangkat. Plugin ini menambahkan library yang tercantum dalam tag <uses-library>
di manifes modul sebagai elemen CLC tingkat teratas.
Untuk setiap library yang digunakan, PackageManager
akan mendapatkan semua dependensi <uses-library>
-nya (ditentukan sebagai tag dalam manifes library tersebut) dan menambahkan CLC bertingkat untuk setiap dependensi. Proses ini berlanjut secara rekursif hingga semua
node daun dari pohon CLC yang dibuat adalah library tanpa <uses-library>
dependensi.
PackageManager
hanya mengetahui library bersama. Definisi bersama dalam
penggunaan ini berbeda dari arti biasanya (seperti dalam 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 lain yang digunakan saat runtime, dan ditentukan dalam
tag <uses-library>
di manifesnya).
Dengan kata lain, ada dua sumber informasi yang memungkinkan PackageManager
membuat CLC saat runtime: tag <uses-library>
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 dilakukan di perangkat, dexopt memiliki informasi yang sama dengan PackageManager
(dependensi library bersama dan manifes).
Namun, Dexpreopt dilakukan 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 waktu proses yang digunakan oleh
PackageManager
adalah hal yang sama, tetapi dihitung dengan dua cara yang berbeda.
CLC waktu build dan waktu proses harus bertepatan, jika tidak, kode yang dikompilasi AOT
yang dibuat oleh dexpreopt akan ditolak. Untuk memeriksa kesamaan CLC waktu build dan
waktu proses, 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 waktu proses dilaporkan di logcat selama booting. Telusuri dengan perintah ini:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Ketidakcocokan berdampak buruk pada performa, karena memaksa library atau aplikasi untuk dioptimalkan dex, 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 (jika
tidak ada, akan terjadi error build). Library opsional dapat ada atau tidak ada
pada waktu build: jika ada, library tersebut ditambahkan ke CLC, diteruskan ke dex2oat, dan
dicatat dalam file *.odex
. Jika tidak ada library opsional, library tersebut akan dilewati
dan tidak ditambahkan ke CLC. Jika ada ketidakcocokan antara status waktu build dan
waktu proses (library opsional ada dalam satu kasus, tetapi tidak ada dalam kasus lain),
maka CLC waktu build dan waktu proses 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 diupdate 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
bergantung lagi secara transitif melalui library lain.
Tidak semua tag <uses-library>
dapat dihitung dengan cara ini, tetapi jika memungkinkan, sebaiknya biarkan Soong menambahkan entri manifes secara otomatis; hal ini lebih tidak rentan terhadap kesalahan dan menyederhanakan pemeliharaan. Misalnya, saat banyak aplikasi menggunakan library statis yang menambahkan dependensi <uses-library>
baru, semua aplikasi harus diupdate, sehingga sulit untuk dipertahankan.