تحقق Dexpreopt و <uses-library>

يحتوي Android 12 على تغييرات نظام بناء على تجميع AOT لملفات DEX (dexpreopt) لوحدات Java النمطية التي تحتوي على تبعيات <uses-library> . في بعض الحالات ، يمكن أن تؤدي تغييرات نظام البناء هذه إلى تعطيل الإنشاءات. استخدم هذه الصفحة للتحضير لكسر ، واتبع الوصفات على هذه الصفحة لإصلاحها والتخفيف من حدتها.

Dexpreopt هي عملية تجميع تطبيقات وتطبيقات Java مسبقًا. يحدث Dexpreopt على المضيف في وقت الإنشاء (على عكس dexopt ، والذي يحدث على الجهاز). تُعرف بنية تبعيات المكتبة المشتركة التي تستخدمها وحدة Java (مكتبة أو تطبيق) باسم سياق أداة تحميل الفئات (CLC). لضمان صحة dexpreopt ، يجب أن تتزامن CLCs مع وقت الإنشاء ووقت التشغيل. وقت البناء CLC هو ما يستخدمه مترجم dex2oat في وقت dexpreopt (يتم تسجيله في ملفات ODEX) ، ووقت التشغيل CLC هو السياق الذي يتم فيه تحميل التعليمات البرمجية المترجمة مسبقًا على الجهاز.

يجب أن تتوافق CLCs الخاصة بوقت البناء ووقت التشغيل لأسباب تتعلق بالصحة والأداء. من أجل الصحة ، من الضروري التعامل مع الفئات المكررة. إذا كانت تبعيات المكتبة المشتركة في وقت التشغيل مختلفة عن تلك المستخدمة في التجميع ، فقد يتم حل بعض الفئات بشكل مختلف ، مما يتسبب في أخطاء دقيقة في وقت التشغيل. يتأثر الأداء أيضًا بفحوصات وقت التشغيل للفئات المكررة.

حالات الاستخدام المتأثرة

التمهيد الأول هو حالة الاستخدام الرئيسية التي تتأثر بهذه التغييرات: إذا اكتشف ART عدم تطابق بين وقت الإنشاء ووقت التشغيل CLCs ، فإنه يرفض المصنوعات dexpreopt ويقوم بتشغيل dexopt بدلاً من ذلك. بالنسبة إلى عمليات التمهيد اللاحقة ، يعد هذا أمرًا جيدًا لأنه يمكن تعديل التطبيقات في الخلفية وتخزينها على القرص.

المناطق المتضررة من Android

يؤثر هذا على جميع تطبيقات ومكتبات Java التي لها تبعيات وقت التشغيل في مكتبات Java الأخرى. يحتوي Android على آلاف التطبيقات ، ويستخدم المئات منها مكتبات مشتركة. يتأثر الشركاء أيضًا ، لأن لديهم مكتباتهم وتطبيقاتهم الخاصة.

كسر التغييرات

يحتاج نظام البناء إلى معرفة تبعيات <uses-library> قبل أن يولد قواعد بناء dexpreopt. ومع ذلك ، لا يمكنه الوصول إلى البيان مباشرةً وقراءة علامات <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 الذي تم تعيينه في الخطوة 1 ؛ يجب ألا يفشل التصميم بعد هذا التغيير (بسبب الخطوتين 2 و 3).

  5. أصلح الوحدات التي قمت بتعطيلها في الخطوة 3 ، واحدة تلو الأخرى ، ثم أعد تمكين dexpreopt وفحص <uses-library> . ملف البق إذا لزم الأمر.

يتم فرض عمليات تحقق <uses-library> وقت الإنشاء في Android 12.

إصلاح الكسور

توضح الأقسام التالية كيفية إصلاح أنواع معينة من الكسر.

خطأ في الإنشاء: عدم تطابق CLC

يقوم نظام الإنشاء بفحص تماسك وقت البناء بين المعلومات الموجودة في ملفات Android.bp أو Android.mk . يتعذر على نظام الإنشاء قراءة البيان ، ولكن يمكنه إنشاء قواعد بناء لقراءة البيان (استخراجه من ملف APK إذا لزم الأمر) ، ومقارنة علامات <uses- <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 في ملف makefile للمنتج. لا يزال التحقق من ترابط وقت البناء قيد التنفيذ ، لكن فشل الفحص لا يعني فشل الإنشاء. بدلاً من ذلك ، يؤدي فشل التحقق إلى قيام نظام الإنشاء بخفض مستوى عامل تصفية المترجم 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_library بدلاً من java_sdk_library ).
    • المكتبة لها اسم مكتبة مختلف (في البيان) عن اسم الوحدة (في نظام البناء).

    لإصلاح ذلك مؤقتًا ، أضف provides_uses_lib: "<library-name>" في تعريف مكتبة Android.bp . لحل طويل الأمد ، أصلح المشكلة الأساسية: تحويل المكتبة إلى مكتبة SDK ، أو إعادة تسمية الوحدة النمطية الخاصة بها.

  2. إذا لم تقدم الخطوة السابقة حلًا ، أضف uses_libs: ["<library-module-name>"] للمكتبات المطلوبة ، أو Optional_uses_libs: ["<library-module-name>" optional_uses_libs: ["<library-module-name>"] للمكتبات الاختيارية إلى Android.bp تعريف Android.bp للوحدة. تقبل هذه الخصائص قائمة بأسماء الوحدات النمطية. يجب أن يكون الترتيب النسبي للمكتبات في القائمة هو نفسه الترتيب الموجود في البيان.

بالنسبة لوحدات Android.mk :

  1. تحقق مما إذا كان للمكتبة اسم مكتبة مختلف (في البيان) عن اسم الوحدة النمطية الخاصة بها (في نظام الإنشاء). إذا كان الأمر كذلك ، فقم بإصلاح ذلك مؤقتًا عن طريق إضافة LOCAL_PROVIDES_USES_LIBRARY := <library-name> في ملف Android.mk للمكتبة ، أو إضافة provide_uses_lib 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 و run-time CLC المستخدم بواسطة PackageManager هما نفس الشيء ، ولكن يتم حسابهما بطريقتين مختلفتين.

يجب أن تتطابق CLCs الخاصة بوقت البناء ووقت التشغيل ، وإلا فسيتم رفض الكود المترجم من AOT الذي تم إنشاؤه بواسطة dexpreopt. للتحقق من المساواة بين CLCs وقت الإنشاء ووقت التشغيل ، يقوم المحول البرمجي classpath بتسجيل CLC وقت البناء في ملفات *.odex (في حقل مسار الفصل لرأس ملف OAT). للعثور على CLC المخزن ، استخدم هذا الأمر:

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

تم الإبلاغ عن عدم تطابق CLC مع وقت الإنشاء ووقت التشغيل في logcat أثناء التمهيد. ابحث عنها بهذا الأمر:

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

يعد عدم التطابق أمرًا سيئًا للأداء ، لأنه يفرض على المكتبة أو التطبيق إما dexopted ، أو التشغيل بدون تحسينات (على سبيل المثال ، قد يلزم استخراج رمز التطبيق في الذاكرة من APK ، وهي عملية مكلفة للغاية).

يمكن أن تكون المكتبة المشتركة اختيارية أو مطلوبة. من وجهة نظر dexpreopt ، يجب أن تكون المكتبة المطلوبة موجودة في وقت الإنشاء (غيابها يعد خطأ في البناء). يمكن أن تكون المكتبة الاختيارية موجودة أو غائبة في وقت الإنشاء: إذا كانت موجودة ، يتم إضافتها إلى CLC ، وتمريرها إلى dex2oat ، وتسجيلها في ملف *.odex . في حالة عدم وجود مكتبة اختيارية ، يتم تخطيها ولا تتم إضافتها إلى CLC. إذا كان هناك عدم تطابق بين حالة وقت الإنشاء ووقت التشغيل (المكتبة الاختيارية موجودة في إحدى الحالات ، ولكن ليس الأخرى) ، فإن وقت الإنشاء ووقت التشغيل CLCs لا يتطابقان ويتم رفض الكود المترجم.

تفاصيل نظام البناء المتقدم (مثبت البيان)

أحيانًا تكون علامات <uses-library> مفقودة من بيان المصدر لمكتبة أو تطبيق. يمكن أن يحدث هذا ، على سبيل المثال ، إذا بدأت إحدى التبعيات متعدية للمكتبة أو التطبيق في استخدام علامة <uses-library> أخرى ، ولم يتم تحديث بيان المكتبة أو التطبيق لتضمينها.

يمكن لـ Soong حساب بعض علامات <uses-library> المفقودة لمكتبة أو تطبيق معين تلقائيًا ، مثل مكتبات SDK في إغلاق التبعية المتعدية للمكتبة أو التطبيق. يلزم الإغلاق لأن المكتبة (أو التطبيق) قد تعتمد على مكتبة ثابتة تعتمد على مكتبة SDK ، وربما تعتمد مرة أخرى بشكل انتقالي من خلال مكتبة أخرى.

لا يمكن حساب جميع علامات <uses-library> بهذه الطريقة ، ولكن عندما يكون ذلك ممكنًا ، يُفضل السماح لـ Soong بإضافة إدخالات البيان تلقائيًا ؛ إنه أقل عرضة للخطأ ويبسط الصيانة. على سبيل المثال ، عندما تستخدم العديد من التطبيقات مكتبة ثابتة تضيف تبعية <uses-library> جديدة ، يجب تحديث جميع التطبيقات ، وهو أمر يصعب صيانته.