يتعطل تطبيق Android كلما حدث خروج غير متوقع بسبب استثناء أو إشارة لم تتم معالجتها. يتعطل التطبيق الذي تمت كتابته باستخدام لغة Java أو Kotlin
إذا قدم استثناءً لم تتم معالجته، وتمثله الفئة
Throwable
. يتعطل التطبيق المكتوب باستخدام رمز آلة أو C++ في حال وجود إشارة لم تتم معالجتها، مثل SIGSEGV
، أثناء تنفيذه.
عندما يتعطل أحد التطبيقات، ينهي Android عملية التطبيق ويعرض مربع حوار لإعلام المستخدم بأن التطبيق قد توقف، كما هو موضح في الشكل 1.
لا يلزم تشغيل التطبيق في المقدمة حتى يتعطّل. إنّ أي مكوِّن تطبيق، حتى المكوّنات مثل أجهزة استقبال البث أو موفّري المحتوى التي تعمل في الخلفية، يمكن أن يتسبّب في تعطُّل التطبيق. غالبًا ما تكون هذه الأعطال مربكة للمستخدمين لأنهم لم يتفاعلوا بشكل نشط مع تطبيقك.
إذا كان تطبيقك يواجه أعطالاً، يمكنك اتّباع الإرشادات الواردة في هذه الصفحة لتشخيص المشكلة وحلّها.
اكتشاف المشكلة
قد لا تعلم دائمًا أنّ المستخدمين يواجهون أعطالاً عند استخدام تطبيقك. إذا سبق لك نشر تطبيقك، يمكنك استخدام "مؤشرات Android الحيوية" للاطّلاع على نِسب الأعطال لتطبيقك.
مؤشرات Android الحيوية
يمكن أن تساعدك "مؤشرات Android الحيوية" في مراقبة نسبة أعطال تطبيقك وتحسينها. تقيس ميزة "مؤشرات Android الحيوية" العديد من معدّلات الأعطال:
- نسبة الأعطال: النسبة المئوية للمستخدمين النشطين يوميًا الذين واجهوا أي نوع من الأعطال.
نسبة الأعطال التي لاحظها المستخدمون: تعرض هذه النسبة النسبة المئوية للمستخدمين النشطين يوميًا الذين واجهوا عطلاً واحدًا على الأقل أثناء استخدامهم لتطبيقك بشكل نشط (أعطال لاحظها المستخدمون). يُعتبر التطبيق قيد الاستخدام النشط إذا كان يعرض أي نشاط أو ينفّذ أي خدمة تعمل في المقدّمة.
نسبة الأعطال المتعددة: النسبة المئوية للمستخدمين النشطين يوميًا الذين واجهوا عُطلَين على الأقل.
المستخدم النشط يوميًا هو مستخدم فريد يستخدم تطبيقك في يوم واحد على جهاز واحد، ويمكن أن يكون خلال عدة جلسات. إذا كان أحد المستخدمين يستخدم تطبيقك على أكثر من جهاز في يوم واحد، سيساهم كل جهاز في عدد المستخدمين النشطين لذلك اليوم. إذا استخدم عدة مستخدمين الجهاز نفسه في يوم واحد، يتم احتساب ذلك كمستخدم نشط واحد.
إنّ نسبة الأعطال التي لاحظها المستخدمون هي مؤشرات أساسية، ما يعني أنّها تؤثر في قابلية اكتشاف تطبيقك على Google Play. وهي مهمة لأنّ الأعطال التي تحسبها تحدث دائمًا عندما يتفاعل المستخدم مع التطبيق، ما يتسبب في أكثر اضطرابًا.
حدّد Play اثنين من الحدّ الأدنى للسلوك السيئ في هذا المقياس:
- الحد الأدنى العام لخلل الأداء: تتعرّض 1.09% على الأقل من المستخدمين النشطين يوميًا لتعطل ملحوظ في جميع طُرز الأجهزة.
- الحد الأدنى لخلل الأداء على مستوى الجهاز: تتعرّض 8% على الأقل من المستخدمين النشطين يوميًا لعُطل لاحظه المستخدمون، لطراز واحد من الأجهزة.
إذا تجاوز تطبيقك الحدّ الأقصى العام لخلل الأداء، من المحتمل أن تنخفض فرص العثور عليه على جميع الأجهزة. إذا تجاوز تطبيقك حدّ السلوك السيئ لكل جهاز على بعض الأجهزة، من المرجَّح أن تنخفض فرص العثور عليه على هذه الأجهزة، وقد يظهر تحذير في بطاقة بيانات المتجر.
يمكن لـ "مؤشرات Android الحيوية" تنبيهك عبر Play Console عندما يعرض تطبيقك أعطالاً مفرطة.
لمزيد من المعلومات حول الطريقة التي يجمع بها Google Play بيانات "مؤشرات Android الحيوية"، يُرجى الاطّلاع على مستندات Play Console.
تشخيص الأعطال
بعد تحديد أنّ تطبيقك يبلغ عن الأعطال، تتمثل الخطوة التالية في تشخيص الأعطال. قد يكون حلّ الأعطال أمرًا صعبًا. ومع ذلك، إذا تمكنت من تحديد السبب الجذري للأعطال، فمن المرجح أن تتمكن من إيجاد حل له.
هناك العديد من المواقف التي يمكن أن تتسبب في حدوث عطل في تطبيقك. وهناك بعض الأسباب الواضحة، مثل التحقق من وجود قيمة فارغة أو سلسلة فارغة، والبعض الآخر يكون أكثر دقة، مثل تمرير الوسيطات غير الصالحة إلى واجهة برمجة تطبيقات أو حتى تفاعلات متعددة السلاسل معقدة.
تؤدي الأعطال على نظام التشغيل Android إلى تقرير تتبُّع تسلسل استدعاء الدوال البرمجية، وهو عبارة عن لقطة لتسلسل الدوال المضمَّنة التي يستدعيها في برنامجك حتى لحظة تعطُّلها. ويمكنك عرض عمليات تتبُّع تسلسل استدعاء الدوال البرمجية من خلال مؤشرات Android الحيوية.
كيفية قراءة عمليات تتبُّع تسلسل استدعاء الدوال البرمجية
تتمثل الخطوة الأولى لإصلاح عطل ما في تحديد مكان حدوثه. يمكنك استخدام تتبُّع تسلسل استدعاء الدوال البرمجية المتاحة في تفاصيل التقرير إذا كنت تستخدم Play Console أو ناتج أداة 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 حيث يمكنك التحكم في حجم الذاكرة على الجهاز.
استثناءات الاتصال بالشبكات
وبما أنّ المستخدمين يدخلون إلى تغطية شبكة الجوّال أو شبكة WiFi وتخرج منها بشكل متكرر، يجب عدم التعامل عادةً مع استثناءات الشبكة في التطبيقات على أنّها أخطاء، بل يجب التعامل معها كظروف تشغيل عادية تحدث بشكل غير متوقّع.
إذا كنت بحاجة إلى إعادة إنتاج استثناء للشبكة، مثل UnknownHostException
، يمكنك محاولة تفعيل وضع الطيران أثناء محاولة التطبيق استخدام الشبكة.
ويتوفّر خيار آخر وهو خفض جودة الشبكة في المحاكي من خلال اختيار محاكاة سرعة الشبكة و/أو تأخير الشبكة. يمكنك استخدام إعدادات
السرعة ووقت الاستجابة في مدير AVD أو يمكنك بدء المحاكي
بعلامتَي -netdelay
و-netspeed
، كما هو موضّح في
مثال سطر الأوامر التالي:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
يحدد هذا المثال تأخيرًا قدره 20 ثانية في جميع طلبات الشبكة، فضلاً عن سرعة تحميل وتنزيل تبلغ 14.4 كيلوبت في الثانية. لمزيد من المعلومات حول خيارات سطر الأوامر للمحاكي، يُرجى الاطّلاع على بدء المحاكي من سطر الأوامر.
القراءة باستخدام Logcat
بعد أن تتمكّن من معرفة خطوات إعادة إظهار العطل، يمكنك استخدام أداة مثل
logcat
للحصول على مزيد من المعلومات.
سيعرض لك ناتج Logcat رسائل السجل الأخرى التي طبعتها،
إلى جانب رسائل أخرى من النظام. لا تنسَ إيقاف أي عبارات Log
إضافية
أضفتها، لأنّ طباعتها تهدر وحدة المعالجة المركزية (CPU) والبطارية أثناء تشغيل التطبيق.
منع الأعطال الناتجة عن استثناءات المؤشر الفارغ
تحدث استثناءات المؤشر الفارغة (التي يتم تحديدها حسب نوع خطأ وقت التشغيل
NullPointerException
) عند محاولة الوصول إلى عنصر فارغ، وعادةً ما يتم ذلك من خلال استدعاء طرقه أو الوصول إلى أعضائه. استثناءات المؤشر الفارغة هي السبب الأكبر في أعطال التطبيق على Google Play. الغرض من القيمة الخالية هو الإشارة إلى أن الكائن مفقود - على سبيل المثال، لم يتم إنشاؤه أو تعيينه بعد. لتجنب استثناءات المؤشر الفارغ، تحتاج إلى التأكد من أن مراجع الكائن التي تعمل بها غير فارغة قبل استدعاء الطرق بها أو محاولة الوصول إلى أعضائها. إذا كان مرجع الكائن فارغًا، تعامل مع هذه الحالة جيدًا (على سبيل المثال، اخرج من طريقة ما قبل تنفيذ أي عمليات في مرجع الكائن واكتب المعلومات في سجل تصحيح الأخطاء).
يمكنك الاعتماد على بيئة التطوير المتكاملة (IDE) أو نوع الكائن للدلالة على قابلية استخدام القيم الفارغة.
لغة البرمجة Java
تنطبق الأقسام التالية على لغة البرمجة Java.
تجميع تحذيرات الوقت
يمكنك إضافة تعليقات توضيحية إلى مَعلمات الطرق والقيم المعروضة باستخدام @Nullable
و@NonNull
لتلقّي تحذيرات متعلّقة بوقت التجميع من بيئة التطوير المتكاملة. تطلب منك هذه التحذيرات توقع ظهور عنصر قابل للقيم:
عمليات التحقق الفارغة هذه مخصصة للكائنات التي تعرف أنها قد تكون فارغة. ويشكّل الاستثناء لكائن
@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 أنّ قيمة السلسلة غير فارغة، لذلك يسمح لك باستخدام المرجع مباشرةً، بدون الحاجة إلى عامل تشغيل الاتصال الآمن.
-
يتيح لك عامل التشغيل هذا تحديد "إذا كان الكائن غير فارغ، يُرجع الكائن؛ وإلا، فسيتم إرجاع شيء آخر".
val length = string?.length ?: 0
لا يزال بإمكانك الحصول على NullPointerException
في Kotlin. فيما يلي أكثر الحالات
شيوعًا:
- عند طرح علامة
NullPointerException
بشكل واضح - عند استخدام
عامل التشغيل
!!
لتأكيد القيمة الفارغ. يحوِّل عامل التشغيل هذا أي قيمة إلى نوع غير فارغ، ما يؤدي إلى عرضNullPointerException
إذا كانت القيمة فارغة. - عند الوصول إلى مرجع فارغ لنوع النظام الأساسي.
أنواع الأنظمة الأساسية
أنواع الأنظمة الأساسية هي تعريفات عناصر صادرة من Java. تتم معالجة هذه الأنواع بشكل خاص؛ لا يتم فرض عمليات التحقق الخالية من القيمة، لذا فإن الضمان غير الفارغ هو نفسه المستخدم في 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.