الأعطال

يتعطّل تطبيق Android عند حدوث مخرج غير متوقع بسبب استثناء أو إشارة لم تتم معالجتها. تطبيق مكتوب باستخدام Java أو Kotlin إذا عرض استثناء غير معالَج، ممثلة صف واحد (Throwable). إنّ إذا حدثت أعطال في تطبيق تمت كتابته باستخدام رمز الجهاز أو C++ الإشارة، مثل SIGSEGV، أثناء تنفيذها.

عندما يتعطّل أحد التطبيقات، ينهي Android معالجة التطبيق ويعرض مربّع حوار. لإعلام المستخدم بأن التطبيق قد توقف، كما هو موضح في الشكل 1.

تعطُّل في التطبيق على جهاز Android

الشكل 1. تعطُّل تطبيق على جهاز Android

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

إذا واجه تطبيقك أعطالاً، يمكنك اتّباع الإرشادات الواردة في هذه الصفحة من أجل: تشخيص المشكلة وإصلاحها.

اكتشاف المشكلة

قد لا تعرف دائمًا أن المستخدمين يواجهون أعطالاً عند استخدامهم لتطبيقك. إذا سبق لك نشر التطبيق، يمكنك استخدام "مؤشرات Android الحيوية" للاطّلاع على نِسب أعطال تطبيقك.

مؤشرات Android الحيوية

يمكن أن تساعدك "مؤشرات Android الحيوية" في مراقبة نسبة أعطال تطبيقك وتحسينها. تقيس ميزة "مؤشرات Android الحيوية" نسب أعطال متعددة:

  • نسبة الأعطال: النسبة المئوية للمستخدمين النشطين يوميًا الذين واجهوا أي نوع من الأعطال.
  • نسبة الأعطال التي لاحظها المستخدمون: النسبة المئوية للمستخدمين النشطين يوميًا الذين واجهوا عطلاً واحدًا على الأقل أثناء استخدامهم لتطبيقك بشكل نشط (تعطُّل لاحظه المستخدم). يُعد التطبيق قيد الاستخدام النشط ما إذا كانت تعرض أي نشاط أو تنفّذ التي تعمل في المقدّمة.

  • نسبة الأعطال المتعددة: النسبة المئوية للمستخدمين النشطين يوميًا الذين شهدت عطلين على الأقل.

المستخدم النشط يوميًا هو مستخدم فردي يستخدم تطبيقك. في يوم واحد على جهاز واحد، وربما عبر جلسات متعددة. إذا كان المستخدم يستخدم تطبيقك على أكثر من جهاز في يوم واحد، سيساهم كل جهاز في عدد المستخدمين النشطين لذلك اليوم. إذا استخدم عدة مستخدمين الجهاز نفسه في يوم واحد، يتم احتسابه كمستخدم نشط واحد.

إنّ نسبة الأعطال التي لاحظها المستخدمون هي أحد مؤشرات الأداء الأساسية، ما يعني أنّها تؤثر في قابلية اكتشاف تطبيقك على Google Play. وهذا مهم لأن الأعطال التي تحدث الأعداد دائمًا عندما يتفاعل المستخدم مع التطبيق، مما يؤدي إلى اضطرابك.

حدّد Google Play معيارَين لتحديد الأداء السيئ استنادًا إلى هذا المقياس:

  • الحد الأدنى العام للأداء السيئ: ما لا يقل عن 1.09% من المستخدمين النشطين يوميًا حدوث أعطال لاحظها المستخدمون في جميع طُرز الأجهزة.
  • معيار تحديد الأداء السيئ حسب الجهاز:% 8 على الأقل من المستخدمين النشطين يوميًا حدوث عُطل ملحوظ لطراز جهاز واحد

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

بإمكان "مؤشرات Android الحيوية" تنبيهك عبر Play Console عندما يعرض تطبيقك أعطالاً زائدة

للحصول على معلومات حول كيفية جمع Google Play لبيانات "مؤشرات Android الحيوية"، يُرجى الاطّلاع على Play Console التوثيق.

تشخيص الأعطال

بعد أن ترصد حدوث أعطال في تطبيقك، الخطوة التالية هي تشخيصهم. قد يكون حلّ الأعطال صعبًا. ومع ذلك، إذا تمكّنت من تحديد السبب الجذري العطل، فمن المرجح أن تتمكن من إيجاد حل له.

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

تؤدي الأعطال على Android إلى إنشاء عملية تتبُّع تسلسُل استدعاء دوال برمجية، وهي عبارة عن لقطة لتسلسل استدعاء الدوال البرمجية. الدوال المتداخلة التي تم استدعاؤها في برنامجك حتى لحظة تعطُّلها. يمكنك عرض عمليات تتبُّع تسلسل استدعاء الدوال البرمجية لتعطُّل التطبيق في مؤشرات Android الحيوية.

كيفية قراءة تقرير تتبُّع تسلسل استدعاء الدوال البرمجية

الخطوة الأولى لإصلاح العطل هي تحديد مكان حدوثه. يمكنك استخدام تتبُّع تسلسل استدعاء الدوال البرمجية المتوفّر في تفاصيل التقرير في حال استخدام Play وحدة التحكم أو نتائج أداة logcat. إذا كنت إذا لم يتوفر تتبع تسلسُل استدعاء الدوال البرمجية، فينبغي عليك إعادة إنتاج العطل محليًا، إما عن طريق اختبار التطبيق يدويًا أو الوصول إلى المستخدمين المتأثرين، وإعادة إنتاجه أثناء استخدام Logcat.

يوضِّح التتبع التالي مثالاً على تعطُّل في تطبيق تمت كتابته باستخدام Java. لغة البرمجة:

--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException: crash sample
at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
at android.view.View.performClick(View.java:6134)
at android.view.View$PerformClick.run(View.java:23965)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6440)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
--------- beginning of system

يعرض تتبُّع تسلسل استدعاء الدوال البرمجية جزأين من المعلومات المهمة لتصحيح أخطاء التعطُّل:

  • نوع الاستثناء الذي تم طرحه
  • قسم الرمز الذي تم فيه طرح الاستثناء.

وعادةً ما يكون نوع الاستثناء الذي يتم طرحه هو تلميح قوي جدًا بشأن ما حدث خطأ. انظر عما إذا كانت IOException، OutOfMemoryError, أو أي شيء آخر، ونعثر على الوثائق المتعلقة بفئة الاستثناء.

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

تعمل عمليات تتبع تسلسل استدعاء الدوال البرمجية للتطبيقات التي تحتوي على رمزَي C وC++ بالطريقة نفسها إلى حدٍ كبير.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/foo/bar:10/123.456/78910:user/release-keys'
ABI: 'arm64'
Timestamp: 2020-02-16 11:16:31+0100
pid: 8288, tid: 8288, name: com.example.testapp  >>> com.example.testapp <<<
uid: 1010332
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
    x0  0000007da81396c0  x1  0000007fc91522d4  x2  0000000000000001  x3  000000000000206e
    x4  0000007da8087000  x5  0000007fc9152310  x6  0000007d209c6c68  x7  0000007da8087000
    x8  0000000000000000  x9  0000007cba01b660  x10 0000000000430000  x11 0000007d80000000
    x12 0000000000000060  x13 0000000023fafc10  x14 0000000000000006  x15 ffffffffffffffff
    x16 0000007cba01b618  x17 0000007da44c88c0  x18 0000007da943c000  x19 0000007da8087000
    x20 0000000000000000  x21 0000007da8087000  x22 0000007fc9152540  x23 0000007d17982d6b
    x24 0000000000000004  x25 0000007da823c020  x26 0000007da80870b0  x27 0000000000000001
    x28 0000007fc91522d0  x29 0000007fc91522a0
    sp  0000007fc9152290  lr  0000007d22d4e354  pc  0000007cba01b640

backtrace:
  #00  pc 0000000000042f89  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::Crasher::crash() const)
  #01  pc 0000000000000640  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::runCrashThread())
  #02  pc 0000000000065a3b  /system/lib/libc.so (__pthread_start(void*))
  #03  pc 000000000001e4fd  /system/lib/libc.so (__start_thread)

إذا لم تظهر لك معلومات على مستوى الفئة والدالة في عمليات تتبُّع تسلسل استدعاء الدوال البرمجية الأصلية، قد تحتاج إلى إنشاء ملف أصلي لتصحيح أخطاء الترميز وتحميله إلى Google Play Console لمزيد من المعلومات، يُرجى مراجعة إزالة تشويش عمليات تتبُّع تسلسُل استدعاء الدوال البرمجية لتعطُّل التطبيق للحصول على معلومات عامة حول الأعطال الأصلية، يُرجى الاطّلاع على تشخيص الأعطال الأصلية:

نصائح لإعادة إنتاج عطل

من المحتمل ألا يمكنك إعادة إنتاج المشكلة بالكامل من خلال بدء أو توصيل جهازك بجهاز الكمبيوتر. بيئات التطوير وتميل إلى امتلاك موارد أكثر، مثل النطاق الترددي والذاكرة وسعة التخزين. يمكنك استخدام نوع الاستثناء لتحديد المورد المحدود هناك علاقة بين إصدار Android أو نوع الجهاز أو نظام تشغيل تطبيقك .

أخطاء الذاكرة

إذا كان لديك OutOfMemoryError، فقد يمكنك إنشاء محاكي ذي سعة ذاكرة منخفضة للاختبار باستخدامه. الشكل 2 يعرض إعدادات مدير AVD التي يمكنك من خلالها التحكّم في حجم الذاكرة على الجهاز.

إعدادات الذاكرة في &quot;مدير AVD&quot;

الشكل 2. إعدادات الذاكرة في "مدير AVD"

استثناءات الاتصال بالشبكات

وبما أن المستخدمين ينتقلون بشكل متكرر من وإلى شبكة الجوال أو شبكة WiFi يجب ألا يتم التعامل مع استثناءات شبكة التطبيقات عادةً على أنها أخطاء، ولكن هي ظروف تشغيل عادية تحدث بشكل غير متوقع.

إذا كنت تريد إعادة إنتاج أحد استثناءات الشبكة، مثل UnknownHostException، ثم جرِّب تشغيل وضع الطيران أثناء محاولة التطبيق استخدام الشبكة.

وهناك خيار آخر وهو خفض جودة الشبكة في المحاكي من خلال اختيار محاكاة سرعة الشبكة و/أو تأخير الشبكة. يمكنك استخدام صفحة إعدادات السرعة ووقت الاستجابة في مدير AVD، أو يمكنك تشغيل المحاكي بعلامة -netdelay و-netspeed كما هو موضح في ما يلي مثال على سطر الأوامر:

emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm

يحدّد هذا المثال مهلة 20 ثانية لجميع طلبات الشبكة وعملية التحميل وسرعة تنزيل تبلغ 14.4 كيلوبت في الثانية. لمزيد من المعلومات حول خيارات سطر الأوامر بالنسبة إلى المحاكي، راجع ابدأ تشغيل المحاكي من سطر الأوامر.

القراءة باستخدام Logcat

بعد أن تصبح لديك خطوات إعادة إنتاج العطل، يمكنك استخدام أداة مثل logcat للحصول على مزيد من المعلومات.

سيعرض لك ناتج Logcat رسائل السجلّ الأخرى التي طبعتها، مع الآخرين من النظام. لا تنسَ إيقاف أي إضافات تصريحات Log التي لأن طباعتها تؤدي إلى إهدار وحدة المعالجة المركزية (CPU) والبطارية أثناء تحميل تطبيقك الجري.

منع الأعطال الناتجة عن استثناءات المؤشر الفارغ

استثناءات المؤشر الخالية (يتم تحديدها حسب نوع خطأ وقت التشغيل NullPointerException) عند محاولة الوصول إلى كائن null، عادة من خلال استدعاء طرقه أو الوصول إلى أعضائه. مؤشر فارغ هي أهم سبب لأعطال التطبيق على Google Play. إن الغرض من فالقيمة "فارغ" تعني أن الكائن مفقود، على سبيل المثال، لم يتم تم إنشاؤه أو تعيينه حتى الآن. لتجنب استثناءات المؤشر الفارغ، يجب التأكد من من أن مراجع الكائن التي تعمل بها غير خالية قبل استدعاء أو محاولة الوصول إلى أعضائها. إذا كان مرجع الكائن null، تعامل مع هذه الحالة جيدًا (على سبيل المثال، الخروج من طريقة قبل تنفيذ أي عمليات على مرجع الكائن وكتابة المعلومات في سجل تصحيح الأخطاء).

لأنك لا تريد أن تكون لديك عمليات تحقق فارغة لكل معلمة في كل طريقة يمكنك الاعتماد على بيئة التطوير المتكاملة (IDE) أو على نوع الكائن للإشارة إلى قبول القيم الفارغة.

لغة برمجة Java

تنطبق الأقسام التالية على لغة برمجة Java.

تحذيرات وقت التجميع

ضع تعليقات توضيحية لأساليبك والقيم المردودة مع @Nullable و @NonNull لتلقي وقت التجميع تحذيرات من بيئة التطوير المتكاملة (IDE). وتطلب منك هذه التحذيرات توقُّع كائن قابل للقيم الفارغة:

تحذير بشأن استثناء المؤشر الفارغ

عمليات التحقق هذه فارغة للكائنات التي تعرف أنها يمكن أن تكون فارغة. هناك استثناء في يشير الكائن @NonNull إلى وجود خطأ في الرمز البرمجي يجب إصلاحه الذي تم تناوله.

تجميع أخطاء الوقت

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

Kotlin

في Kotlin، إمكانية قبول القيم الفارغة جزءًا من نظام الكتابة. على سبيل المثال، يجب تعريف المتغير من البداية على أنها قابلة للقيم الفارغة أو غير قابلة للقيم. يتم تمييز الأنواع القابلة للحذف برمز ?:

// non-null
var s: String = "Hello"

// null
var s: String? = "Hello"

لا يمكن تعيين قيمة فارغة للمتغيرات غير القابلة للقيم الفارغة ومتغيرات قابلة للقيم يجب التحقّق منها للتأكّد من توافقها مع القيم الفارغة قبل استخدامها على أنّها غير خالية.

إذا كنت لا تريد التحقّق من القيم الفارغة بشكل صريح، يمكنك استخدام المكالمة الآمنة ?.. العامل:

val length: Int? = string?.length  // length is a nullable int
                                   // if string is null, then length is null

وكأفضل الممارسات، تأكد من معالجة الحالة الفارغة لكائن قابل للقيم الفارغة، أو قد يواجه تطبيقك حالات غير متوقعة. في حال عدم تعطُّل تطبيقك بعد الآن مع "NullPointerException"، لن تعرف أنّ هذه الأخطاء متوفّرة.

في ما يلي بعض الطرق للتحقّق من القيم الفارغة:

  • if عمليات تحقّق

    val length = if(string != null) string.length else 0
    

    وبسبب الإرسال الذكي والتحقق الفارغ، يعرف المحول البرمجي لـ Kotlin أن قيمة السلسلة هي قيمة غير فارغة، لذا تسمح لك باستخدام المرجع مباشرةً، بدون الحاجة إلى مشغّل اتصال آمن.

  • ?: عامل تشغيل Elvis

    يتيح لك هذا العامل أن يشير إلى "إذا لم يكن الكائن فارغًا، فارجع كائن؛ وإلا، اعرض شيئًا آخر".

    val length = string?.length ?: 0
    

لا يزال بإمكانك الحصول على NullPointerException في Kotlin. فيما يلي هي الأكثر المواقف الشائعة:

  • عند رمي NullPointerException بشكل صريح
  • عند استخدام عامل تشغيل !! لتأكيد فارغ. يحول عامل التشغيل هذا أي قيمة إلى نوع غير فارغ، مما يؤدي NullPointerException إذا كانت القيمة خالية.
  • عند الوصول إلى مرجع فارغ لنوع المنصة

أنواع المنصات

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

val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
                                                       // exception if item == null

تعتمد Kotlin على استنتاج الكتابة عندما يتم تعيين قيمة نظام أساسي إلى لغة Kotlin. متغير، أو يمكنك تحديد النوع المتوقع. إن أفضل طريقة لضمان إن حالة إمكانية قبول القيم الفارغة لمرجع يأتي من Java هي استخدام قابلية القيم الفارغة (مثل @Nullable) في رمز Java. المحول البرمجي لـ Kotlin هذه المراجع كأنواع فعلية قابلة للقيم الفارغة أو غير قابلة للقيم، وليس والمنصات المختلفة.

تمت إضافة تعليقات توضيحية إلى واجهات برمجة تطبيقات Java Jetpack باستخدام @Nullable أو @NonNull حسب الحاجة. كما تم اتباع نهج مماثل في حزمة تطوير البرامج (SDK) لنظام التشغيل Android 11: سيتم تمثيل الأنواع الواردة من حزمة SDK هذه والمستخدمة في Kotlin على النحو التالي: الأنواع الصحيحة القابلة للقيم الفارغة أو غير القابلة للقيم.

وبسبب نظام كتابة Kotlin، لاحظنا انخفاضًا كبيرًا في عدد التطبيقات NullPointerException عطل. على سبيل المثال، شهد تطبيق Google Home نسبة %30 انخفاض في عدد الأعطال الناتجة عن استثناءات المؤشر الفارغ خلال العام الذي تم فيه وقمت بترحيل تطوير الميزات الجديدة إلى لغة Kotlin.