بررسی‌های Dexpreopt و <uses-library>

Android 12 تغییراتی در سیستم ساخت برای کامپایل AOT فایل‌های DEX (dexpreopt) برای ماژول‌های Java که وابستگی‌های <uses-library> دارند ایجاد کرده است. در برخی موارد، این تغییرات سیستم ساخت می‌تواند ساخت‌ها را خراب کند. از این صفحه برای آماده شدن برای خرابی‌ها استفاده کنید و دستورالعمل‌های این صفحه را برای رفع و کاهش آن‌ها دنبال کنید.

‫Dexpreopt فرایند پیش‌بهینه‌سازی کتابخانه‌ها و برنامه‌های Java است. ‫Dexpreopt در زمان ساخت در میزبان انجام می‌شود (برخلاف dexopt که در دستگاه انجام می‌شود). ساختار وابستگی‌های کتابخانه مشترک مورداستفاده یک واحد Java (کتابخانه یا برنامه) به‌عنوان زمینه بارکننده کلاس (CLC) آن شناخته می‌شود. برای تضمین صحت dexpreopt، CLCهای زمان ساخت و زمان اجرا باید با هم منطبق باشند. CLC زمان ساخت چیزی است که کامپایلر dex2oat در زمان dexpreopt استفاده می‌کند (در فایل‌های ODEX ثبت می‌شود)، و CLC زمان اجرا زمینه‌ای است که کد پیش‌کامپایل‌شده در آن در دستگاه بار می‌شود.

این CLCهای زمان ساخت و زمان اجرا باید به دلایل صحت و عملکرد با هم منطبق باشند. برای صحت، لازم است کلاس‌های تکراری مدیریت شوند. اگر وابستگی‌های کتابخانه مشترک در زمان اجرا با وابستگی‌های استفاده‌شده برای ترجمه متفاوت باشد، برخی‌از کلاس‌ها ممکن است به‌طور متفاوتی حل‌وفصل شوند و باعث ایجاد اشکالات جزئی در زمان اجرا شوند. عملکرد تحت‌تأثیر بررسی‌های زمان اجرا برای کلاس‌های تکراری نیز قرار می‌گیرد.

موارد استفاده تحت‌تأثیر قرارگرفته

اولین بوت مورد استفاده اصلی است که تحت‌تأثیر این تغییرات قرار می‌گیرد: اگر ART بین CLCهای زمان ساخت و زمان اجرا عدم تطابق تشخیص دهد، مصنوعات dexpreopt را رد می‌کند و به‌جای آن dexopt را اجرا می‌کند. برای راه‌اندازی‌های بعدی این مشکلی ندارد زیرا برنامه‌ها می‌توانند در پس‌زمینه dexopt شوند و در دیسک ذخیره شوند.

بخش‌های تحت‌تأثیر Android

این موضوع روی همه برنامه‌ها و کتابخانه‌های Java که وابستگی‌های زمان اجرا به کتابخانه‌های Java دیگر دارند تأثیر می‌گذارد. ‫Android هزاران برنامه دارد و صدها برنامه از کتابخانه‌های مشترک استفاده می‌کنند. شرکا نیز تحت تأثیر قرار می‌گیرند، زیرا کتابخانه‌ها و برنامه‌های خودشان را دارند.

تغییرات ناگهانی

سیستم ساخت باید قبل‌از تولید قوانین ساخت dexpreopt، وابستگی‌های <uses-library> را بداند. بااین‌حال، نمی‌تواند مستقیماً به مانیفست دسترسی داشته باشد و برچسب‌های <uses-library> را در آن بخواند، زیرا سیستم ساخت مجاز نیست فایل‌های دلخواه را هنگام تولید قوانین ساخت بخواند (به دلایل عملکرد). علاوه‌براین، مانیفست ممکن است در داخل APK یا پیش‌ساخته بسته‌بندی شود. بنابراین، <uses-library> اطلاعات باید در فایل‌های ساخت (Android.bp یا Android.mk) وجود داشته باشد.

قبلاً ART از راهکاری استفاده می‌کرد که وابستگی‌های کتابخانه مشترک را نادیده می‌گرفت (که به‌نام &-classpath شناخته می‌شد). این راهکار ناامن بود و باعث ایجاد اشکالات جزئی می‌شد، بنابراین این راهکار در Android 12 برداشته شد.

درنتیجه، واحدهای Java که اطلاعات <uses-library> صحیح را در فایل‌های ساخت خود ارائه نمی‌دهند می‌توانند باعث خرابی ساخت (ناشی از عدم تطابق CLC در زمان ساخت) یا پس‌رفت‌های زمان اولین راه‌اندازی (ناشی از عدم تطابق CLC در زمان راه‌اندازی و به‌دنبال آن dexopt) شوند.

مسیر انتقال

برای رفع مشکل ساخت خراب، این مراحل را دنبال کنید:

  1. با تنظیم کردن، بررسی زمان ساخت را برای محصولی خاص به‌طور سراسری غیرفعال کنید

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    در فایل ساخت محصول. این کار خطاهای ساخت را برطرف می‌کند (به‌جز موارد خاص، که در بخش برطرف کردن خرابی‌ها فهرست شده است). بااین‌حال، این یک راهکار موقت است و می‌تواند باعث عدم تطابق CLC در زمان راه‌اندازی و به‌دنبال آن dexopt شود.

  2. قبل‌از اینکه بررسی زمان ساخت را به‌طور سراسری غیرفعال کنید، با افزودن اطلاعات <uses-library> لازم به فایل‌های ساخت آن‌ها، واحدهایی را که ناموفق بوده‌اند اصلاح کنید (برای جزئیات، اصلاح خرابی‌ها را ببینید). برای اکثر واحدها، این کار مستلزم افزودن چند خط در Android.bp یا در Android.mk است.

  3. بررسی زمان ساخت و dexpreopt را برای موارد مشکل‌ساز، به‌صورت هر واحد، غیرفعال کنید. ‫dexpreopt را غیرفعال کنید تا زمان ساخت و فضای ذخیره‌سازی را برای آرتیفکت‌هایی که در زمان راه‌اندازی رد می‌شوند هدر ندهید.

  4. با لغو تنظیم PRODUCT_BROKEN_VERIFY_USES_LIBRARIES که در «مرحله ۱» تنظیم شده بود، بررسی زمان ساخت را به‌صورت جهانی دوباره فعال کنید؛ ساخت نباید پس‌از این تغییر (به‌دلیل مراحل ۲ و ۳) ناموفق باشد.

  5. ماژول‌هایی را که در «مرحله ۳» غیرفعال کردید یکی‌یکی اصلاح کنید، سپس dexpreopt و بررسی <uses-library> را دوباره فعال کنید. درصورت نیاز، اشکالات را گزارش کنید.

بررسی‌های <uses-library> زمان ساخت در Android 12 اعمال می‌شود.

رفع خرابی‌ها

بخش‌های زیر به شما می‌گوید چگونه انواع خاصی از خرابی را برطرف کنید.

خطای ساخت: عدم تطابق CLC

سیستم ساخت، بررسی انسجام زمان ساخت را بین اطلاعات موجود در فایل‌های Android.bp یا Android.mk و مانیفست انجام می‌دهد. سیستم ساخت نمی‌تواند مانیفست را بخواند، اما می‌تواند قوانین ساخت را برای خواندن مانیفست (استخراج آن از APK درصورت لزوم) تولید کند و برچسب‌های <uses-library> در مانیفست را با اطلاعات <uses-library> در فایل‌های ساخت مقایسه کند. اگر بررسی ناموفق باشد، خطا به این شکل خواهد بود:

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

همان‌طور که پیام خطا پیشنهاد می‌کند، بسته به فوریت، راه‌حل‌های متعددی وجود دارد:

  • برای اصلاح موقت در سطح محصول، PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true را در فایل ساخت محصول تنظیم کنید. بررسی یکپارچگی زمان ساخت همچنان انجام می‌شود، اما ناموفق بودن بررسی به معنای ناموفق بودن ساخت نیست. درعوض، عدم موفقیت در بررسی باعث می‌شود سیستم ساخت، فیلتر کامپایلر dex2oat را در dexpreopt به verify تنزل دهد که کاملاً کامپایل AOT را برای این ماژول غیرفعال می‌کند.
  • برای اصلاح سریع و سراسری خط فرمان، از متغیر محیطی RELAX_USES_LIBRARY_CHECK=true استفاده کنید. این دستور همان اثر PRODUCT_BROKEN_VERIFY_USES_LIBRARIES را دارد، اما برای استفاده در خط فرمان درنظر گرفته شده است. متغیر محیطی بر متغیر محصول اولویت دارد.
  • برای راه‌حل رفع علت ریشه‌ای خطا، سیستم ساخت را از برچسب‌های <uses-library> در مانیفست مطلع کنید. بازرسی پیام خطا نشان می‌دهد کدام کتابخانه‌ها باعث مشکل شده‌اند (همچنین بازرسی AndroidManifest.xml یا مانیفست درون APK که با `aapt dump badging $APK | grep uses-library` قابل بررسی است).

برای واحدهای Android.bp:

  1. کتابخانه گمشده را در دارایی libs واحد پیدا کنید. اگر آنجا باشد، Soong معمولاً چنین کتابخانه‌هایی را به‌طور خودکار اضافه می‌کند، به‌جز در این موارد خاص:

    • کتابخانه، کتابخانه کیت توسعه نرم‌افزار نیست (به‌صورت java_library تعریف شده است نه java_sdk_library).
    • نام کتابخانه (در مانیفست) با نام واحد آن (در سیستم ساخت) متفاوت است.

    برای رفع موقت این مشکل، provides_uses_lib: "<library-name>" را در تعریف کتابخانه Android.bp اضافه کنید. برای راه‌حل بلندمدت، مشکل زیربنایی را برطرف کنید: کتابخانه را به کتابخانه کیت توسعه نرم‌افزار تبدیل کنید یا نام واحد آن را تغییر دهید.

  2. اگر مرحله قبلی راه‌حلی ارائه نداد، uses_libs: ["<library-module-name>"] را برای کتابخانه‌های الزامی، یا optional_uses_libs: ["<library-module-name>"] را برای کتابخانه‌های اختیاری به تعریف Android.bp واحد اضافه کنید. این دارایی‌ها فهرستی از نام‌های واحد را می‌پذیرند. ترتیب نسبی کتابخانه‌ها در فهرست باید با ترتیب آن‌ها در مانیفست یکسان باشد.

برای Android.mk واحد:

  1. بررسی کنید که آیا کتابخانه نام کتابخانه متفاوتی (در مانیفست) از نام ماژول خود (در سیستم ساخت) دارد یا خیر. اگر این‌طور است، این مشکل را موقتاً با افزودن LOCAL_PROVIDES_USES_LIBRARY := <library-name> در فایل Android.mk کتابخانه برطرف کنید، یا provides_uses_lib: "<library-name>" را در فایل Android.bp کتابخانه اضافه کنید (هر دو مورد امکان‌پذیر است زیرا ممکن است یک ماژول Android.mk به کتابخانه Android.bp وابسته باشد). برای راه‌حل بلندمدت، مشکل زیربنایی را برطرف کنید: واحد کتابخانه را تغییر نام دهید.

  2. ‫LOCAL_USES_LIBRARIES := <library-module-name> را برای کتابخانه‌های الزامی؛ و LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> را برای کتابخانه‌های اختیاری به تعریف Android.mk ماژول اضافه کنید. این ویژگی‌ها فهرستی از نام‌های واحد را می‌پذیرند. ترتیب نسبی کتابخانه‌ها در فهرست باید با ترتیب آن‌ها در مانیفست یکسان باشد.

خطای ساخت: مسیر کتابخانه ناشناخته

اگر سیستم ساخت نتواند مسیری به <uses-library> DEX jar پیدا کند (یا مسیر زمان ساخت در میزبان یا مسیر نصب در دستگاه)، معمولاً ساخت ناموفق خواهد بود. عدم یافتن مسیر می‌تواند نشان دهد که کتابخانه به روشی غیرمنتظره پیکربندی شده است. با غیرفعال کردن dexpreopt برای واحد مشکل‌دار، ساخت را به‌طور موقت اصلاح کنید.

Android.bp (ویژگی‌های واحد):

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

Android.mk (متغیرهای ماژول):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

برای بررسی هرگونه سناریو پشتیبانی‌نشده، اشکالی ثبت کنید.

خطای ساخت: وابستگی کتابخانه وجود ندارد

تلاش برای افزودن <uses-library> X از مانیفست ماژول Y به فایل ساخت Y ممکن است به‌دلیل وابستگی مفقود X منجر به خطای ساخت شود.

این یک پیام خطای نمونه برای واحدهای Android.bp است:

"Y" depends on undefined module "X"

این یک پیام خطای نمونه برای واحدهای 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

یکی از منابع رایج چنین خطاهایی زمانی است که نام کتابخانه با نام واحد مربوطه در سیستم ساخت متفاوت باشد. برای مثال، اگر ورودی <uses-library> مانیفست com.android.X باشد، اما نام واحد کتابخانه فقط X باشد، این امر باعث بروز خطا می‌شود. برای حل کردن این مورد، به سیستم ساخت بگویید که واحدی به‌نام X <uses-library> به‌نام com.android.X را ارائه می‌دهد.

این نمونه‌ای برای کتابخانه‌های Android.bp (ویژگی واحد) است:

provides_uses_lib: “com.android.X”,

این نمونه‌ای برای کتابخانه‌های Android.mk (متغیر ماژول) است:

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

عدم تطابق CLC زمان راه‌اندازی

در اولین راه‌اندازی، logcat را برای پیام‌های مربوط به عدم تطابق CLC جستجو کنید، همان‌طور که در زیر نشان داده شده است:

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

برونداد می‌تواند پیام‌هایی به شکل نشان‌داده‌شده در اینجا داشته باشد:

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

اگر هشدار عدم تطابق CLC دریافت کردید، به‌دنبال فرمان dexopt برای واحد معیوب بگردید. برای رفع این مشکل، مطمئن شوید که بررسی زمان ساخت برای واحد انجام می‌شود. اگر این کار مؤثر نبود، ممکن است مورد شما موردی خاص باشد که توسط سیستم ساخت پشتیبانی نمی‌شود (مانند برنامه‌ای که APK دیگری را بار می‌کند، نه کتابخانه). سیستم ساخت همه موارد را مدیریت نمی‌کند، زیرا در زمان ساخت نمی‌توان با قطعیت دانست که برنامه در زمان اجرا چه چیزی را بار می‌کند.

زمینه بارکننده کلاس

‫CLC ساختاری درخت‌مانند است که سلسله‌مراتب بارکننده کلاس را توصیف می‌کند. سیستم ساخت از CLC در معنای محدود استفاده می‌کند (فقط کتابخانه‌ها را پوشش می‌دهد، نه فایل‌های APK یا بارکننده‌های کلاس سفارشی): این سیستم درختی از کتابخانه‌ها است که نشان‌دهنده بستار انتقالی همه وابستگی‌های <uses-library> یک کتابخانه یا برنامه است. عناصر سطح بالای CLC وابستگی‌های <uses-library> مستقیم مشخص‌شده در مانیفست (مسیر کلاس) است. هر گره درخت CLC یک گره <uses-library> است که ممکن است گره‌های فرعی <uses-library> خود را داشته باشد.

ازآنجایی‌که <uses-library> وابستگی یک گراف غیرچرخه‌ای جهت‌دار است و لزوماً یک درخت نیست، CLC می‌تواند شامل چندین زیردرخت برای یک کتابخانه باشد. به‌عبارت دیگر، CLC نمودار وابستگی است که به درخت «بازشده» است. تکرار فقط در سطح منطقی است؛ بارکننده‌های کلاس اصلی واقعی تکرار نمی‌شوند (در زمان اجرا، یک نمونه بارکننده کلاس برای هر کتابخانه وجود دارد).

‫CLC ترتیب جستجوی کتابخانه‌ها را هنگام حل کردن کلاس‌های Java مورداستفاده کتابخانه یا برنامه تعریف می‌کند. ترتیب جستجو مهم است زیرا کتابخانه‌ها می‌توانند کلاس‌های تکراری داشته باشند و کلاس به اولین مورد منطبق حل می‌شود.

‫CLC در دستگاه (زمان اجرا)

‫PackageManager (در frameworks/base) یک CLC برای بار کردن واحد Java در دستگاه ایجاد می‌کند. کتابخانه‌های فهرست‌شده در برچسب‌های <uses-library> را به‌عنوان عناصر CLC سطح بالا به مانیفست واحد اضافه می‌کند.

برای هر کتابخانه استفاده‌شده، PackageManager همه <uses-library> وابستگی‌های آن (که به‌عنوان برچسب در مانیفست آن کتابخانه مشخص شده است) را دریافت می‌کند و برای هر وابستگی یک CLC تودرتو اضافه می‌کند. این فرایند به‌صورت بازگشتی ادامه می‌یابد تا زمانی که همه گره‌های برگ درخت CLC ساخته‌شده کتابخانه‌هایی بدون وابستگی <uses-library> باشند.

‫PackageManager فقط از کتابخانه‌های مشترک مطلع است. تعریف «مشترک» در این کاربرد با معنی معمول آن (مثلاً درمقابل «ایستا») متفاوت است. در Android، کتابخانه‌های مشترک Java آن‌هایی هستند که در پیکربندی‌های XML نصب‌شده در دستگاه (/system/etc/permissions/platform.xml) فهرست شده‌اند. هر ورودی حاوی نام کتابخانه مشترک، مسیر فایل DEX jar آن، و فهرستی از وابستگی‌ها است (کتابخانه‌های مشترک دیگری که این کتابخانه در زمان اجرا استفاده می‌کند و در برچسب‌های <uses-library> در مانیفست خود مشخص می‌کند).

به عبارت دیگر، دو منبع اطلاعاتی وجود دارد که به PackageManager اجازه می‌دهد CLC را در زمان اجرا بسازد: برچسب‌های <uses-library> در مانیفست، و وابستگی‌های کتابخانه مشترک در پیکربندی‌های XML.

‫CLC درون میزبان (زمان ساخت)

‫CLC فقط هنگام بار کردن کتابخانه یا برنامه لازم نیست، بلکه هنگام کامپایل کردن آن نیز لازم است. ترکیب می‌تواند هم در دستگاه (dexopt) و هم درطول ساخت (dexpreopt) انجام شود. ازآنجایی‌که dexopt در دستگاه انجام می‌شود، اطلاعات آن با PackageManager (مانیفست‌ها و وابستگی‌های کتابخانه مشترک) یکسان است. بااین‌حال، Dexpreopt در میزبان و در محیطی کاملاً متفاوت انجام می‌شود و باید اطلاعات یکسانی را از سیستم ساخت دریافت کند.

بنابراین، CLC زمان ساخت که توسط dexpreopt استفاده می‌شود و CLC زمان اجرا که توسط PackageManager استفاده می‌شود یکسان هستند، اما به دو روش متفاوت محاسبه می‌شوند.

‫CLCهای زمان ساخت و زمان اجرا باید با هم مطابقت داشته باشند، درغیراین‌صورت کد کامپایل‌شده AOT ایجادشده توسط dexpreopt رد می‌شود. برای بررسی برابری CLC زمان ساخت و زمان اجرا، کامپایلر dex2oat CLC زمان ساخت را در فایل‌های *.odex (در فیلد classpath سرایند فایل OAT) ثبت می‌کند. برای پیدا کردن CLC ذخیره‌شده، از این فرمان استفاده کنید:

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

عدم تطابق CLC زمان ساخت و زمان اجرا در logcat درطول راه‌اندازی گزارش می‌شود. با این فرمان آن را جستجو کنید:

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

عدم تطابق برای عملکرد بد است، زیرا کتابخانه یا برنامه را مجبور می‌کند که یا dexopt شود یا بدون بهینه‌سازی اجرا شود (برای مثال، ممکن است لازم باشد کد برنامه در حافظه از APK استخراج شود، که عملیاتی بسیار پرهزینه است).

کتابخانه مشترک می‌تواند اختیاری یا الزامی باشد. از دیدگاه dexpreopt، کتابخانه موردنیاز باید در زمان ساخت وجود داشته باشد (عدم وجود آن خطای ساخت است). کتابخانه اختیاری می‌تواند در زمان ساخت وجود داشته باشد یا نداشته باشد: اگر وجود داشته باشد، به CLC اضافه می‌شود، به dex2oat ارسال می‌شود، و در فایل *.odex ثبت می‌شود. اگر کتابخانه اختیاری وجود نداشته باشد، از آن صرف‌نظر می‌شود و به CLC اضافه نمی‌شود. اگر بین وضعیت زمان ساخت و زمان اجرا عدم تطابق وجود داشته باشد (کتابخانه اختیاری در یک مورد وجود داشته باشد، اما در مورد دیگر وجود نداشته باشد)، CLC زمان ساخت و زمان اجرا مطابقت ندارند و کد کامپایل‌شده رد می‌شود.

جزئیات پیشرفته سیستم ساخت (تعمیرکننده مانیفست)

گاهی اوقات برچسب‌های <uses-library> در مانیفست منبع کتابخانه یا برنامه وجود ندارند. این اتفاق ممکن است زمانی رخ دهد که یکی از وابستگی‌های انتقالی کتابخانه یا برنامه شروع به استفاده از برچسب <uses-library> دیگری کند و مانیفست کتابخانه یا برنامه برای گنجاندن آن به‌روزرسانی نشود.

‫Soong می‌تواند برخی‌از برچسب‌های <uses-library> ازدست‌رفته را برای کتابخانه یا برنامه معینی به‌طور خودکار محاسبه کند، زیرا کتابخانه‌های «کیت توسعه نرم‌افزار» در بستار وابستگی انتقالی کتابخانه یا برنامه قرار دارند. بستار لازم است زیرا کتابخانه (یا برنامه) ممکن است به کتابخانه ایستا وابسته باشد که به کتابخانه «کیت توسعه نرم‌افزار» وابسته است و احتمالاً ممکن است دوباره ازطریق کتابخانه دیگری به‌صورت انتقالی وابسته باشد.

Not all <uses-library> tags can be computed this way, but when possible, it's prefereable to let Soong add manifest entries automatically; it's less error-prone and simplifies maintenance. برای مثال، وقتی بسیاری از برنامه‌ها از کتابخانه ایستا استفاده می‌کنند که وابستگی جدید <uses-library> را اضافه می‌کند، همه برنامه‌ها باید به‌روزرسانی شوند که نگهداری آن دشوار است.