אפליקציה ל-Android קורסת בכל פעם שקיימת יציאה לא צפויה שנגרמת
חריג או אות שלא טופלו. אפליקציה שנכתבת באמצעות Java או Kotlin
קורס אם הוא גורם לחריגה לא מטופלת, המיוצגת על ידי
Throwable
.
אפליקציה שנכתבת באמצעות קוד מכונה או C++ קורסת אם יש שגיאה לא מטופלת
אות, כמו SIGSEGV
, במהלך הביצוע שלו.
כשאפליקציה קורסת, מערכת Android מסיימת את התהליך של האפליקציה ומציגה תיבת דו-שיח כדי ליידע את המשתמש שהאפליקציה הפסיקה לפעול, כפי שמוצג באיור 1.
אפליקציה לא צריכה לפעול בחזית כדי שהיא תקרוס. אפליקציה כלשהי רכיבים שונים, אפילו רכיבים כמו מקלטי שידורים או ספקי תוכן פועלות ברקע ועלולות לגרום לקריסת אפליקציה. הקריסות האלה לעיתים קרובות מבלבלים את המשתמשים כי הם לא היו מעורבים באופן פעיל באפליקציה.
אם יש קריסות באפליקציה, אפשר להיעזר בהנחיות שבדף הזה כדי לאבחן ולפתור את הבעיה.
זיהוי הבעיה
יכול להיות שלא תמיד תדעו שהמשתמשים שלכם חווים קריסות הם משתמשים באפליקציה. אם כבר פרסמת את האפליקציה, אפשר להשתמש ב- תפקוד האפליקציה כדי לראות את שיעורי הקריסות של האפליקציה.
תפקוד האפליקציה
התכונה 'תפקוד האפליקציה' יכולה לעזור לך לעקוב אחר שיעור הקריסות של האפליקציה ולשפר אותו. התכונה 'תפקוד האפליקציה' מודדת כמה שיעורי קריסות:
- שיעור הקריסות: אחוז המשתמשים הפעילים ביום (DAU) קריסה מכל סוג שהוא.
שיעור הקריסות שהשפיעו על המשתמשים: אחוז המשתמשים הפעילים ביום (DAU) קריסה אחת לפחות בזמן שהם השתמשו באפליקציה באופן פעיל (קריסה שהשפיעו על המשתמשים). אפליקציה נחשבת בשימוש פעיל אם הוא מציג פעילות או מבצע פעולה כלשהי שירות שפועל בחזית.
שיעור קריסות מרובות: אחוז המשתמשים הפעילים ביום (DAU) אירעו שתי קריסות לפחות.
משתמש פעיל ביום הוא משתמש ייחודי שמשתמש באפליקציה שלכם ביום אחד במכשיר יחיד, אולי במהלך מספר סשנים. אם משתמש משתמש באפליקציה שלך ביותר ממכשיר אחד ביום, כל מכשיר יתרום למספר המשתמשים הפעילים באותו יום. אם כמה משתמשים משתמשים באותו מכשיר ביום, נספר כמשתמש פעיל אחד.
שיעור הקריסות שהשפיעו על המשתמשים הוא תפקוד ליבה, כלומר הוא משפיע על יכולת הגילוי של האפליקציה ב-Google Play. זה חשוב כי הוא קורס ספירות מתרחשות תמיד כאשר המשתמש יוצר אינטראקציה עם האפליקציה, וכתוצאה מכך שיבוש.
מערכת Play הגדירה שני ערכי סף של התנהגות לא תקינה במדד הזה:
- סף התנהגות לא תקינה: לפחות 1.09% מהמשתמשים הפעילים ביום קריסה שהשפיעה על המשתמשים בכל דגמי המכשירים.
- סף התנהגות לא תקינה לפי מכשיר: לפחות 8% מהמשתמשים הפעילים ביום מתרחשת קריסה שהשפיעו על המשתמשים, בדגם מכשיר אחד.
אם האפליקציה חורגת מסף ההתנהגות הלא תקינה הכולל, סביר להניח שהיא פחות חשיפה בכל המכשירים. אם האפליקציה שלך חורגת מההתנהגות הלא תקינה לכל מכשיר במכשירים מסוימים, סביר להניח שהדבר יפגע ביכולת הגילוי שלו. ויכול להיות שתוצג אזהרה בדף האפליקציה בחנות.
התכונה 'תפקוד האפליקציה' יכולה לשלוח לך התראה דרך Play Console כשבאפליקציה יש יותר מדי קריסות.
כדי לקבל מידע על אופן האיסוף של נתוני תפקוד האפליקציה ב-Google Play, אפשר לעיין במאמר Play Console התיעוד.
אבחון הקריסות
אחרי שזיהית שהאפליקציה שלך מדווחת על קריסות, השלב הבא הוא לאבחן אותם. פתרון קריסות יכול להיות קשה. אבל אם אתם יכולים לזהות את שורש הבעיה את הקריסה, סביר להניח שאפשר למצוא לה פתרון.
יש מצבים רבים שעלולים לגרום לקריסה באפליקציה. חלק מהסיבות לכך הן מובנים מאליהם, כמו בדיקה של ערך null או מחרוזת ריקה, אבל אחרים הם דקים, כמו העברת ארגומנטים לא חוקיים ל-API או אפילו שרשורים מורכבים האינטראקציות.
קריסות ב-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
,
או משהו אחר, ומוצאים את התיעוד לגבי הכיתה החריגה.
המחלקה, השיטה, הקובץ ומספר השורה של קובץ המקור שבהם החריג מוצגת בשורה השנייה של דוח הקריסות. לכל פונקציה שנקראה, שורה נוספת מציגה את אתר השיחה הקודם (שנקרא מסגרת סטאק). על ידי הצגת הערימה ובדיקת הקוד, אולי תמצאו מקום קביעת ערך שגוי. אם הקוד לא מופיע בדוח הקריסות, סביר להניח שבמקום כלשהו העברתם פרמטר לא חוקי לתוך פעולה. לרוב אפשר להבין מה קרה על ידי בחינת כל אחת מהשורות דוח קריסות, למצוא את כל מחלקות ה-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 Console. מידע נוסף זמין במאמר הבא: פענוח קוד מעורפל של דוחות קריסה. מידע כללי על קריסות נייטיב זמין בכתובת אבחון קריסות מקוריות.
טיפים לשחזור תאונה
ייתכן שלא ניתן יהיה לשחזר את הבעיה רק על ידי התחלת אמולטור או חיבור המכשיר למחשב. סביבות פיתוח בדרך כלל יש להם יותר משאבים, כמו רוחב פס, זיכרון ונפח אחסון. משתמשים ב סוג של חריג, כדי לקבוע מה יכול להיות המשאב הנדיר, או אם יש התאמה בין גרסת Android, סוג המכשיר או .
שגיאות זיכרון
אם יש לך
OutOfMemoryError
תוכלו ליצור אמולטור עם קיבולת זיכרון נמוכה כדי לבצע בדיקה. דמות
2 מציגה את ההגדרות של מנהל ה-AVD שבהן אפשר לשלוט בנפח הזיכרון מופעל
במכשיר.
חריגים ברשתות
מאחר שמשתמשים עוברים לעיתים קרובות פנימה ויוצאים מכיסוי של רשתות סלולריות או Wi-Fi, בדרך כלל לא להתייחס לחריגים של רשת אפליקציות כשגיאות, במקום בתנאי הפעלה רגילים שמתרחשים באופן בלתי צפוי.
אם עליך לשחזר חריג רשת, כגון
UnknownHostException
לאחר מכן נסו להפעיל את מצב טיסה בזמן שהאפליקציה מנסה להשתמש
עמוקה מאוד,
אפשרות נוספת היא להפחית את איכות הרשת באמולטור על ידי
בחירה של אמולציית מהירות רשת ו/או עיכוב ברשת. אפשר להשתמש
אפשר להגדיר מהירות וזמן אחזור במנהל ה-AVD. אפשר גם להפעיל את האמולטור
עם הדגלים -netdelay
ו--netspeed
, כפי שמוצג למטה
דוגמה לשורת פקודה:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
בדוגמה הזו מוגדרת השהייה של 20 שניות בכל בקשות הרשת והעלאה ומהירות ההורדה היא 14.4Kbps. מידע נוסף על אפשרויות שורת הפקודה לאמולטור: מפעילים את האמולטור משורת הפקודה.
קריאה עם Logcat
אחרי שיהיו לכם את השלבים לשחזור הקריסה, תוכלו להשתמש בכלי כמו
logcat
כדי לקבל מידע נוסף.
הפלט של ה-Logcat יראה לך אילו הודעות יומן אחרות הדפסת, יחד עם
עם אנשים אחרים מהמערכת. לא לשכוח להשבית תוספים נוספים
Log
הצהרות
שנוספו כי הדפסתן מבזבזת מעבד (CPU) וסוללה בזמן שהאפליקציה
ריצה.
מניעת קריסות שנגרמו עקב חריגים במצבי null
חריגים מסוג null (מזוהים לפי סוג השגיאה בסביבת זמן הריצה)
NullPointerException
) מתרחשת כשמנסים לגשת לאובייקט
null, בדרך כלל על ידי הפעלת השיטות שלו או גישה לחברים שנכללים בו. סמן ערך null
חריגות הן הסיבה הגדולה ביותר לקריסות של אפליקציות ב-Google Play. המטרה של
הערך null מציין שהאובייקט חסר – לדוגמה, הוא לא קיים
נוצרו או הוקצו עדיין. כדי להימנע מהחרגות של מצביע null, צריך לוודא
שההפניות של האובייקט שאתם עובדים איתו אינן null לפני הקריאה
שיטות בהם או לנסות לגשת לחברים שלהם. אם ההפניה לאובייקט היא
null, טפל במקרה זה היטב (לדוגמה, יציאה מ-method לפני ביצוע
פעולות בהפניה לאובייקט וכתיבת מידע ביומן ניפוי באגים).
מאחר שאתם לא רוצים לבצע בדיקות null לכל פרמטר בכל שיטה. אפשר להסתמך על סביבת הפיתוח המשולבת (IDE) או על סוג האובייקט יכולת אפס.
שפת תכנות Java
הסעיפים הבאים חלים על שפת התכנות Java.
הידור אזהרות לגבי הזמן
הוספת הערות לשיטות שלך ותחזיר ערכים עם
@Nullable
ו-
@NonNull
כדי לקבל זמן הידור
אזהרות מסביבת הפיתוח המשולבת (IDE). האזהרות הבאות מאפשרות לצפות לאובייקט שאינו יכול להיות ריק (null):
בדיקות ה-null האלה מיועדות לאובייקטים שידוע לכם שהם יכולים להיות null. חריג לכלל
אובייקט @NonNull
הוא אינדיקציה לשגיאה בקוד שצריך
טופל.
הידור שגיאות הזמן
ליכולת ה-null צריכה להיות משמעות, אפשר להטמיע אותה בסוגים שבהם משתמשים
כך שיש בדיקת זמן הידור ל-null. אם יודעים שאובייקט יכול להיות
null וצריך לטפל בהן, אפשר לעטוף אותו באובייקט כמו
Optional
תמיד צריך להעדיף סוגים שמעבירים ערך null.
Kotlin
ב-Kotlin,
ערך null
הוא חלק ממערכת הסוגים. לדוגמה, צריך להצהיר על משתנה מ-
הערך של ההתחלה מוגדר כ-null או כ-null. סוגי הערכים שאינם מסומנים מסומנים ב-?
:
// non-null
var s: String = "Hello"
// null
var s: String? = "Hello"
לא ניתן להקצות ערך null ומשתנים שאינם ערכי null צריך לבדוק אם קיימת יכולת null לפני שמשתמשים בהן בתור null.
אם לא רוצים לבדוק באופן מפורש את הערך null, אפשר להשתמש בקריאה הבטוחה של ?.
.
אופרטור:
val length: Int? = string?.length // length is a nullable int
// if string is null, then length is null
השיטה המומלצת היא לטפל באותיות ה-null של אובייקט שהוא אפס (null).
אחרת האפליקציה עלולה להגיע למצבים לא צפויים. אם האפליקציה לא קורסת
עם NullPointerException
, לא תדעו שהשגיאות האלה קיימות.
יש כמה דרכים לבדוק אם הערך הוא null:
if
בדיקותval length = if(string != null) string.length else 0
הודות להעברה חכמה ובדיקת ה-null, מהדר של Kotlin יודע ערך המחרוזת אינו null ולכן הוא מאפשר להשתמש ישירות בהפניה, ללא צורך במוקד השיחה הבטוח.
-
האופרטור הזה מאפשר לציין "אם האובייקט אינו אפס, צריך להחזיר את אובייקט; אחרת, מחזיר משהו אחר".
val length = string?.length ?: 0
עדיין אפשר לקבל NullPointerException
ב-Kotlin. אלה הסוגים הנפוצים ביותר
מצבים נפוצים:
- כשמריצים
NullPointerException
באופן מפורש. - בזמן השימוש
אופרטור null טענת נכוֹנוּת (assertion)
!!
. האופרטור הזה ממיר כל ערך לסוג שאינו null,NullPointerException
אם הערך הוא null. - כשניגשים להפניה אפס של סוג פלטפורמה.
סוגי פלטפורמות
סוגי הפלטפורמות הם הצהרות אובייקטים שמגיעות מ-Java. סוגים אלה מטופלים באופן מיוחד; בדיקות null לא נאכפות באותה מידה, כך שהאחריות שאינה אפסית זהה Java. כשניגשים להפניה לסוג פלטפורמה, Kotlin לא יוצר הידור (compile) אבל הפניות אלה יכולות להוביל לשגיאות בזמן ריצה. כדאי לעיין בנושאים הבאים דוגמה מתוך מסמכי התיעוד של 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
משתנה, או להגדיר מה הסוג של הנתונים. הדרך הטובה ביותר לוודא
מצב אפסי (nullability) של הפניה שמגיעה מ-Java הוא שימוש ביכולת ה-null
(לדוגמה, @Nullable
) בקוד ה-Java. המהדר של Kotlin
שמייצג את ההפניות האלו כסוגים אמיתיים כערכי null או ככאלה שאינם null,
בסוגי הפלטפורמות השונות.
לממשקי API של Java Jetpack נוספו הערות עם @Nullable
או @NonNull
לפי הצורך,
וננקטו גישה דומה
Android 11 SDK.
סוגים שמגיעים מה-SDK הזה, שנמצאים בשימוש ב-Kotlin, מיוצגים בתור
סוגים נכונים של ערכי null או null.
בזכות מערכת הסוגים של Kotlin, ראינו ירידה משמעותית
NullPointerException
קריסות. לדוגמה, אפליקציית Google Home ראתה 30%
ירידה במספר הקריסות שנגרמו עקב חריגים של מצביע null במהלך השנה שבה
העבירה את פיתוח התכונות החדשות ל-Kotlin.