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

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

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

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

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

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

  4. إعادة تفعيل فحص وقت الإصدار على مستوى العالم من خلال إلغاء ضبط PRODUCT_BROKEN_VERIFY_USES_LIBRARIES الذي تم ضبطه في الخطوة 1، من المفترض ألا يتعذّر عملية الإصدار بعد هذا التغيير (بسبب الخطوتَين 2 و3)

  5. أصلِح الوحدات التي أوقفتها في الخطوة 3، وحدة تلو الأخرى، ثم أعِد تفعيلها وفحص <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.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 التي يستخدمها المكتبة أو التطبيق. إنّ ترتيب البحث مهم لأنّ المكتبات يمكن أن تحتوي على فئات مكرّرة، ويتم تحليل الفئة إلى المطابقة الأولى.

معالجة البيانات على الجهاز (وقت التشغيل)

ينشئ 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، ولكن يتم احتسابها بطريقتَين مختلفتَين.

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