Dexpreopt và & lt; use-library & gt; Séc

Android 12 có các thay đổi về hệ thống xây dựng đối với việc biên dịch AOT 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 về hệ thống bản dựng này có thể làm hỏng bản dựng. 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 chúng.

Dexpreopt là quá trình biên dịch trước các thư viện và ứng dụng Java. Dexpreopt xảy ra trên máy chủ tại thời điểm xây dựng (ngược lại với dexopt xảy 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 được mô-đun Java (thư viện hoặc ứng dụng) sử dụng được gọi là bối cảnh trình nạp lớp (CLC) của nó. Để đả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 nhau. CLC thời gian xây dựng là những gì trình biên dịch dex2oat sử dụng tại thời điểm dexpreopt (nó được ghi trong tệp ODEX) và CLC thời gian chạy là bối cảnh trong đó mã biên dịch trước được tải trên thiết bị.

Các CLC thời gian xây dựng và thời gian chạy này phải trùng khớp vì lý do cả về tính chính xác và hiệu suất. Để chính xác, cần phải 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 khác với các phần phụ thuộc được sử dụng để biên dịch thì một số lớp có thể được giải quyết khác nhau, gây ra các lỗi nhỏ trong thời gian chạy. Hiệu suất cũng bị ảnh hưởng bởi việc kiểm tra thời gian chạy đối với 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 bị ảnh hưởng bởi những thay đổi này: nếu ART phát hiện sự không khớp giữa CLC thời gian xây dựng và thời gian chạy, thì ART sẽ từ chối các tạo phẩm dexpreopt và thay vào đó chạy dexopt. Đối với những lần khởi động tiếp theo, điều này không sao cả vì các ứng dụng có thể được giải mã ở chế độ 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ả cá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 riêng.

Thay đổi ngắt quãng

Hệ thống xây dựng cần biết các phần phụ thuộc <uses-library> trước khi tạo quy tắc xây dựng dexpreopt. Tuy nhiên, nó không thể truy cập trực tiếp vào tệp kê khai và đọc các thẻ <uses-library> trong đó vì hệ thống xây dựng không được phép đọc các tệp tùy ý 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 bản dựng 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 giải pháp bỏ qua các phần phụ thuộc của thư viện dùng chung (được gọi là &-classpath ). Điều này không an toàn và gây ra nhiều lỗi nhỏ nên giải pháp này đã bị xóa trong Android 12.

Kết quả là, các mô-đun Java không cung cấp thông tin <uses-library> chính xác trong tệp bản dựng của chúng có thể gây ra sự cố bản dựng (do CLC thời gian xây dựng không khớp) hoặc hồi quy thời gian khởi động lần đầu (do CLC thời gian khởi động gây ra). không khớp, theo sau là dexopt).

Đường dẫn di chuyển

Hãy làm theo các bước sau để khắc phục bản dựng bị hỏng:

  1. Vô hiệu hóa toàn bộ việc kiểm tra thời gian xây dựng cho một sản phẩm cụ thể bằng cách cài đặt

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    trong tệp tạo sản phẩm. Điều này sửa 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 chỉ là giải pháp tạm thời và có thể khiến CLC thời gian khởi động không khớp, sau đó là dexopt.

  2. Sửa các mô-đun không thành công trước khi bạn vô hiệu hóa toàn bộ quá trình kiểm tra thời gian xây dự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 chúng (xem Khắc phục sự cố để biết chi tiết). Đối với hầu hết các mô-đun, điều này yêu cầu thêm một vài dòng trong Android.bp hoặc trong Android.mk .

  3. Tắt tính năng kiểm tra thời gian xây dựng và giải mã các trường hợp có vấn đề, trên cơ sở từng mô-đun. Tắt dxpreopt để bạn không lãng phí thời gian xây dựng và dung lượng lưu trữ trên các tạo phẩm bị từ chối khi khởi động.

  4. Kích hoạt lại toàn bộ kiểm tra thời gian xây dựng bằng cách bỏ đặt PRODUCT_BROKEN_VERIFY_USES_LIBRARIES đã được đặt ở Bước 1; bản dựng sẽ không bị lỗi sau thay đổi này (vì bước 2 và 3).

  5. Hãy sửa từng mô-đun mà bạn đã tắt ở Bước 3, sau đó bật lại dexpreopt và kiểm tra <uses-library> . Tập tin lỗi nếu cần thiết.

Việc kiểm tra <uses-library> tại thời điểm xây dựng được thực thi trong Android 12.

Sửa chữa sự cố

Các phần sau đây cho bạn biết cách khắc phục các loại hỏng hóc cụ thể.

Lỗi xây dựng: CLC không khớp

Hệ thống xây dựng thực hiện kiểm tra tính mạch lạc 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ệp kê khai từ APK nếu cần) và so sánh các thẻ <uses-library> trong tệp kê khai với thông tin <uses-library> trong các tập tin xây dựng. Nếu kiểm tra không thành công, lỗi sẽ trông như thế này:

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 gợi ý, có nhiều giải pháp, tùy thuộc vào mức độ khẩn cấp:

  • Để khắc phục tạm thời cho toàn bộ sản phẩm , hãy đặt PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true trong tệp tạo sản phẩm. Việc kiểm tra tính mạch lạc trong thời gian xây dựng vẫn được thực hiện nhưng lỗi kiểm tra không có nghĩa là bản dựng bị lỗi. Thay vào đó, lỗi kiểm tra khiến hệ thống xây dựng hạ cấp bộ lọc trình biên dịch dex2oat để verify trong dexpreopt, điều này vô hiệu hóa hoàn toàn quá trình biên dịch AOT cho mô-đun này.
  • Để khắc phục nhanh chóng, toàn cục dòng lệnh , hãy sử dụng biến môi trường RELAX_USES_LIBRARY_CHECK=true . Nó có tác dụng tương tự như PRODUCT_BROKEN_VERIFY_USES_LIBRARIES nhưng được thiết kế để sử 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ễ , hãy làm cho hệ thống xây dựng nhận biết 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 tra AndroidManifest.xml hoặc tệp kê khai bên trong APK có thể được kiểm tra bằng ` aapt dump badging $APK | grep uses-library `).

Đối với mô-đun Android.bp :

  1. Tìm thư viện bị thiếu trong thuộc tính libs của mô-đun. Nếu có, Soong thường tự động thêm các thư viện như vậy, ngoại trừ những trường hợp đặc biệt sau:

    • Thư viện không phải là thư viện SDK (nó được định nghĩa là java_library chứ không phải java_sdk_library ).
    • Thư viện có tên thư viện (trong tệp kê khai) khác với tên mô-đun của nó (trong hệ thống xây dựng).

    Để khắc phục sự cố này tạm thời, hãy thêm provides_uses_lib: "<library-name>" vào định nghĩa thư viện Android.bp . Để có giải pháp lâu dài, hãy khắc phục sự cố cơ bản: chuyển đổi thư viện thành thư viện SDK hoặc đổi tên mô-đun của nó.

  2. Nếu bước trước không cung cấp giải pháp, hãy thêm uses_libs: ["<library-module-name>"] cho các thư viện bắt buộc hoặc optional_uses_libs: ["<library-module-name>"] cho các thư viện tùy chọn cho Android.bp định nghĩa 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 mô-đun Android.mk :

  1. Kiểm tra xem thư viện có tên thư viện khác (trong tệp kê khai) với tên mô-đun của nó (trong hệ thống xây dựng) hay không. Nếu đúng như vậy, hãy khắc phục sự cố này tạm thời bằng cách thêm LOCAL_PROVIDES_USES_LIBRARY := <library-name> vào tệp Android.mk của thư viện hoặc thêm provides_uses_lib: "<library-name>" vào tệp Android.bp của thư viện (cả hai trường hợp có thể thực hiện được vì mô-đun Android.mk có thể phụ thuộc vào thư viện Android.bp ). Để có giải pháp lâu dài, hãy khắc phục sự cố cơ bản: đổi tên mô-đun thư viện.

  2. Thêm LOCAL_USES_LIBRARIES := <library-module-name> cho các thư viện cần thiết; thêm LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> cho các thư viện tùy chọn vào định nghĩa Android.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 trong danh sách phải giống như trong tệp kê khai.

Lỗi xây dựng: đường dẫn thư viện không xác định

Nếu hệ thống xây dựng không thể tìm thấy đường dẫn đến jar DEX <uses-library> (đường dẫn thời gian xây dựng trên máy chủ hoặc đường dẫn cài đặt trên thiết bị), thì quá trình xây dựng thường không thành công. Việc không tìm được đường dẫn có thể cho biết rằng thư viện được cấu hình theo cách không mong muốn. Tạm thời sửa 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 xây dựng: thiếu phụ thuộc thư viện

Việc cố gắng thêm <uses-library> X từ tệp kê khai của mô-đun Y vào tệp 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 mô-đun Android.bp:

"Y" depends on undefined module "X"

Đây là thông báo lỗi mẫu cho 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

Nguyên nhân phổ biến của các lỗi như vậy là khi thư viện được đặt tên khác với tên mô-đun tương ứng của nó trong hệ thống xây dựng. Ví dụ: nếu mục nhập <uses-library> của tệp kê khai là com.android.X nhưng tên của mô-đun thư viện chỉ là X thì điều đó sẽ gây ra lỗi. Để giải quyết trường hợp này, hãy thông báo cho hệ thống xây dựng 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à một ví dụ cho 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 các thông báo liên quan đến CLC không khớp, như hiển thị bên dưới:

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

Đầu ra có thể có các thông báo có dạng hiển thị ở đây:

[...] 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 cho mô-đun bị lỗi. Để khắc phục, hãy đảm bảo rằng quá trình kiểm tra thời gian xây dựng mô-đun đã thành công. Nếu cách đó không hiệu quả thì trường hợp của bạn có thể là trường hợp đặc biệt không được hệ thống xây dựng hỗ trợ (chẳng hạn như ứng dụng tải APK khác chứ không phải 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 gì khi chạy.

Bối cảnh của trình nạp lớp

CLC là một cấu trúc dạng cây mô tả hệ thống phân cấp của trình nạp lớp. Hệ thống xây dựng sử dụng CLC theo nghĩa hẹp (nó chỉ bao gồm các thư viện, không phải APK hoặc trình tải lớp tùy chỉnh): đó là một cây thư viện thể hiện việc đóng bắc cầu của tất cả các phần phụ thuộc <uses-library> của 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 (đường dẫn lớp). Mỗi nút của cây CLC là một nút <uses-library> có thể có các nút con <uses-library> riêng.

Vì các phần phụ thuộc <uses-library> là một biểu đồ 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. Nói cách khác, CLC là đồ thị phụ thuộc được "mở" thành cây. Sự trùng lặp chỉ ở mức độ logic; các trình nạp lớp cơ bản thực tế không bị trùng lặp (trong thời gian chạy có một phiên bản trình nạp lớp duy nhất cho mỗi thư viện).

CLC xác định thứ tự tra cứu của các thư viện khi giải quyết các lớp Java được thư viện hoặc ứng dụng sử dụng. Thứ tự tra cứu 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 đó được phân giải thành kết quả khớp đầu tiên.

Trên thiết bị (thời gian chạy) CLC

PackageManager (trong frameworks/base ) tạo CLC để tải mô-đun Java trên thiết bị. Nó thêm các thư viện được liệt kê trong thẻ <uses-library> trong bảng kê khai của mô-đun dưới dạng các phần tử CLC cấp cao nhất.

Đối với mỗi thư viện được sử dụng, PackageManager nhận tất cả các phần phụ thuộc <uses-library> (được chỉ định dưới dạng thẻ trong tệp kê khai của thư viện đó) và thêm CLC lồng nhau cho từng 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 xây dựng là các thư viện không có phần phụ thuộc <uses-library> .

PackageManager chỉ biết các thư viện dùng chung. Định nghĩa chia sẻ trong cách sử dụng này khác với ý nghĩa thông thường của nó (như chia sẻ so với 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 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 dùng chung này sử dụng trong thời gian chạy và chỉ định trong thẻ <uses-library> trong tệp kê khai của nó).

Nói cách khác, có hai nguồn thông tin cho phép PackageManager xây dựng CLC trong thời gian chạy: thẻ <uses-library> trong tệp kê khai và các phần phụ thuộc của thư viện dùng chung trong cấu hình XML.

CLC trên máy chủ (thời gian xây 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 xây dựng (dexpreopt). Vì dexopt diễn ra trên thiết bị nên nó có cùng thông tin với PackageManager (tệp kê khai và phần phụ thuộc của 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 và nó phải lấy thông tin tương tự từ hệ thống xây dựng.

Do đó, CLC thời gian xây dựng được sử dụng bởi dxpreopt và CLC thời gian chạy được PackageManager sử dụng 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 sẽ bị từ chối. Để kiểm tra sự bằng nhau của CLC thời gian xây dựng và thời gian chạy, trình biên dịch dex2oat ghi lại CLC 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 được lưu trữ, sử dụng lệnh này:

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

CLC thời gian xây dựng và thời gian chạy không khớp được báo cáo trong logcat trong khi khởi động. Tìm kiếm nó bằng lệnh này:

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

Việc không khớp không phù hợp có ảnh hưởng xấu đến hiệu suất vì nó buộc thư viện hoặc ứng dụng phải bị giải mã hoặc chạy mà không được tối ưu hóa (ví dụ: mã của ứng dụng có thể cần được trích xuất trong bộ nhớ từ APK, một thao tác rất tốn kém).

Thư viện dùng chung có thể là tùy chọn hoặc bắt buộc. Từ quan điểm dexpreopt, một thư viện bắt buộc phải có mặt tại thời điểm xây dựng (sự vắng mặt của nó là lỗi xây dựng). Một thư viện tùy chọn có thể hiện diện hoặc vắng mặt tại thời điểm xây dựng: nếu có, nó sẽ được thêm vào CLC, chuyển tới dex2oat và được ghi trong tệp *.odex . Nếu thiếu thư viện tùy chọn, 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 tùy chọn có trong một trường hợp, nhưng không có trong trường hợp kia), thì CLC thời gian xây dựng và thời gian chạy không khớp và mã được biên dịch sẽ bị từ chối.

Chi tiết hệ thống xây dựng nâng cao (trình sửa lỗi 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, ví dụ: 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 thẻ <uses-library> khác và tệp kê khai của thư viện hoặc ứng dụng không được cập nhật để bao gồm thẻ đó.

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ụ thuộc bắc cầu của thư viện hoặc ứng dụng. Việc đóng là cần thiết vì thư viện (hoặc ứng dụng) có thể phụ thuộc vào 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 thư viện khác.

Không phải tất cả các thẻ <uses-library> đều có thể được tính toán theo cách này, nhưng khi có thể, tốt nhất là để Soong tự động thêm các mục kê khai; nó ít xảy ra lỗi hơn và đơn giản hóa việc bảo trì. Ví dụ: khi nhiều ứng dụng sử dụng thư viện tĩnh có thêm phần phụ thuộc <uses-library> mới thì tất cả ứng dụng đều phải được cập nhật, điều này rất khó duy trì.