Причины возникновения ANR и как этого избежать

    ANR (Application Not Responding) — ошибка, которая возникает, когда приложение не отвечает. В итоге открывается диалоговое окно, предлагающее пользователю подождать или закрыть приложение.
    image alt

    Условия возникновения ANR


    • Входные события (кнопки и сенсорные события) не обрабатываются в течение 5 секунд;
    • BroadcastReceiver (onRecieve()) не был обработан в течение указанного времени (foreground — 10 с, background — 60 с);
    • ContentProvider не завершен в течение 10 секунд.

    Обычно основной поток блокируется.

    Если вы читали мои статьи, то наверно уже привыкли к тому, что мы лезет в исходный код. Так что давайте посмотрим как выглядит ANR под капотом.

    Класс AppErrors занимается обработкой не только ANR, но и других ошибок, которые могут возникнуть в приложении, включая crash. Метод handleShowAnrUi() как раз и открывает это страшное для многих разработчиков и пользователей окно, отображающее ANR.

    class AppErrors {
        ...
        
        void handleShowAnrUi(Message msg) {
            Dialog dialogToShow = null;
            synchronized (mService) {
                AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
                final ProcessRecord proc = data.proc;
                if (proc == null) {
                    Slog.e(TAG, "handleShowAnrUi: proc is null");
                    return;
                }
                if (proc.anrDialog != null) {
                    Slog.e(TAG, "App already has anr dialog: " + proc);
                    MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                            AppNotRespondingDialog.ALREADY_SHOWING);
                    return;
                }
    
                Intent intent = new Intent("android.intent.action.ANR");
                if (!mService.mProcessesReady) {
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                }
                mService.broadcastIntentLocked(null, null, intent,
                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                        null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
    
                boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                        Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
                if (mService.canShowErrorDialogs() || showBackground) {
                    dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
                    proc.anrDialog = dialogToShow;
                } else {
                    MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                            AppNotRespondingDialog.CANT_SHOW);
                    // Just kill the app if there is no dialog to be shown.
                    mService.killAppAtUsersRequest(proc, null);
                }
            }
            // If we've created a crash dialog, show it without the lock held
            if (dialogToShow != null) {
                dialogToShow.show();
            }
        }
    
    ...

    Однако, ANR начинается не здесь. Как я и говорила выше, одна из первых причин возникновения этой ошибки — это задержка входного события, которая составляет 5 секунд. Недолгим поиском мы можем найти где задается это значение.

    namespace android {
    // Default input dispatching timeout if there is no focused application or paused window
    // from which to determine an appropriate dispatching timeout.
    const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec

    Теперь мы можем посмотреть в коде, где вызывается нативная часть. Это происходит в классе InputManagerService.

        // Native callback.
        private long notifyANR(InputApplicationHandle inputApplicationHandle,
                InputWindowHandle inputWindowHandle, String reason) {
            return mWindowManagerCallbacks.notifyANR(
                    inputApplicationHandle, inputWindowHandle, reason);
        }
    

    А вот и mWindowManagerCallbacks в InputMonitor:

            if (appWindowToken != null && appWindowToken.appToken != null) {
                // Notify the activity manager about the timeout and let it decide whether
                // to abort dispatching or keep waiting.
                final AppWindowContainerController controller = appWindowToken.getController();
                final boolean abort = controller != null
                        && controller.keyDispatchingTimedOut(reason,
                                (windowState != null) ? windowState.mSession.mPid : -1);
                if (!abort) {
                    // The activity manager declined to abort dispatching.
                    // Wait a bit longer and timeout again later.
                    return appWindowToken.mInputDispatchingTimeoutNanos;
                }
            } else if (windowState != null) {
                try {
                    // Notify the activity manager about the timeout and let it decide whether
                    // to abort dispatching or keep waiting.
                    long timeout = ActivityManager.getService().inputDispatchingTimedOut(
                            windowState.mSession.mPid, aboveSystem, reason);
                    if (timeout >= 0) {
                        // The activity manager declined to abort dispatching.
                        // Wait a bit longer and timeout again later.
                        return timeout * 1000000L; // nanoseconds
                    }
                } catch (RemoteException ex) {
                }
            }
            return 0; // abort dispatching
        }

    Давайте посмотрим внимательнее на inputDispatchingTimedOut(). Тут как раз мы и показываем сообщение через ActivityManager об истечении времени ожидания и даем пользователю решить, следует ли отменить действие или продолжить ожидание. И именно в ActivityManagerService и вызывается AppErrors в случае возникновения crash или ANR.

      private boolean makeAppCrashingLocked(ProcessRecord app,
                String shortMsg, String longMsg, String stackTrace) {
            app.crashing = true;
            app.crashingReport = generateProcessError(app,
                    ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
            startAppProblemLocked(app);
            app.stopFreezingAllLocked();
            return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
        }
        private void makeAppNotRespondingLocked(ProcessRecord app,
                String activity, String shortMsg, String longMsg) {
            app.notResponding = true;
            app.notRespondingReport = generateProcessError(app,
                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
                    activity, shortMsg, longMsg, null);
            startAppProblemLocked(app);
            app.stopFreezingAllLocked();
        }

    Основные причины ANR


    • Блокировка ввода/вывода
    • Перегрузка сети
    • Блокировка потоков
    • Бесконечный цикл
    • Бизнес-логика выполняется слишком долго

    Как избежать ANR


    • Главный поток пользовательского интерфейса выполняет логику, связанную только с пользовательским интерфейсом;
    • Сложные вычисления (например, операции с базой данных, операции ввода-вывода, сетевые операции и т.д.) производятся в отдельном потоке;
    • Используйте Handler для взаимодействия между потоком пользовательского интерфейса и рабочим потоком;
    • Используйте RxJava и т.д. для обработки асинхронных операций.

    Как поймать ANR


    • Информация об ANR может храниться в файле /data/anr/traces.txt, либо по другому пути /data/anr/anr_*. Получить ее можно с помощью следующих команд:

      adb root
      adb shell ls /data/anr
      adb pull /data/anr/<filename>
    • Использовать проект с открытым исходным кодом ANR-WatchDog для обнаружения ANR
    • См. Как избежать ANR :)

    P.S. Все подборки я публикую в телеграм канале @paradisecurity.
    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое