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

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

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

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

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

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

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

CLC على المضيف (وقت الإنشاء)

لا تكون CLC مطلوبة فقط عند تحميل مكتبة أو تطبيق، بل تكون مطلوبة أيضًا عند compiling أحدهما. يمكن أن تتم عملية الترجمة على الجهاز (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'

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

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

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

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

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

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