מדוע ANR מסוכן לאפליקציה שלך?
תארו לעצמכם: האפליקציה שלכם קופאת למשך 5 שניות, המשתמש רואה מסך שחור וחלון סגירה. Crashlytics מדווח על ANR ללא מחסנית קריאות — רק שם ה-Activity. הסיבה לא ידועה. כך אתם מאבדים משתמשים ודירוגים ב-Google Play. לפי התיעוד הרשמי, ANR (Application Not Responding) מתרחש כאשר ה-thread הראשי חסום למשך יותר מ-5 שניות עבור אירוע קלט או 10 שניות עבור BroadcastReceiver. סיבות נפוצות כוללות תחרות על נעילות (lock contention), השהיות של איסוף זבל (GC), והחמצות מטמון דפים בקלט/פלט של דיסק.
| סוג אירוע | סף זמן |
|---|---|
| אירוע קלט | 5 שניות |
| BroadcastReceiver | 10 שניות |
| Service (startService) | 20 שניות |
המשתמש רואה מסך שחור ואת החלון "האפליקציה לא מגיבה", ולאחר מכן המערכת הורגת את התהליך. Crashlytics רושם ערך ללא מחסנית קריאות — רק שם ה-ANR וה-Activity. זה גרוע יותר מקריסה: ללא עקבות אינכם יודעים את הסיבה. הקמנו ניטור ANR מקצה לקצה: חיבור ל-Firebase Crashlytics או ל-Sentry, קריאת ApplicationExitInfo לקבלת עקבות מדויקות, והגדרת התראות Slack. כתוצאה מכך, אתם רואים איזה קוד גרם לקיפאון ומתקנים אותו לפני תלונות המוניות. חיסכון בזמן דיבוג עד 50%. למידע נוסף על ANR בתיעוד Android. קבלו הערכת פרויקט — כתבו לנו.
מאיפה מגיעים ANRs
לרוב זו לא קריאה כבדה אחת אלא שרשרת. לדוגמה, קורוטינה על Dispatchers.Main קוראת ל-runBlocking, שבתוכה יש Room.database.query() ללא suspend. במכשיר חלש עם דיסק עמוס, זה 5+ שניות של חסימה. מקור נפוץ נוסף הוא SharedPreferences במהלך התנעה קרה, הגורם להחמצות מטמון דפים במכשירים תקציביים.
// Антипаттерн — чтение SharedPreferences на main thread при старте
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val token = getSharedPreferences("prefs", MODE_PRIVATE)
.getString("auth_token", null) // блокирует main thread
}
}הגישה הנכונה: מעבר ל-SharedPreferences (מבוסס קורוטינות) או קריאה ב-// Антипаттерн — чтение SharedPreferences на main thread при старте class SplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val token = getSharedPreferences("prefs", MODE_PRIVATE) .getString("auth_token", null) // блокирует main thread } } .
השוואת כלי ניטור ANR
השוו בין שלושה פתרונות פופולריים:
| כלי | שיטת זיהוי | מורכבות | דיוק עקבות |
|---|---|---|---|
| Firebase Crashlytics | Watchdog + ApplicationExitInfo (API 30+) | נמוכה (שורה אחת) | מוגבל (לא תמיד מחסנית) |
| Sentry | Watchdog עם timeout ניתן להגדרה | בינונית (הגדרה) | גבוה (מחסנית מלאה) – 70% יותר עקבות מ-Crashlytics |
| Android Vitals | צבירה ב-Google Play | ללא SDK | נתוני שיעור ANR, ללא מחסנית |
Firebase Crashlytics הוא הנתיב הפשוט ביותר אם הוא כבר משולב. אך לניתוח מעמיק, השתמשו ב-Sentry או ב-DataStore. Sentry מספק 70% יותר עקבות אינפורמטיביות מ-Crashlytics. ApplicationExitInfo מדויק פי 3 מ-watchdog: הוא שומר את תמונת ה-thread המלאה ברגע ה-ANR, לא רק את המחסנית הנוכחית.
הגדרת ANR ב-Crashlytics
- הוסיפו את התלות ב-
lifecycleScope.launch(Dispatchers.IO):
implementation("com.google.firebase:firebase-crashlytics:18.+") - אתחלו את ה-SDK ב-
ApplicationExitInfo. - Crashlytics רושם אוטומטית ANRs דרך
build.gradleב-API 30+ או דרך ה-watchdog שלו בגרסאות ישנות יותר.
הגדרת ANR ב-Sentry
SentryAndroid.init(this) { options ->
options.dsn = "https://[email protected]/project"
options.isAnrEnabled = true
options.anrTimeoutIntervalMillis = 5000
options.isAnrReportInDebug = false // не спамим в debug
}Sentry מפעיל thread של watchdog שבודק כל 1000 אלפיות השנייה אם ה-thread הראשי חי. אם אין תגובה למשך יותר מ-implementation("com.google.firebase:firebase-crashlytics:18.+") , הוא לוכד את המחסנית ושולח את האירוע.
ApplicationExitInfo — המקור המדויק לעקבות
ב-Android 10+, המערכת שומרת את סיבת סיום התהליך ב-Application.onCreate(). זה מדויק פי 3 מ-threads של watchdog:
val activityManager = getSystemService(ActivityManager::class.java)
val exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 10)
exitReasons.filter { it.reason == ApplicationExitInfo.REASON_ANR }.forEach { info ->
info.traceInputStream?.use { stream ->
val trace = stream.bufferedReader().readText()
// отправляем trace в ваш мониторинг
Log.e("ANR", trace)
}
}ApplicationExitInfo מכיל את תמונת ה-thread המלאה — אותם נתונים כמו ב-SentryAndroid.init(this) { options -> options.dsn = "https://[email protected]/project" options.isAnrEnabled = true options.anrTimeoutIntervalMillis = 5000 options.isAnrReportInDebug = false // не спамим в debug } . ניתן לקרוא אותו בהפעלה הבאה ולשלוח אותו ל-Sentry או ל-Datadog כקובץ מצורף.
אבחון באמצעות StrictMode
ב-build של דיבוג, StrictMode עוזר לתפוס ANRs פוטנציאליים לפני הייצור:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyFlashScreen()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}anrTimeoutIntervalMillis מהבהב את המסך באדום על כל קריאת דיסק ב-thread הראשי — המפתח רואה את הבעיה מיד.
הגדרת התראות
ב-Firebase Crashlytics, הגדירו התראת מהירות (velocity alert) עבור ANRs:
- סף: >1% מהמפגשים ללא קריסה מושפעים בשעה אחת.
- ערוץ: webhook של Slack דרך Firebase Alert Channels.
ב-Sentry, השתמשו ב-ApplicationExitInfo עם התנאי val activityManager = getSystemService(ActivityManager::class.java) val exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 10) exitReasons.filter { it.reason == ApplicationExitInfo.REASON_ANR }.forEach { info -> info.traceInputStream?.use { stream -> val trace = stream.bufferedReader().readText() // отправляем trace в ваш мониторинг Log.e("ANR", trace) } } .
מה כלול בהקמת ניטור ANR
- שילוב גלאי ANR (Firebase Crashlytics, Sentry, או שניהם)
- הגדרת קריאת
traceInputStreamב-API 30+ לקבלת עקבות מדויקות - הפעלת StrictMode ב-builds של דיבוג לאבחון מונע
- הגדרת התראות מהירות עם הודעת Slack
- ניתוח קו הבסיס של Android Vitals להשוואה עם מתחרים
- תיעוד על שחזור ANR והמלצות לתיקון
- תמיכה לאחר ההקמה (שבועיים בחינם)
הניסיון שלנו: יותר מ-5 שנים בפיתוח Android, יותר מ-50 פרויקטי ניטור. אנו מבטיחים שיעור ANR מתחת ל-0.2% לאחר האופטימיזציה. הקמה בסיסית מתחילה ב-$500; הקמה מלאה עם Sentry ו-ApplicationExitInfo מ-$2000. השקעה זו בדרך כלל חוסכת $10,000+ בשנה בעלויות תמיכה מופחתות ועזיבת משתמשים.
כמה זמן לוקחת הקמת ניטור ANR?
הקמה בסיסית עם Firebase Crashlytics אורכת בין 4 שעות ליום אחד. אם נדרשים Sentry וניתוח /data/anr/traces.txt, זה לוקח יומיים. אנו מספקים דשבורד מוכן והתראות. צרו קשר — קבלו הערכת פרויקט מפורטת.
ציר זמן ועלות
הקמה בסיסית — בין 4 שעות ליום אחד, החל מ-$500. עם ניתוח if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyFlashScreen() .build() ) StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .build() ) } ודשבורד — יומיים, מ-$2000. העלות מחושבת באופן אישי לפי מורכבות הפרויקט ומספר המסכים. ההשקעה משתלמת דרך הפחתת עזיבת משתמשים. קבלו הערכה — כתבו לנו.







