هر زمان که یک خروج غیرمنتظره ناشی از یک استثنا یا سیگنال کنترل نشده باشد، یک برنامه اندروید از کار می افتد. برنامهای که با استفاده از جاوا یا کاتلین نوشته میشود، در صورت ایجاد یک استثنا کنترلنشده، که توسط کلاس Throwable
نمایش داده میشود، از کار میافتد. برنامهای که با استفاده از کد ماشین یا ++C نوشته شده است، در صورت وجود یک سیگنال کنترل نشده، مانند SIGSEGV
، در حین اجرای آن از کار میافتد.
همانطور که در شکل 1 نشان داده شده است، هنگامی که یک برنامه از کار می افتد، Android روند برنامه را خاتمه می دهد و یک گفتگو نمایش داده می شود تا به کاربر اطلاع دهد که برنامه متوقف شده است.
لازم نیست یک برنامه در پیش زمینه اجرا شود تا از کار بیفتد. هر مؤلفه برنامه، حتی مؤلفه هایی مانند گیرنده های پخش یا ارائه دهندگان محتوا که در پس زمینه در حال اجرا هستند، می توانند باعث از کار افتادن برنامه شوند. این خرابیها اغلب برای کاربران گیجکننده هستند، زیرا آنها به طور فعال با برنامه شما درگیر نیستند.
اگر برنامه شما با خرابی مواجه است، میتوانید از راهنمایی در این صفحه برای تشخیص و رفع مشکل استفاده کنید.
مشکل را تشخیص دهید
ممکن است همیشه ندانید که کاربران شما هنگام استفاده از برنامه شما با خرابی مواجه می شوند. اگر قبلاً برنامه خود را منتشر کرده اید، می توانید از Android vitals برای مشاهده نرخ خرابی برنامه خود استفاده کنید.
حیاتی اندروید
Android vitals می تواند به شما در نظارت و بهبود نرخ خرابی برنامه کمک کند. Android vitals چندین نرخ خرابی را اندازه گیری می کند:
- نرخ خرابی: درصدی از کاربران فعال روزانه شما که هر نوع خرابی را تجربه کردهاند.
نرخ خرابی درک شده توسط کاربر: درصدی از کاربران فعال روزانه شما که حداقل یک بار خرابی را در حین استفاده فعال از برنامه شما تجربه کرده اند (یک خرابی درک شده توسط کاربر). اگر برنامهای در حال نمایش فعالیت یا اجرای خدمات پیشزمینه باشد، در حال استفاده فعال در نظر گرفته میشود.
نرخ خرابی چندگانه: درصدی از کاربران فعال روزانه شما که حداقل دو بار خرابی را تجربه کردهاند.
کاربر فعال روزانه یک کاربر منحصر به فرد است که از برنامه شما در یک روز در یک دستگاه واحد استفاده می کند، به طور بالقوه در چندین جلسه. اگر کاربر در یک روز از برنامه شما در بیش از یک دستگاه استفاده کند، هر دستگاه به تعداد کاربران فعال آن روز کمک خواهد کرد. اگر چند کاربر در یک روز از یک دستگاه استفاده کنند، این به عنوان یک کاربر فعال حساب می شود.
نرخ خرابی درک شده توسط کاربر یک امر حیاتی اصلی است که بر قابلیت کشف برنامه شما در Google Play تأثیر می گذارد. این مهم است زیرا خرابی هایی که شمارش می کند همیشه زمانی رخ می دهد که کاربر با برنامه درگیر است و بیشترین اختلال را ایجاد می کند.
Play دو آستانه رفتار بد در این معیار تعریف کرده است:
- آستانه رفتار بد کلی: حداقل 1.09٪ از کاربران فعال روزانه یک خرابی درک شده توسط کاربر را در همه مدلهای دستگاه تجربه میکنند.
- آستانه رفتار بد برای هر دستگاه: حداقل 8٪ از کاربران فعال روزانه یک خرابی درک شده توسط کاربر را برای یک مدل دستگاه تجربه می کنند.
اگر برنامه شما از آستانه کلی رفتار بد فراتر رود، احتمالاً در همه دستگاهها کمتر قابل شناسایی است. اگر برنامه شما از آستانه رفتار بد برای هر دستگاه در برخی دستگاهها فراتر رود، احتمالاً در آن دستگاهها کمتر قابل شناسایی است و ممکن است هشداری در فهرست فروشگاه شما نشان داده شود.
Android vitals می تواند از طریق کنسول Play به شما هشدار دهد زمانی که برنامه شما خرابی های بیش از حد را نشان می دهد.
برای اطلاعات در مورد نحوه جمعآوری دادههای حیاتی Android توسط Google Play، به مستندات کنسول Play مراجعه کنید.
خرابی ها را تشخیص دهید
هنگامی که متوجه شدید که برنامه شما در حال گزارش خرابی است، گام بعدی تشخیص آنهاست. حل خرابی ها می تواند دشوار باشد. با این حال، اگر بتوانید علت اصلی سقوط را شناسایی کنید، به احتمال زیاد می توانید راه حلی برای آن پیدا کنید.
موقعیت های زیادی وجود دارد که می تواند باعث خرابی برنامه شما شود. برخی از دلایل واضح هستند، مانند بررسی مقدار تهی یا رشته خالی، اما برخی دیگر ظریف تر هستند، مانند ارسال آرگومان های نامعتبر به یک API یا حتی تعاملات چند رشته ای پیچیده.
خرابیها در اندروید یک ردیابی پشته ایجاد میکنند، که یک عکس فوری از توالی توابع تودرتو است که در برنامه شما تا لحظه خراب شدن خوانده میشود. میتوانید ردیابی پشته خرابی را در Android vitals مشاهده کنید.
نحوه خواندن ردیابی پشته
اولین قدم برای رفع خرابی، شناسایی مکانی است که در آن اتفاق می افتد. اگر از Play Console یا خروجی ابزار logcat استفاده می کنید، می توانید از stack trace موجود در جزئیات گزارش استفاده کنید. اگر ردیابی پشته ای در دسترس ندارید، باید با آزمایش دستی برنامه یا با دسترسی به کاربران آسیب دیده، خرابی را به صورت محلی بازتولید کنید و هنگام استفاده از logcat آن را بازتولید کنید.
ردیابی زیر نمونه ای از خرابی برنامه ای را نشان می دهد که با استفاده از زبان برنامه نویسی جاوا نوشته شده است:
--------- 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
یا چیز دیگری است و مستندات مربوط به کلاس استثنا را پیدا کنید.
کلاس، روش، فایل و شماره خط فایل منبع که در آن استثنا پرتاب شده است در خط دوم یک ردیابی پشته نشان داده شده است. برای هر تابعی که فراخوانی شد، خط دیگری سایت فراخوانی قبلی را نشان می دهد (به نام قاب پشته). با بالا رفتن از پشته و بررسی کد، ممکن است مکانی را پیدا کنید که مقدار نادرستی را ارسال می کند. اگر کد شما در ردیابی پشته ظاهر نمی شود، احتمالاً در جایی، یک پارامتر نامعتبر را به یک عملیات ناهمزمان منتقل کرده اید. اغلب میتوانید با بررسی هر خط از stack trace، پیدا کردن کلاسهای API که استفاده کردهاید، و تأیید درست بودن پارامترهایی که پاس دادهاید، و اینکه آن را از جایی که مجاز است فراخوانی کردهاید، بفهمید که چه اتفاقی افتاده است.
پشتههای ردیابی برنامههای دارای کد 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 آپلود کنید. برای اطلاعات بیشتر، Deobfuscate ردیابی پشته خرابی را ببینید. برای اطلاعات کلی در مورد خرابی های بومی، به تشخیص خرابی های بومی مراجعه کنید.
نکاتی برای بازتولید تصادف
این امکان وجود دارد که فقط با راه اندازی یک شبیه ساز یا اتصال دستگاه خود به رایانه، نتوانید مشکل را کاملاً بازتولید کنید. محیط های توسعه معمولا منابع بیشتری مانند پهنای باند، حافظه و ذخیره سازی دارند. از نوع استثنا برای تعیین منبعی که کمیاب است استفاده کنید یا بین نسخه Android، نوع دستگاه یا نسخه برنامه خود ارتباط پیدا کنید.
خطاهای حافظه
اگر OutOfMemoryError
دارید، می توانید یک شبیه ساز با ظرفیت حافظه کم برای آزمایش ایجاد کنید. شکل 2 تنظیمات مدیر AVD را نشان می دهد که در آن می توانید میزان حافظه دستگاه را کنترل کنید.
استثناهای شبکه
از آنجایی که کاربران اغلب به داخل و خارج از پوشش شبکه تلفن همراه یا وای فای می روند، در یک برنامه استثنائات شبکه معمولاً نباید به عنوان خطا در نظر گرفته شوند، بلکه باید به عنوان شرایط عملیاتی عادی که به طور غیرمنتظره رخ می دهند تلقی شوند.
اگر نیاز به بازتولید یک استثنای شبکه، مانند UnknownHostException
دارید، سپس سعی کنید حالت هواپیما را در حالی که برنامه شما تلاش می کند از شبکه استفاده کند، روشن کنید.
گزینه دیگر کاهش کیفیت شبکه در شبیه ساز با انتخاب شبیه سازی سرعت شبکه و/یا تاخیر شبکه است. میتوانید از تنظیمات Speed و Latency در AVD manager استفاده کنید، یا میتوانید شبیهساز را با پرچمهای -netdelay
و -netspeed
راهاندازی کنید، همانطور که در مثال خط فرمان زیر نشان داده شده است:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
این مثال برای تمام درخواست های شبکه 20 ثانیه تاخیر و سرعت آپلود و دانلود 14.4 کیلوبیت بر ثانیه تعیین می کند. برای اطلاعات بیشتر در مورد گزینه های خط فرمان برای شبیه ساز، به شروع شبیه ساز از خط فرمان مراجعه کنید.
خواندن با logcat
هنگامی که قادر به انجام مراحل بازتولید خرابی شدید، می توانید از ابزاری مانند logcat
برای دریافت اطلاعات بیشتر استفاده کنید.
خروجی logcat به شما نشان می دهد که چه پیام های گزارش دیگری را همراه با سایر پیام های سیستم چاپ کرده اید. فراموش نکنید که هر گونه عبارت Log
اضافه ای را که اضافه کرده اید خاموش کنید زیرا چاپ آنها باعث هدر رفتن CPU و باتری در حین اجرای برنامه شما می شود.
از خرابی های ناشی از استثناهای نشانگر تهی جلوگیری کنید
استثناهای اشاره گر تهی (که با نوع خطای زمان اجرا NullPointerException
مشخص می شود) زمانی رخ می دهد که شما سعی می کنید به یک شی که تهی است، معمولاً با فراخوانی متدهای آن یا دسترسی به اعضای آن، دسترسی پیدا کنید. استثناهای نشانگر تهی بزرگترین علت خرابی برنامه در Google Play هستند. هدف از null این است که نشان دهد شی از دست رفته است - به عنوان مثال، هنوز ایجاد یا اختصاص داده نشده است. برای جلوگیری از استثناهای اشاره گر تهی، باید قبل از فراخوانی متدها یا تلاش برای دسترسی به اعضای آنها، مطمئن شوید که ارجاعات شی که با آنها کار می کنید غیر پوچ هستند. اگر مرجع شی تهی است، این مورد را به خوبی مدیریت کنید (به عنوان مثال، قبل از انجام هر عملیاتی روی مرجع شیء از یک متد خارج شوید و اطلاعات را در یک گزارش اشکال زدایی بنویسید).
از آنجایی که نمیخواهید برای هر پارامتر از هر متد فراخوانی شده، بررسیهای تهی داشته باشید، میتوانید برای نشان دادن پوچپذیری به IDE یا نوع شی تکیه کنید.
زبان برنامه نویسی جاوا
بخش های زیر برای زبان برنامه نویسی جاوا اعمال می شود.
اخطارهای زمان را جمع آوری کنید
برای دریافت هشدارهای زمان کامپایل از IDE، پارامترهای متدهای خود را حاشیه نویسی کنید و مقادیر را با @Nullable
و @NonNull
برگردانید. این اخطارها از شما می خواهند که انتظار یک شیء باطل را داشته باشید:
این بررسیهای تهی برای اشیایی هستند که میدانید ممکن است تهی باشند. یک استثنا در یک شی @NonNull
نشان دهنده یک خطا در کد شما است که باید برطرف شود.
کامپایل خطاهای زمانی
از آنجایی که پوچپذیری باید معنیدار باشد، میتوانید آن را در انواعی که استفاده میکنید جاسازی کنید تا زمان کامپایل برای null وجود داشته باشد. اگر میدانید که یک شی میتواند تهی باشد و باید آن را باطل کرد، میتوانید آن را در یک شی مانند Optional
بپیچید. همیشه باید انواعی را ترجیح دهید که پوچ بودن را منتقل می کنند.
کاتلین
در کاتلین، پوچ پذیری بخشی از سیستم نوع است. به عنوان مثال، یک متغیر باید از ابتدا به عنوان nullable یا nonnullable اعلام شود. انواع nullable با علامت ?
:
// non-null
var s: String = "Hello"
// null
var s: String? = "Hello"
متغیرهای غیر تهی را نمی توان یک مقدار تهی نسبت داد و متغیرهای تهی باید قبل از استفاده به عنوان غیر تهی از نظر پوچ بودن بررسی شوند.
اگر نمی خواهید صراحتاً وجود null را بررسی کنید، می توانید از ?.
اپراتور تماس ایمن:
val length: Int? = string?.length // length is a nullable int
// if string is null, then length is null
بهعنوان بهترین روش، مطمئن شوید که مورد تهی را برای یک شیء تهپذیر نشان میدهید، در غیر این صورت برنامه شما ممکن است در حالتهای غیرمنتظره قرار گیرد. اگر برنامه شما دیگر با NullPointerException
خراب نمی شود، متوجه وجود این خطاها نخواهید شد.
در زیر چند روش برای بررسی null وجود دارد:
if
بررسیval length = if(string != null) string.length else 0
به دلیل پخش هوشمند و بررسی تهی، کامپایلر Kotlin می داند که مقدار رشته غیر پوچ است، بنابراین به شما اجازه می دهد تا از مرجع مستقیماً بدون نیاز به اپراتور تماس ایمن استفاده کنید.
این عملگر به شما این امکان را می دهد که "اگر شی غیر تهی است، شی را برگردانید، در غیر این صورت، چیز دیگری را برگردانید".
val length = string?.length ?: 0
هنوز هم می توانید یک NullPointerException
در Kotlin دریافت کنید. موارد زیر رایج ترین موقعیت ها هستند:
- هنگامی که شما به صراحت یک
NullPointerException
را پرتاب می کنید. - وقتی از ادعای تهی استفاده می کنید
!!
اپراتور . این عملگر هر مقداری را به یک نوع غیر تهی تبدیل می کند و اگر مقدار تهی باشد،NullPointerException
پرتاب می کند. - هنگام دسترسی به یک مرجع تهی از نوع پلت فرم.
انواع پلت فرم
انواع پلتفرم اعلان های شی هستند که از جاوا می آیند. این انواع به طور ویژه درمان می شوند . چک های پوچ به اندازه ای اجرا نمی شوند، بنابراین ضمانت غیر پوچ مانند جاوا است. هنگامی که به یک مرجع نوع پلتفرم دسترسی دارید، کاتلین خطاهای زمان کامپایل ایجاد نمی کند، اما این ارجاعات می توانند منجر به خطاهای زمان اجرا شوند. مثال زیر را از مستندات کاتلین ببینید:
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 بر استنتاج نوع متکی است، یا می توانید نوع مورد انتظار را تعریف کنید. بهترین راه برای اطمینان از وضعیت پوچ پذیری صحیح مرجعی که از جاوا می آید، استفاده از حاشیه نویسی پوچ پذیری (به عنوان مثال @Nullable
) در کد جاوا است. کامپایلر Kotlin این ارجاعات را بهعنوان انواع باطلپذیر یا غیرقابل تهی نمایش میدهد، نه به عنوان انواع پلتفرم.
APIهای Java Jetpack در صورت نیاز با @Nullable
یا @NonNull
حاشیه نویسی شده اند و رویکرد مشابهی در Android 11 SDK اتخاذ شده است. انواع حاصل از این SDK، که در Kotlin استفاده میشوند، بهعنوان انواع صحیح تهی یا غیر قابل تهی نمایش داده میشوند.
به دلیل سیستم نوع کاتلین، شاهد کاهش شدید برنامهها در خرابیهای NullPointerException
بودهایم. به عنوان مثال، برنامه Google Home در طول سالی که توسعه ویژگی های جدید را به Kotlin منتقل کرد، 30 درصد کاهش در خرابی های ناشی از استثناهای نشانگر تهی را شاهد بود.