عمليات التحقّق من 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 آلاف التطبيقات، ويستخدم المئات منها المكتبات المشتركة. ويتأثر الشركاء أيضًا لأنّ لديهم مكتبات وتطبيقات خاصة بهم.

تغييرات قد تؤدي إلى أعطال

يحتاج نظام الإنشاء إلى معرفة تبعيات <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

    في ملف makefile الخاص بالمنتج يؤدي ذلك إلى حلّ أخطاء الإنشاء (باستثناء الحالات الخاصة المذكورة في قسم إصلاح المشاكل). ومع ذلك، هذا حل مؤقت، ويمكن أن يؤدي إلى عدم تطابق 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 في ملف 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>"] للمكتبات الاختيارية إلى تعريف 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 الخاصة بوقت الإنشاء مع رموز CLC الخاصة بوقت التشغيل، وسيتم رفض الرمز البرمجي الذي تم تجميعه.

تفاصيل نظام الإنشاء المتقدّم (أداة إصلاح ملف البيان)

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

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

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