Dexpreopt و & lt ؛ استخدامات المكتبة & GT. الفحوصات

قام 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 بدلاً من ذلك. بالنسبة للتمهيد اللاحق، يعد هذا أمرًا جيدًا لأنه يمكن إلغاء اختيار التطبيقات في الخلفية وتخزينها على القرص.

المناطق المتضررة من الروبوت

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

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

  2. إذا لم توفر الخطوة السابقة حلاً، أضف 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 الخاص بالمكتبة، أو قم بإضافة 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 هما نفس الشيء، ولكن يتم حسابهما بطريقتين مختلفتين.

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

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

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

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

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

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

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

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

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

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