عمليات التحقّق من Dexpreopt و <uses-library>

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

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

يجب أن تتطابق قوائم CLC هذه في وقت الإنشاء ووقت التشغيل لأغراض صحة الرمز المبرمَج وأداء التطبيق. ولضمان صحة البيانات، من الضروري التعامل مع الفئات المكرّرة. إذا كانت تبعيات المكتبة المشتركة في وقت التشغيل مختلفة عن تلك المستخدَمة في عملية الترجمة، قد يتم حلّ بعض الفئات بشكل مختلف، ما يؤدي إلى حدوث أخطاء طفيفة في وقت التشغيل. ويتأثر الأداء أيضًا بعمليات البحث في بيئة التشغيل عن النُسخ المكرّرة الصفوف.

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

إنّ التشغيل الأول هو حالة الاستخدام الرئيسية التي ستتأثر بهذه التغييرات: في حال استخدام ART اكتشاف عدم تطابق بين العناصر القابلة للتنفيذ في وقت الإصدار وتلك المستندة إلى وقت التشغيل، فإنها ترفض 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

    في ملف 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 في ملف make الخاص بالمنتج. لا يزال يتم إجراء عملية التحقّق من التماسك أثناء مرحلة التصميم، ولكن تعذُّر إجراء عملية التحقّق لا يعني تعذُّر البناء. بدلاً من ذلك، يؤدي تعذُّر الفحص إلى خفض نظام الإنشاء لإصدار فلاتر مترجم verify في dexpreopt إلى verify، ما يؤدي إلى إيقاف الترجمة المسبقة للتعدُّد (AOT) تمامًا لهذه الوحدة.
  • للحصول على حلّ سريع وشامل من سطر الأوامر، استخدِم متغيّر البيئة RELAX_USES_LIBRARY_CHECK=true. له نفس تأثير PRODUCT_BROKEN_VERIFY_USES_LIBRARIES، ولكنه مخصّص للاستخدام في سطر الأوامر. يتجاهل متغيّر المعالجة المتغيّر product.
  • للحصول على حلّ لإصلاح السبب الأساسي للخطأ، يجب إبلاغ نظام الإنشاء بعلامات <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 للوحدة. تقبل هذه السمات قائمة بأسماء الوحدات. الترتيب النسبي للمكتبات على يجب أن تكون القائمة هي نفسها الموجودة في البيان.

خطأ في الإصدار: مسار مكتبة غير معروف

إذا لم يتمكّن نظام التصميم من العثور على مسار إلى وعاء 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 ترتيب البحث عن المكتبات عند تحليل فئات 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). يحتوي كل إدخال على اسم مكتبة مشترَكة ومسار إلى ملف jar بتنسيق DEX وقائمة بالملحقات (المكتبات المشترَكة الأخرى التي تستخدمها هذه المكتبة أثناء التشغيل، والتي تحدّدها في علامات <uses-library> في ملف البيان).

بعبارة أخرى، هناك مصدران للمعلومات يسمحان PackageManager بإنشاء CLC في وقت التشغيل: علامات <uses-library> في البيان، وتبعيات المكتبة المشتركة في إعدادات XML.

CLC عند المضيف (وقت الإصدار)

لا حاجة إلى استخدام CLC عند تحميل مكتبة أو تطبيق فحسب، بل يجب استخدامها أيضًا عند تحميل مكتبة أو تطبيق وتجميع واحدة. يمكن أن تتم عملية التحويل البرمجي إما على الجهاز (dexopt) أو أثناء إنشاء (dexpreopt). بما أنّ أداة dexopt تعمل على الجهاز، فهي تتضمّن المعلومات نفسها التي تتضمّنها أداة PackageManager (الملفات البيانية وتبعيات المكتبات المشتركة). في المقابل، تتم عملية Dexpreopt على المضيف بطريقة مختلفة ويجب أن يحصل على المعلومات نفسها من نظام التصميم.

وبالتالي، فإنّ ملف CLC الذي يتم استخدامه في وقت الإنشاء من خلال dexpreopt وملف CLC الذي يتم استخدامه في وقت التشغيل من خلال PackageManager هما الشيء نفسه، ولكن يتم احتسابهما بطريقتَين مختلفتَين.

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

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

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

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

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

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

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

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

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

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