Android 12 có các thay đổi về hệ thống xây dựng đối với quá trình biên dịch AOT
của các tệp DEX (dexpreopt) cho các mô-đun Java có phần phụ thuộc
<uses-library>
. Trong một số trường hợp, những thay đổi này với hệ thống xây dựng có thể bị lỗi
bản dựng. Hãy sử dụng trang này để chuẩn bị cho các sự cố và làm theo các công thức trên trang này để khắc phục và giảm thiểu các sự cố đó.
Dexpreopt là quá trình biên dịch trước các thư viện Java và của chúng tôi. Dexpreopt xảy ra trên máy chủ tại thời gian xây dựng (trái ngược với dexopt, mà diễn ra trên thiết bị). Cấu trúc của các phần phụ thuộc thư viện dùng chung mà mô-đun Java (thư viện hoặc ứng dụng) sử dụng được gọi là ngữ cảnh trình tải lớp (CLC). Để đảm bảo tính chính xác của dexpreopt, CLC thời gian xây dựng và thời gian chạy phải trùng khớp. CLC tại thời gian xây dựng là nội dung mà trình biên dịch dex2oat sử dụng tại thời điểm dexpreopt (được ghi lại trong các tệp ODEX) và CLC tại thời gian chạy là ngữ cảnh trong đó mã được biên dịch trước được tải trên thiết bị.
Các CLC tại thời gian xây dựng và thời gian chạy này phải trùng khớp vì cả lý do về độ chính xác và hiệu suất. Để đảm bảo tính chính xác, bạn cần xử lý các lớp trùng lặp. Nếu các phần phụ thuộc của thư viện dùng chung trong thời gian chạy sẽ khác với các phần phụ thuộc được dùng cho biên dịch mã, một số lớp có thể được giải quyết theo cách khác, gây ra thời gian chạy. Hiệu suất cũng bị ảnh hưởng bởi các hoạt động kiểm tra thời gian chạy để tìm các lớp trùng lặp.
Các trường hợp sử dụng bị ảnh hưởng
Lần khởi động đầu tiên là trường hợp sử dụng chính chịu ảnh hưởng của những thay đổi sau: nếu ART phát hiện sự không khớp giữa CLC trong thời gian xây dựng và thời gian chạy, công cụ này sẽ từ chối dexpreopt cấu phần phần mềm và chạy dexopt. Đối với những lần khởi động tiếp theo, điều này không có vấn đề gì vì ứng dụng có thể được chuyển sang tệp dex trong nền và được lưu trữ trên ổ đĩa.
Các khu vực bị ảnh hưởng của Android
Điều này ảnh hưởng đến tất cả ứng dụng và thư viện Java có phần phụ thuộc thời gian chạy trên các thư viện Java khác. Android có hàng nghìn ứng dụng và hàng trăm ứng dụng trong số đó sử dụng thư viện dùng chung. Các đối tác cũng bị ảnh hưởng vì họ có thư viện và ứng dụng.
Thay đổi về điểm chèn quảng cáo
Hệ thống xây dựng cần biết các phần phụ thuộc <uses-library>
trước khi có
tạo các quy tắc xây dựng dexpreopt. Tuy nhiên, hệ thống này không thể truy cập trực tiếp vào tệp kê khai và đọc các thẻ <uses-library>
trong tệp kê khai đó, vì hệ thống xây dựng không được phép đọc các tệp tuỳ ý khi tạo quy tắc xây dựng (vì lý do hiệu suất). Hơn nữa, tệp kê khai có thể
được đóng gói bên trong APK hoặc thư viện tạo sẵn. Do đó, thông tin <uses-library>
phải có trong tệp bản dựng (Android.bp
hoặc Android.mk
).
Trước đây, ART đã sử dụng một giải pháp bỏ qua các phần phụ thuộc của thư viện dùng chung (đã biết
làm &-classpath
). Điều này không an toàn và gây ra các lỗi nhỏ, vì vậy giải pháp
đã bị xoá trong Android 12.
Do đó, các mô-đun Java không cung cấp <uses-library>
chính xác
thông tin trong các tệp bản dựng của chúng có thể gây lỗi bản dựng (do
CLC thời gian xây dựng không khớp) hoặc số lần hồi quy trong lần khởi động đầu tiên (do thời gian khởi động)
CLC không khớp, theo sau là dexopt).
Lộ trình di chuyển
Hãy làm theo các bước sau để khắc phục bản dựng bị hỏng:
Tắt tính năng kiểm tra thời gian tạo bản dựng trên toàn cầu cho một sản phẩm cụ thể bằng cách thiết lập
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
trong tệp makefile của sản phẩm. Thao tác này sẽ khắc phục các lỗi bản dựng (ngoại trừ các trường hợp đặc biệt, được liệt kê trong phần Khắc phục sự cố). Tuy nhiên, đây là giải pháp tạm thời và có thể khiến CLC không khớp trong thời gian khởi động tiếp theo là dexopt.
Khắc phục các mô-đun không thành công trước khi bạn tắt tính năng kiểm tra thời gian xây dựng trên toàn hệ thống bằng cách thêm thông tin
<uses-library>
cần thiết vào tệp bản dựng của các mô-đun đó (xem phần Khắc phục sự cố để biết thông tin chi tiết). Đối với hầu hết các mô-đun, bạn cần thêm một vài dòng trongAndroid.bp
hoặc trongAndroid.mk
.Tắt tính năng kiểm tra thời gian xây dựng và dexpreopt cho các trường hợp có vấn đề, trên cơ sở mỗi mô-đun. Tắt dexpreopt để bạn không lãng phí thời gian xây dựng và dung lượng lưu trữ cho các cấu phần phần mềm bị từ chối khi khởi động.
Bật lại tính năng kiểm tra thời gian xây dựng trên toàn cục bằng cách huỷ đặt
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
đã đặt ở Bước 1; bản dựng sẽ không bị lỗi sau thay đổi này (do các bước 2 và 3).Khắc phục từng mô-đun mà bạn đã tắt ở Bước 3, sau đó bật lại tính năng dexpreopt và tính năng kiểm tra
<uses-library>
. Báo cáo lỗi nếu cần.
Các bước kiểm tra <uses-library>
tại thời điểm tạo bản dựng được thực thi trong Android 12.
Khắc phục sự cố
Các phần sau đây sẽ hướng dẫn bạn cách khắc phục các loại lỗi cụ thể.
Lỗi bản dựng: CLC không khớp
Hệ thống xây dựng sẽ kiểm tra tính nhất quán trong thời gian xây dựng giữa thông tin trong
Tệp Android.bp
hoặc Android.mk
và tệp kê khai. Hệ thống xây dựng không thể đọc
tệp kê khai, nhưng có thể tạo các quy tắc xây dựng để đọc tệp kê khai (trích xuất
từ một APK nếu cần) và so sánh các thẻ <uses-library>
trong tệp kê khai
dựa vào thông tin <uses-library>
trong tệp bản dựng. Nếu quy trình kiểm tra không thành công, lỗi sẽ có dạng như sau:
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
Như thông báo lỗi cho thấy, có nhiều giải pháp, tuỳ thuộc vào tính cấp bách:
- Đối với khắc phục tạm thời trên toàn sản phẩm, hãy đặt
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
trong tệp makefile của sản phẩm. Quy trình kiểm tra tính nhất quán tại thời điểm tạo bản dựng vẫn được thực hiện, nhưng việc kiểm tra không thành công không có nghĩa là bản dựng không thành công. Thay vào đó, nếu không kiểm tra được, hệ thống xây dựng sẽ hạ cấp bộ lọc trình biên dịch dex2oat thànhverify
trong dexpreopt, tắt tính năng biên dịch AOT hoàn toàn cho mô-đun này. - Để sửa nhanh dòng lệnh toàn cục, hãy sử dụng biến môi trường
RELAX_USES_LIBRARY_CHECK=true
. Phương thức này có tác dụng tương tự nhưPRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, nhưng được dùng trên dòng lệnh. Biến môi trường sẽ ghi đè biến sản phẩm. - Để có giải pháp khắc phục nguyên nhân gốc rễ lỗi này, hãy cho hệ thống xây dựng biết về các thẻ
<uses-library>
trong tệp kê khai. Việc kiểm tra thông báo lỗi sẽ cho biết thư viện nào gây ra sự cố (cũng như việc kiểm traAndroidManifest.xml
hoặc tệp kê khai bên trong tệp APK có thể được kiểm tra bằng `aapt dump badging $APK | grep uses-library
`).
Đối với các mô-đun Android.bp
:
Tìm thư viện bị thiếu trong thuộc tính
libs
của mô-đun. Nếu ở đó, Soong thường tự động thêm các thư viện như vậy, ngoại trừ trong các trường hợp đặc biệt:- Thư viện này không phải là thư viện SDK (được xác định là
java_library
thay vìjava_sdk_library
). - Thư viện có tên thư viện khác (trong tệp kê khai) với mô-đun của thư viện tên (trong hệ thống xây dựng).
Để khắc phục vấn đề này tạm thời, hãy thêm
provides_uses_lib: "<library-name>"
vào Định nghĩa thư việnAndroid.bp
. Để có giải pháp lâu dài, hãy khắc phục vấn đề cơ bản: chuyển đổi thư viện thành thư viện SDK hoặc đổi tên mô-đun của thư viện.- Thư viện này không phải là thư viện SDK (được xác định là
Nếu bước trước đó không đưa ra giải pháp, hãy thêm
uses_libs: ["<library-module-name>"]
cho các thư viện bắt buộc, hoặcoptional_uses_libs: ["<library-module-name>"]
cho các thư viện không bắt buộc để định nghĩaAndroid.bp
của mô-đun. Các thuộc tính này chấp nhận danh sách tên mô-đun. Thứ tự tương đối của các thư viện trong danh sách phải giống với thứ tự trong tệp kê khai.
Đối với các mô-đun Android.mk
:
Kiểm tra xem thư viện có tên thư viện khác (trong tệp kê khai) với tên thư viện hay không tên mô-đun (trong hệ thống xây dựng). Nếu có, hãy khắc phục vấn đề này tạm thời bằng cách thêm
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
trong tệpAndroid.mk
của thư viện hoặc thêmprovides_uses_lib: "<library-name>"
vàoAndroid.bp
tệp của thư viện (cả hai trường hợp đều có thể xảy ra vì mô-đunAndroid.mk
có thể phụ thuộc vào thư việnAndroid.bp
). Để có giải pháp lâu dài, hãy khắc phục vấn đề cơ bản: đổi tên mô-đun thư viện.Thêm
LOCAL_USES_LIBRARIES := <library-module-name>
cho các thư viện bắt buộc; thêmLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
cho các thư viện không bắt buộc vào định nghĩaAndroid.mk
của mô-đun. Các thuộc tính này chấp nhận danh sách tên mô-đun. Thứ tự tương đối của các thư viện trên danh sách phải giống như trong tệp kê khai.
Lỗi bản dựng: đường dẫn thư viện không xác định
Nếu hệ thống xây dựng không tìm thấy đường dẫn đến tệp DEX <uses-library>
(có thể là
đường dẫn thời gian xây dựng trên máy chủ lưu trữ hoặc đường dẫn cài đặt trên thiết bị), thường thì lỗi
bản dựng. Việc không tìm thấy đường dẫn có thể cho biết rằng thư viện được định cấu hình trong
theo cách ngoài dự kiến. Tạm thời khắc phục bản dựng bằng cách tắt dexpreopt cho mô-đun có vấn đề.
Android.bp (thuộc tính mô-đun):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (biến mô-đun):
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
Gửi lỗi để điều tra mọi trường hợp không được hỗ trợ.
Lỗi bản dựng: thiếu phần phụ thuộc thư viện
Thêm <uses-library>
X từ tệp kê khai của mô-đun Y vào bản dựng
cho Y có thể dẫn đến lỗi bản dựng do thiếu phần phụ thuộc X.
Đây là thông báo lỗi mẫu cho các mô-đun Android.bp:
"Y" depends on undefined module "X"
Đây là thông báo lỗi mẫu cho các mô-đun 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
Một nguyên nhân phổ biến gây ra các lỗi như vậy là khi tên thư viện khác với tên mô-đun tương ứng trong hệ thống xây dựng. Ví dụ: nếu tệp kê khai
Mục nhập <uses-library>
là com.android.X
, nhưng tên của mô-đun thư viện là
chỉ X
, vì điều này sẽ gây ra lỗi. Để giải quyết trường hợp này, hãy cho hệ thống xây dựng biết rằng mô-đun có tên X
cung cấp <uses-library>
có tên com.android.X
.
Đây là ví dụ về thư viện Android.bp
(thuộc tính mô-đun):
provides_uses_lib: “com.android.X”,
Đây là ví dụ về thư viện Android.mk (biến mô-đun):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
CLC thời gian khởi động không khớp
Ở lần khởi động đầu tiên, hãy tìm kiếm logcat để tìm thông báo liên quan đến sự không khớp CLC, như minh hoạ bên dưới:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
Kết quả đầu ra có thể có các thông báo có dạng như sau:
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
Nếu bạn nhận được cảnh báo CLC không khớp, hãy tìm lệnh dexopt để tìm lỗi . Để khắc phục vấn đề này, hãy đảm bảo rằng quy trình kiểm tra thời gian xây dựng cho mô-đun đã vượt qua. Nếu cách này không hiệu quả, thì trường hợp của bạn có thể là một trường hợp đặc biệt mà hệ thống xây dựng không hỗ trợ (chẳng hạn như một ứng dụng tải một APK khác, chứ không phải một thư viện). Hệ thống xây dựng không xử lý tất cả các trường hợp vì tại thời điểm xây dựng, không thể biết chắc chắn ứng dụng sẽ tải nội dung gì trong thời gian chạy.
Ngữ cảnh của trình tải lớp
CLC là một cấu trúc dạng cây mô tả hệ phân cấp trình tải lớp. Hệ thống xây dựng sử dụng CLC theo nghĩa hẹp (chỉ bao gồm các thư viện, không phải APK hoặc trình tải lớp tuỳ chỉnh): đây là một cây thư viện đại diện cho việc đóng transitive của tất cả các phần phụ thuộc <uses-library>
của một thư viện hoặc ứng dụng. Các phần tử cấp cao nhất của CLC là các phần phụ thuộc <uses-library>
trực tiếp được chỉ định trong tệp kê khai (lớp đường dẫn). Mỗi nút của cây CLC là một
Nút <uses-library>
có thể có nút phụ <uses-library>
riêng.
Vì các phần phụ thuộc <uses-library>
là một biểu đồ không tuần hoàn có hướng và không nhất thiết phải là một cây, nên CLC có thể chứa nhiều cây con cho cùng một thư viện. Ngang bằng
nói cách khác, CLC là biểu đồ phần phụ thuộc "unfolded" thành một cái cây. Nội dung trùng lặp
chỉ trên mức độ logic; các trình tải lớp cơ bản thực tế không
trùng lặp (trong thời gian chạy, có một phiên bản trình tải lớp duy nhất cho mỗi thư viện).
CLC xác định thứ tự tra cứu của thư viện khi phân giải lớp Java mà thư viện hoặc ứng dụng. Thứ tự tìm kiếm rất quan trọng vì các thư viện có thể chứa các lớp trùng lặp và lớp này được giải quyết cho lớp trùng khớp đầu tiên.
CLC trên thiết bị (thời gian chạy)
PackageManager
(trong frameworks/base
) tạo một CLC để tải mô-đun Java trên thiết bị. Tệp này thêm các thư viện được liệt kê trong thẻ <uses-library>
trong tệp kê khai của mô-đun dưới dạng phần tử CLC cấp cao nhất.
Đối với mỗi thư viện đã sử dụng, PackageManager
sẽ nhận được tất cả <uses-library>
phần phụ thuộc (được chỉ định dưới dạng thẻ trong tệp kê khai của thư viện đó) và thêm một
CLC lồng ghép cho mỗi phần phụ thuộc. Quá trình này tiếp tục đệ quy cho đến khi tất cả các nút lá của cây CLC được tạo đều là thư viện không có phần phụ thuộc <uses-library>
.
PackageManager
chỉ nhận biết được thư viện dùng chung. Định nghĩa về "được chia sẻ trong"
cách sử dụng này khác với ý nghĩa thông thường của nó (như trong dạng chia sẻ và tĩnh). Trong Android, thư viện dùng chung Java là những thư viện được liệt kê trong cấu hình XML được cài đặt trên thiết bị (/system/etc/permissions/platform.xml
). Mỗi mục nhập chứa tên của một thư viện dùng chung, đường dẫn đến tệp jar DEX và danh sách các phần phụ thuộc (các thư viện dùng chung khác mà thư viện này sử dụng trong thời gian chạy và chỉ định trong thẻ <uses-library>
trong tệp kê khai).
Nói cách khác, có hai nguồn thông tin cho phép PackageManager
tạo CLC trong thời gian chạy: thẻ <uses-library>
trong tệp kê khai và các phần phụ thuộc thư viện dùng chung trong cấu hình XML.
CLC trên máy chủ (thời gian tạo bản dựng)
CLC không chỉ cần thiết khi tải thư viện hoặc ứng dụng mà còn cần thiết khi biên dịch thư viện hoặc ứng dụng. Quá trình biên dịch có thể diễn ra trên thiết bị (dexopt) hoặc trong quá trình tạo bản dựng (dexpreopt). Vì quá trình dexopt diễn ra trên thiết bị, nên quá trình này
dưới dạng PackageManager
(tệp kê khai và phần phụ thuộc thư viện dùng chung).
Tuy nhiên, Dexpreopt diễn ra trên máy chủ và trong một môi trường hoàn toàn khác
môi trường và phải nhận cùng thông tin từ hệ thống xây dựng.
Do đó, CLC trong thời gian xây dựng được dexpreopt và CLC thời gian chạy sử dụng được sử dụng
PackageManager
là giống nhau, nhưng được tính theo hai cách khác nhau.
CLC thời gian xây dựng và thời gian chạy phải trùng nhau, nếu không mã do AOT biên dịch
do dexpreopt tạo ra bị từ chối. Để kiểm tra sự bằng nhau của CLC tại thời gian xây dựng và thời gian chạy, trình biên dịch dex2oat sẽ ghi lại CLC tại thời gian xây dựng trong các tệp *.odex
(trong trường classpath
của tiêu đề tệp OAT). Để tìm CLC đã lưu trữ, hãy sử dụng lệnh sau:
oatdump --oat-file=<FILE> | grep '^classpath = '
Lỗi không khớp trong CLC thời gian xây dựng và thời gian chạy được báo cáo trong logcat trong quá trình khởi động. Tìm kiếm cho máy chủ đó bằng lệnh sau:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Việc không khớp sẽ ảnh hưởng xấu đến hiệu suất, vì nó buộc thư viện hoặc ứng dụng phải được chuyển đổi sang tệp dex hoặc chạy mà không cần tối ưu hoá (ví dụ: mã của ứng dụng có thể cần được trích xuất trong bộ nhớ từ tệp APK, đây là một thao tác rất tốn kém).
Thư viện dùng chung có thể là không bắt buộc hoặc bắt buộc. Từ quan điểm dexpreopt, thư viện bắt buộc phải có tại thời điểm tạo bản dựng (nếu không có thì đó là lỗi bản dựng). Thư viện không bắt buộc có thể có hoặc không có tại thời điểm tạo bản dựng: nếu có, thư viện này sẽ được thêm vào CLC, chuyển đến dex2oat và được ghi lại trong tệp *.odex
. Nếu không có thư viện không bắt buộc, thư viện đó sẽ bị bỏ qua và không được thêm vào CLC. Nếu có sự không khớp giữa trạng thái thời gian xây dựng và thời gian chạy (thư viện không bắt buộc có trong một trường hợp nhưng không có trong trường hợp khác), thì CLC thời gian xây dựng và thời gian chạy sẽ không khớp và mã được biên dịch sẽ bị từ chối.
Thông tin chi tiết nâng cao về hệ thống xây dựng (trình sửa tệp kê khai)
Đôi khi, thẻ <uses-library>
bị thiếu trong tệp kê khai nguồn của
thư viện hoặc ứng dụng. Điều này có thể xảy ra, chẳng hạn như nếu một trong các phần phụ thuộc bắc cầu
của thư viện hoặc ứng dụng bắt đầu sử dụng một thẻ <uses-library>
khác và
thư viện hoặc tệp kê khai của ứng dụng không được cập nhật để bao gồm thư viện hoặc tệp kê khai đó.
Soong có thể tự động tính toán một số thẻ <uses-library>
bị thiếu cho một thư viện hoặc ứng dụng nhất định, dưới dạng thư viện SDK trong phần đóng phần phụ thuộc bắc cầu của thư viện hoặc ứng dụng. Phần đóng này là cần thiết vì thư viện (hoặc ứng dụng) có thể phụ thuộc vào một thư viện tĩnh phụ thuộc vào thư viện SDK và có thể lại phụ thuộc bắc cầu thông qua một thư viện khác.
Không phải tất cả các thẻ <uses-library>
đều có thể được tính theo cách này, nhưng khi có thể,
nên ưu tiên cho phép Soong tự động thêm các mục nhập tệp kê khai; ít hơn
dễ gặp lỗi và đơn giản hoá việc bảo trì. Ví dụ: khi nhiều ứng dụng dùng
thêm phần phụ thuộc <uses-library>
mới, thì tất cả ứng dụng đều phải
mới nhất, do đó rất khó duy trì.