يتعطّل تطبيق Android عند حدوث مخرج غير متوقع بسبب
استثناء أو إشارة لم تتم معالجتها. تطبيق مكتوب باستخدام Java أو Kotlin
إذا عرض استثناء غير معالَج، ممثلة
صف واحد (Throwable
). إنّ
إذا حدثت أعطال في تطبيق تمت كتابته باستخدام رمز الجهاز أو C++
الإشارة، مثل SIGSEGV
، أثناء تنفيذها.
عندما يتعطّل أحد التطبيقات، ينهي Android معالجة التطبيق ويعرض مربّع حوار. لإعلام المستخدم بأن التطبيق قد توقف، كما هو موضح في الشكل 1.
لا يلزم تشغيل التطبيق في المقدّمة حتى يتعطّل. أي تطبيق أو المكونات الأخرى مثل أجهزة استقبال البث أو مزودي المحتوى تعمل في الخلفية، يمكن أن يؤدي إلى تعطُّل التطبيق. هذه الأعطال هي أمرًا مربكًا للمستخدمين غالبًا بسبب عدم تفاعلهم بشكل نشط مع تطبيقك.
إذا واجه تطبيقك أعطالاً، يمكنك اتّباع الإرشادات الواردة في هذه الصفحة من أجل: تشخيص المشكلة وإصلاحها.
اكتشاف المشكلة
قد لا تعرف دائمًا أن المستخدمين يواجهون أعطالاً عند استخدامهم لتطبيقك. إذا سبق لك نشر التطبيق، يمكنك استخدام "مؤشرات 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 التي يمكنك من خلالها التحكّم في حجم الذاكرة على
الجهاز.
استثناءات الاتصال بالشبكات
وبما أن المستخدمين ينتقلون بشكل متكرر من وإلى شبكة الجوال أو شبكة 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 أن قيمة السلسلة هي قيمة غير فارغة، لذا تسمح لك باستخدام المرجع مباشرةً، بدون الحاجة إلى مشغّل اتصال آمن.
-
يتيح لك هذا العامل أن يشير إلى "إذا لم يكن الكائن فارغًا، فارجع كائن؛ وإلا، اعرض شيئًا آخر".
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.