التحقق من سلوك التطبيق في وقت تشغيل Android (ART)

وقت تشغيل Android (ART) هو وقت التشغيل التلقائي للأجهزة التي تعمل بنظام التشغيل Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث. ويوفّر وقت التشغيل هذا عددًا من الميزات التي تعمل على تحسين الأداء والسلاسة في نظام Android الأساسي وتطبيقاته. يمكنك العثور على مزيد من المعلومات حول الميزات الجديدة في أداة ART في المقالة بعنوان نقدّم لك ART.

ومع ذلك، فإن بعض التقنيات التي تعمل على "دالفيك" لا تصلح للاستخدام في ART. يتيح لك هذا المستند معرفة الأمور التي يجب الانتباه إليها عند نقل بيانات تطبيق حالي ليصبح متوافقًا مع ART. من المفترض أن تعمل معظم التطبيقات فقط عند التشغيل باستخدام ART.

معالجة مشاكل جمع البيانات غير الصالحة (GC)

ضمن Dalvik، غالبًا ما تجد التطبيقات أنّه من المفيد تسمية الرمز System.gc() بشكل صريح لطلب جمع البيانات غير المرغوب فيها (GC). ولن يكون هذا الإجراء ضروريًا مع ART، خاصةً عند استدعاء جمع البيانات غير المرغوب فيها لمنع حدوث تكرار من النوع GC_FOR_ALLOC أو لتقليل التجزئة. يمكنك التحقق من وقت التشغيل قيد الاستخدام من خلال طلب الرقم System.getProperty("java.vm.version"). إذا كانت سمة ART قيد الاستخدام، تكون قيمة الخاصية "2.0.0" أو أعلى.

يستخدم ART أداة جمع النسخ المتزامن (CC) الذي يضغط بشكل متزامن كومة جافا. ولهذا السبب، يجب أن تتجنّب استخدام الأساليب التي لا تتوافق مع ضغط تجميع البيانات المهملة (مثل حفظ المؤشرات لبيانات مثيل الكائن). يُذكر أن هذا الأمر بالغ الأهمية للتطبيقات التي تستفيد من واجهة Java الأصلية (JNI). لمزيد من المعلومات، يُرجى الاطّلاع على مقالة منع مشاكل JNI.

منع مشاكل مؤشر JNI

إلا أن مؤشر JNI الخاص بـ ART أكثر صرامة من Dalvik. ومن الجيد على وجه الخصوص استخدام وضع CheckJNI لرصد المشاكل الشائعة. إذا كان تطبيقك يستخدم كود C/C++، فيجب عليك مراجعة المقالة التالية:

تصحيح الأخطاء في Android JNI باستخدام CheckJNI

جارٍ التحقّق من رمز JNI لمشاكل جمع البيانات غير المرغوب فيها

قد ينقل جامع النسخ المتزامن (CC) الكائنات في الذاكرة لضغطها. إذا كنت تستخدم التعليمات البرمجية C/C++ ، فلا تُجري عمليات غير متوافقة مع ضغط تجميع البيانات المهملة. لقد طوّرنا أداة CheckJNI لتحديد بعض المشاكل المحتملة (على النحو الموضّح في مقالة تغييرات المراجع المحلية في JNI في ICS).

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

  • إذا أجريت أي تغييرات على عناصر الصفيف المعروضة، عليك طلب دالة Release...ArrayElements() المناسبة عند الانتهاء للتأكّد من نسخ التغييرات التي أجريتها بشكل صحيح إلى كائن المصفوفة الأساسي.
  • عند تحرير عناصر مصفوفة الذاكرة، يجب استخدام الوضع المناسب، استنادًا إلى التغييرات التي أجريتها:
    • إذا لم تجرِ أي تغييرات على عناصر المصفوفة، يمكنك استخدام الوضع JNI_ABORT الذي يؤدي إلى تحرير الذاكرة بدون نسخ التغييرات إلى كائن الصفيف الأساسي.
    • إذا أجريت تغييرات على المصفوفة ولم تعُد بحاجة إلى المرجع، يمكنك استخدام الرمز 0 (الذي يعدّل كائن الصفيفة ويحرر نسخة الذاكرة).
    • إذا أجريت تغييرات على المصفوفة التي تريد تطبيقها وكنت تريد الاحتفاظ بنسخة المصفوفة، استخدِم JNI_COMMIT (التي تعدِّل كائن الصفيف الأساسي وتحتفظ بالنسخة).
  • عند استدعاء Release...ArrayElements()، يمكنك عرض المؤشر نفسه الذي تم عرضه في الأصل من قِبل Get...ArrayElements(). على سبيل المثال، ليس من الآمن إضافة المؤشر الأصلي (لفحص عناصر الصفيف المعروضة) ثم تمرير المؤشر المتزايد إلى Release...ArrayElements(). قد يؤدي تمرير هذا المؤشر المعدّل إلى إخلاء مساحة في الذاكرة الخاطئة، ما يؤدي إلى تلف الذاكرة.

معالجة الأخطاء

يعرض مؤشر JNI الخاص بأداة ART أخطاءً في عدد من الحالات التي لا يخطئ فيها دالفيك. (مرة أخرى، يمكنك اكتشاف العديد من هذه الحالات عن طريق الاختبار باستخدام CheckJNI).

على سبيل المثال، إذا تم استدعاء RegisterNatives باستخدام طريقة غير متوفّرة (ربما بسبب إزالة الطريقة باستخدام أداة مثل ProGuard)، سيعرض ART الآن القيمة NoSuchMethodError بشكل صحيح:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

يسجِّل ART أيضًا خطأً (يظهر في logcat) في حال استدعاء RegisterNatives بدون أيّ طرق:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

بالإضافة إلى ذلك، تعرض دالتا JNI GetFieldID() وGetStaticFieldID() الآن NoSuchFieldError بشكل صحيح بدلاً من عرض قيمة فارغة. وبالمثل، يتم الآن عرض GetMethodID() وGetStaticMethodID() NoSuchMethodError بشكل صحيح. وقد يؤدي هذا إلى إخفاقات CheckJNI بسبب الاستثناءات التي لم تتم معالجتها أو الاستثناءات التي يتم فرضها على مستخدمي Java للرموز البرمجية الأصلية. ويجعل ذلك من المهم على وجه الخصوص اختبار التطبيقات المتوافقة مع ART باستخدام وضع CheckJNI.

تتوقع ART من مستخدمي طرق JNI CallNonvirtual...Method() (مثل CallNonvirtualVoidMethod()) أن يستخدموا فئة التعريف الخاصة بالطريقة، وليس فئة فرعية، كما هو مطلوب في مواصفات JNI.

منع المشاكل المتعلقة بحجم تسلسل استدعاء الدوال البرمجية

كان لدى Dalvik حزم منفصلة للرمز الأصلي ولغة Java، مع حجم تكديس تلقائي من Java يبلغ 32 كيلوبايت وحجم مكدس تلقائي من نوع 1 ميغابايت. يحتوي ART على حزمة موحدة للحصول على منطقة محلية أفضل. في العادة، يجب أن يكون حجم حزمة ART Thread نفس الحجم تقريبًا في Dalvik. ومع ذلك، إذا حددت أحجام التكديس بوضوح، فقد تحتاج إلى إعادة النظر في تلك القيم للتطبيقات التي تعمل في ART.

  • في لغة Java، راجع الطلبات الواردة إلى الدالة الإنشائية Thread التي تحدِّد حجم تكديس صريح. على سبيل المثال، ستحتاج إلى زيادة الحجم في حالة حدوث StackOverflowError.
  • في لغة C/C++ ، راجِع استخدام pthread_attr_setstack() وpthread_attr_setstacksize() لسلاسل المحادثات التي تشغِّل أيضًا رمز Java عبر مؤشر JNI. في ما يلي مثال على الخطأ الذي يتم تسجيله عندما يحاول أحد التطبيقات استدعاء JNI AttachCurrentThread() عندما يكون حجم pthread صغيرًا جدًا:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

تغييرات نموذج العنصر

سمح Dalvik للفئات الفرعية بشكل غير صحيح بتجاوز الأساليب الخاصة بالحزمة. يصدر ART تحذيرًا في الحالات التالية:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

إذا كنت تنوي إلغاء طريقة فئة معيّنة في حزمة مختلفة، يجب تحديد الطريقة على أنّها public أو protected.

تضم "Object" الآن حقولاً خاصة. بالنسبة إلى التطبيقات التي تعتمِد على الحقول في التسلسل الهرمي للفئات، يجب أن تحرص على عدم محاولة النظر إلى حقول Object. على سبيل المثال، إذا كنت تقوم بالتكرار الهرمي للفئة كجزء من إطار عمل التسلسل، فتوقف عند

Class.getSuperclass() == java.lang.Object.class

بدلاً من المتابعة حتى تعرض الطريقة null.

يتلقى الخادم الوكيل InvocationHandler.invoke() الآن null في حال عدم توفُّر وسيطات بدلاً من صفيف فارغ. تم توثيق هذا السلوك سابقًا ولكن لم يتم التعامل معه بشكل صحيح في Dalvik. تواجه الإصدارات السابقة من Mockito صعوبات في هذا، لذا استخدم إصدار Mockito محدَّث عند الاختبار باستخدام ART.

إصلاح مشاكل تجميع AOT

يجب أن يعمل تجميع Java المسبق الوقت (AOT) من ART مع جميع أكواد Java القياسية. يتم تجميع البيانات باستخدام أداة dex2oat الخاصة بأداة ART . إذا واجهت أي مشاكل متعلّقة بـ dex2oat في وقت التثبيت، يُرجى إعلامنا بذلك (يُرجى الاطّلاع على الإبلاغ عن المشاكل) لنتمكّن من حلّها في أسرع وقت ممكن. هناك مشكلتان يجب أخذهما في الاعتبار:

  • يجري ART عملية تحقّق أكثر إحكامًا باستخدام رمز بايت أثناء التثبيت مقارنةً بأداة Dalvik. سيكون من الملائم استخدام الرمز البرمجي الذي تنشئه أدوات إصدار Android. ومع ذلك، قد تؤدي بعض أدوات ما بعد المعالجة (خاصةً الأدوات التي تستخدم إخفاء مفاتيح فك التشفير) إلى إنشاء ملفات غير صالحة يسمح "دالفيك" بها ولكن سيرفضها فريق ART. لقد عملنا مع مورّدي الأدوات للعثور على هذه المشاكل وحلّها. في كثير من الحالات، يمكن حلّ هذه المشاكل من خلال الحصول على أحدث إصدارات من أدواتك وإعادة إنشاء ملفات DEX.
  • في ما يلي بعض المشاكل المعتادة التي يتم الإبلاغ عنها من خلال أداة التحقّق من صحة ART:
    • تدفق التحكم غير صالح
    • غير متوازن monitorenter/monitorexit
    • حجم قائمة أنواع المعلمات ذات الطول 0
  • تعتمد بعض التطبيقات على تنسيق ملف .odex المثبَّت في /system/framework أو /data/dalvik-cache أو في دليل الإخراج المحسَّن في DexClassLoader. أصبحت هذه الملفات الآن ملفات ELF وليست صيغة موسّعة من ملفات DEX. يحاول ART اتّباع قواعد التسمية والقفل نفسها التي يتّبعها تطبيق Dalvik، يجب ألا تعتمد التطبيقات على تنسيق الملف، وقد يخضع التنسيق للتغيير بدون إشعار.

    ملاحظة: في نظام التشغيل Android 8.0 (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث، تم إيقاف دليل الإخراج المحسَّن DexClassLoader نهائيًا. لمزيد من المعلومات، يمكنك الاطّلاع على مستندات الدالة الإنشائية DexClassLoader().

الإبلاغ عن المشاكل

إذا واجهت أي مشاكل لا تتعلق بمشاكل مؤشر JNI للتطبيق، يمكنك الإبلاغ عنها عبر أداة تتبُّع مشاكل مشروع مفتوح المصدر لنظام Android على https://code.google.com/p/android/issues/list. تضمين "adb bugreport" ورابط إلى التطبيق في "متجر Google Play" في حال توفّره وإلا، أرفِق ملف APK يعيد إظهار المشكلة، إن أمكن. تجدر الإشارة إلى أنّ المشاكل (بما في ذلك المرفقات) مرئية للجميع.