Causes of ANR and how to avoid it

    ANR (Application Not Responding) - an error that occurs when an application does not respond. As a result, a dialog opens prompting the user to wait or close the application.
    image alt

    ANR conditions


    • Input events (buttons and touch events) are not processed for 5 seconds;
    • BroadcastReceiver (onRecieve ()) was not processed within the specified time (foreground - 10 s, background - 60 s);
    • ContentProvider not completed within 10 seconds.

    Normally the main thread is blocked.

    If you read my articles, you probably already got used to the fact that we are crawling into the source code. So let's see what ANR looks like under the hood .

    The class AppErrors deals with processing not only ANR, but also other errors that may occur in the application, including crash. The handleShowAnrUi () method just opens this scary window for many developers and users, displaying ANR.

    classAppErrors{
        ...
        voidhandleShowAnrUi(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 heldif (dialogToShow != null) {
                dialogToShow.show();
            }
        }
    ...

    However, ANR does not start here. As I said above, one of the first causes of this error is the delay in the input event, which is 5 seconds. With a short search, we can find where this value is given.

    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

    Now we can see in the code where the native part is called. This happens in the InputManagerService class .

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

    And here is the mWindowManagerCallbacks in 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();
                finalboolean 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;
                }
            } elseif (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) {
                }
            }
            return0; // abort dispatching
        }

    Let's take a closer look at inputDispatchingTimedOut (). This is where we show the message through the ActivityManager about the timeout and let the user decide whether to cancel the action or continue waiting. And it is in ActivityManagerService that AppErrors is called in case of a crash or ANR.

    privatebooleanmakeAppCrashingLocked(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);
        }
        privatevoidmakeAppNotRespondingLocked(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 root causes


    • I / O lock
    • Network overload
    • Blocking threads
    • Endless cycle
    • Business logic takes too long

    How to avoid ANR


    • The main user interface thread executes logic associated only with the user interface;
    • Complex calculations (for example, database operations, I / O operations, network operations, etc.) are performed in a separate thread;
    • Use Handler to communicate between the user interface thread and the workflow;
    • Use RxJava, etc. for processing asynchronous operations.

    How to catch ANR


    • Information about the ANR can be stored in the file /data/anr/traces.txt, or in another path / data / anr / anr_ *. You can get it using the following commands:

      adb root
      adb shell ls /data/anr
      adb pull /data/anr/<filename>
    • Use the open source project ANR-WatchDog to detect ANR
    • See How to avoid ANR :)

    PS I publish all the selections in the @paradisecurity telegram .

    Also popular now: