ANR的原因以及如何避免

ANR(应用程序无响应)-应用程序无响应时发生的错误。 结果,将打开一个对话框,提示用户等待或关闭应用程序。
图片替代

ANR条件


  • 输入事件(按钮和触摸事件)在5秒钟内未处理;
  • 在指定的时间内(前景-10 s,背景-60 s)未处理BroadcastReceiver(onRecieve());
  • ContentProvider在10秒内未完成。

通常,主线程被阻塞。

如果您阅读了我的文章,您可能已经习惯了我们抓取源代码的事实。 因此,让我们看看ANR在引擎盖下的外观。

AppErrors类不仅处理 ANR,还处理应用程序中可能发生的其他错误,包括崩溃。 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); } 

这是InputMonitor中的mWindowManagerCallbacks

  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显示有关超时到期的消息,并让用户决定是取消操作还是继续等待。 在崩溃或ANR的情况下,正是在ActivityManagerService中调用AppErrors。

  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


  • 主用户界面线程执行仅与用户界面相关联的逻辑。
  • 复杂的计算(例如,数据库操作,输入输出操作,网络操作等)在单独的流中执行;
  • 使用处理程序在用户界面线程和工作流之间进行交互;
  • 使用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 :)

PS我在电报频道@paradisecurity中发布了所有选择。

Source: https://habr.com/ru/post/zh-CN439086/


All Articles