Dexpreopt و <uses-library> چک ها

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

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

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

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

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

مناطق آسیب‌پذیر اندروید

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

تغییرات شکستن

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

پیش از این، ART از یک راهکار جایگزین استفاده می‌کرد که وابستگی‌های کتابخانه مشترک (معروف به &-classpath ) را نادیده می‌گرفت. این راهکار ناامن بود و باعث ایجاد اشکالات جزئی می‌شد، بنابراین این راهکار در اندروید ۱۲ حذف شد.

در نتیجه، ماژول‌های جاوا که اطلاعات صحیح <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> در زمان ساخت در اندروید ۱۲ اعمال می‌شوند.

رفع شکستگی‌ها

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

خطای ساخت: عدم تطابق 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 را برای verify در dexpreopt تنزل رتبه دهد، که کامپایل 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 معمولاً چنین کتابخانه‌هایی را به‌طور خودکار اضافه می‌کند، مگر در موارد خاص:

    • این کتابخانه یک کتابخانه SDK نیست (به جای java_sdk_library java_library شده است).
    • نام کتابخانه (در مانیفست) با نام ماژول آن (در سیستم ساخت) متفاوت است.

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

  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 ماژول اضافه کنید. این ویژگی‌ها لیستی از نام ماژول‌ها را می‌پذیرند. ترتیب نسبی کتابخانه‌ها در لیست باید مانند مانیفست باشد.

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

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

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

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

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

PackageManager فقط از کتابخانه‌های اشتراکی آگاه است. تعریف کلمه اشتراکی در این کاربرد با معنای معمول آن (مانند اشتراکی در مقابل استاتیک) متفاوت است. در اندروید، کتابخانه‌های اشتراکی جاوا، آن‌هایی هستند که در پیکربندی‌های XML که روی دستگاه نصب شده‌اند ( /system/etc/permissions/platform.xml ) فهرست شده‌اند. هر ورودی شامل نام یک کتابخانه اشتراکی، مسیری به فایل jar DEX آن و لیستی از وابستگی‌ها (سایر کتابخانه‌های اشتراکی که این کتابخانه در زمان اجرا استفاده می‌کند و در برچسب‌های <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 [az ]+ mismatch'

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

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

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

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

سونگ می‌تواند برخی از تگ‌های <uses-library> گم‌شده را برای یک کتابخانه یا برنامه‌ی معین، به صورت خودکار محاسبه کند، زیرا کتابخانه‌های SDK در وابستگی انتقالیِ بسته شدنِ کتابخانه یا برنامه قرار دارند. این بسته شدن مورد نیاز است زیرا کتابخانه (یا برنامه) ممکن است به یک کتابخانه‌ی ایستا وابسته باشد که آن کتابخانه نیز به یک کتابخانه‌ی SDK وابسته است و احتمالاً ممکن است دوباره از طریق کتابخانه‌ی دیگری به صورت انتقالی وابسته شود.

همه تگ‌های <uses-library> را نمی‌توان به این روش محاسبه کرد، اما در صورت امکان، ترجیح داده می‌شود که به Soong اجازه دهید ورودی‌های manifest را به طور خودکار اضافه کند. این روش کمتر مستعد خطا است و نگهداری را ساده می‌کند. به عنوان مثال، وقتی بسیاری از برنامه‌ها از یک کتابخانه استاتیک استفاده می‌کنند که یک وابستگی جدید <uses-library> اضافه می‌کند، همه برنامه‌ها باید به‌روزرسانی شوند که نگهداری آن دشوار است.