1.什么是anr
ANR(Application Not Responding),即“应用程序无响应”。anr是android自身的一种监测机制,如果ui线程在特定时间无法对输入事件做出响应或则对特定操作没有执行完毕,就会出现anr的情况。
2.anr分类
1,InputEventTimeout
输入事件(按键或触屏事件)超过5s无响应就会弹出anr提示框,供用户选择继续等待程序响应或则关闭这个应用程序,输入事件无响应是anr出现最频繁的情况,输入超时类可以细分以下二类:
1.处理消息超时,在log中会出现Input dispatching timed out
2.无法获取焦点,这一类通常是由于新窗口创建慢或者旧窗口退出慢而导致窗口无法获取焦点而出现anr,在log中一般会出现 Reason: Waiting because no window hasfocus but there is a focused application that may eventually add a window when it finishes starting up.
2.BroadcastTimeout
BroadcastRecevier 在规定时间无法处理完成(前台广播10s,后台广播60s),这类超时没有提示框弹出,一般出现在statusbar,setting应用中
3.ServiceTimeout
service在20s内无法处理完成操作,即会报出服务超时,同样没有提示框出现,这一类anr可能会出现在bluetooth或wifi中,很少会遇到,至少我没有遇到过
3.anr产生的原因
产生anr的原因很多,可以分为二类:
1.应用自身进程引起的问题,比如, 在oncreate ,onstart等生命周期中执行耗时操作,ui线程阻塞,挂起,死循环等
2,其他进程引起的,比如:io操作导致cpu使用过高,导致当前应用进程无法抢占到cpu时间片
4.anr产生过程
1.keyDispatchingTimeOut (5s)
当应用程序的Window处于Active状态并且能够接收输入事件(例如按键事件、触摸事件等)时,系统底层上报的事件就会被InputDispatcher分发给这个应用程序,应用程序的主线程通过InputChannel读取输入事件并交给界面视图处理:
InputReader:读事件,待有事件发生时唤醒InputDispatcher
InputDispatcher:將事件派发给当前活动的窗口。
InputDispatcherThread::threadLoop-------->
InputDispatcher::dispatchOnce------>
InputDispatcher::dispatchOnceInnerLocked---->
分支1:(key事件)
InputDispatcher::dispatchKeyLocked------------〉
InputDispatcher::findFocusedWindowTargetsLocked-----
分支2:(屏幕触摸和移动事件)
InputDispatcher::dispatchMotionLocked-------->
InputDispatcher ::findTouchedWindowTargetsLocked----
InputDispatcher::handleTargetsNotReadyLocked----->
InputDispatcher::onANRLocked------------>
InputDispatcher::doNotifyANRLockedInterruptible----->
com_android_server_input_InputManagerService::NativeInputManager::notifyANR--->
InputMangerService::notifyANR---->InputMonitor::notifyANR-->
ActivityRecord::keyDispatchingTimedOut-〉
ActivityManageService::inputDispatchingTimedOut 而后就会在System.log,dropbox,trane.txt中看出相应的anr信息输出
分支1: Key消息处理中findFocusedWindowTargetsLocked是ANR 的入口函数.从这里可以看出在三种情况下会进入ANR.1:如果该窗口没找到,但应用程序找到,说明此时该应用程序正在启动中,还没有完成。等待直到启动完成或超时,超 时就 anr.
2:判断该窗口是否pause状态,是则等待,直到启动完成或超时,超时就 anr
3:判断该窗口是否已经处理完上一个分发给它的事件,没有就等待,直到超时anr
在按键消息传递给View的过程中会调用ViewRootImpl.java中deliverKeyEvent.这里进行了一些处理.如果存在输入法窗口,会先将按键消息派发到输入法窗口处理,输入法如果没有消耗消息,消息则进入View树进行处理。 在消息传递中,完成消息处理后,都会调用finishKeyEvent.给Wms一个处理回执,从而使得Wms可以继续派发下一个消息.
分支2:Motion消息处理中findTouchedWindowTargetsLocked是ANR 的入口函数. 从这里可以看出主要存在以下三种情况会进入ANR.
1: 如果该窗口没找到,但应用程序找到,则等待添加新窗口。等待完成,超 时就 anr
2: 判断该窗口是否pause状态, 是则等待,超时就 anr
3: 判断该窗口是否已经处理完上一个分发给它的事件,没有就等待,直到超时anr
调用流程如下:
ActivityManagerService.java
attachApplication-->attachApplicationLocked
对于broadcast的anr
-->sendPendingBroadcastsLocked(app);
BroadcastQueue.java中
-->processCurBroadcastLocked(br, app);抛出异常时,捕获异常时会调用scheduleBroadcastsLocked会发送BROADCAST_INTENT_MSG到消息队列,handleMessage函数来处理此消息调用processNextBroadcast(true)被调用来分发广播,这调用setBroadcastTimeoutLocked(timeoutTime);来注册超时的广播,BROADCAST_TIMEOUT_MSG消息发送到超时的消息队列中,同样通过handleMessage函数来处理此消息,并调用broadcastTimeoutLocked函数,若发生超时则调用mHandler.post(new AppNotResponding(app, anrMessage))触发ANR的报告发生;
3、ServiceTimeout(一般是20 s,后台service为200s) Service在特定的时间内无法处理完成。
static final int SERVICE_TIMEOUT = 20*1000;调用流程如下:
ActivityManagerService.java
attachApplication
attachApplicationLocked
对于service的anr
mServices.attachApplicationLocked
ActiveServices.java
realStartServiceLocked->bumpServiceExecutingLocked->scheduleServiceTimeoutLocked这里会发出SERVICE_TIMEOUT_MSG超时消息ActivityManagerService.java中的Handler mHandler内的handleMessage会处理这个消息调用mServices.serviceTimeout((ProcessRecord)msg.obj);说明service程序超时20s会发生ANR调用mAm.appNotResponding(proc, null, null, false, anrMessage)触发ANR发生;
5.anr log的输出代码分析
AppErrors-->appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
if (mService.mController != null) {
try {
// 0 == continue, -1 = kill process immediately
int res = mService.mController.appEarlyNotResponding(
app.processName, app.pid, annotation);
if (res < 0 && app.pid != MY_PID) {
app.kill("anr", true);
}
} catch (RemoteException e) {
mService.mController = null;
Watchdog.getInstance().setActivityController(null);
}
}
long anrTime = SystemClock.uptimeMillis();
if (ActivityManagerService.MONITOR_CPU_USAGE) {
//更新cpu使用率
mService.updateCpuStatsNow();
}
// Unless configured otherwise, swallow ANRs in background processes & kill the process.
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
boolean isSilentANR;
synchronized (mService) {
//过滤几种不需要显示anr的情况
// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
if (mService.mShuttingDown) {
Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
return;
} else if (app.notResponding) {
Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
return;
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
}
// In case we come through here for the same app before completing
// this one, mark as anring now so we will bail out.
app.notResponding = true;
// Log the ANR to the event log.
//输出相关anr信息到event.log中
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
firstPids.add(app.pid);
// Don't dump other PIDs if it's a background ANR
isSilentANR = !showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID;
if (!isSilentANR) {
int parentPid = app.pid;
if (parent != null && parent.app != null && parent.app.pid > 0) {
parentPid = parent.app.pid;
}
if (parentPid != app.pid) firstPids.add(parentPid);
if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mService.mLruProcesses.get(i);
if (r != null && r.thread != null) {
int pid = r.pid;
if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
if (r.persistent) {
firstPids.add(pid);
if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
} else {
lastPids.put(pid, Boolean.TRUE);
if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
}
}
}
}
}
}
// Log the ANR to the main log.
//输出anr的相关信息到main.log中
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
String[] nativeProcs = NATIVE_STACKS_OF_INTEREST;
// don't dump native PIDs for background ANRs
File tracesFile = null;
//输入trace文件
if (isSilentANR) {
tracesFile = mService.dumpStackTraces(true, firstPids, null, lastPids,
null);
} else {
tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
nativeProcs);
}
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
//刷新cpu的使用情况
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
//输出出现anr之前cpu的使用率
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
//输出出现anr之后cpu的使用率
info.append(processCpuTracker.printCurrentState(anrTime));
Slog.e(TAG, info.toString());
if (tracesFile == null) {
// There is no trace file, so dump (only) the alleged culprit's threads to the log
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
//输出相关的anr信息到dropbox中
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
if (mService.mController != null) {
try {
// 0 == show dialog, 1 = keep waiting, -1 = kill process immediately
int res = mService.mController.appNotResponding(
app.processName, app.pid, info.toString());
if (res != 0) {
if (res < 0 && app.pid != MY_PID) {
app.kill("anr", true);
} else {
synchronized (mService) {
mService.mServices.scheduleServiceTimeoutLocked(app);
}
}
return;
}
} catch (RemoteException e) {
mService.mController = null;
Watchdog.getInstance().setActivityController(null);
}
}
synchronized (mService) {
mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
if (isSilentANR) {
app.kill("bg anr", true);
return;
}
// Set the app's notResponding state, and look up the errorReportReceiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : "ANR",
info.toString());
// Bring up the infamous App Not Responding dialog
//显示anr提示对话框
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mService.mUiHandler.sendMessage(msg);
}
}
6.如何防止和分析anr
1.预防出现anr
ui主线程中只做ui相关的处理,避免过多执行耗时操作,如遇到需要执行的耗时操作,建议放到单独的线程中进行处理,尽量使用handler来处理ui线程和其他线程之间的交互
2.anr一般分析步骤
1.首先从main.log找到进程出现anr对应的大体时间,如在log中查询"anr in"字段
2.根据出现anr的进程名到anr文件夹中找出trance.txt文件,根据文件的信息首先判断anr的类型,是app自身还是系统问题,如果是app应用问题,则根据对用的调用解决问题
3.如不是app应用问题,则从main.log中判断anr的类型,如是keydispatch time out,则应该根据anr准确的时间点上推5s钟,看看此时对用进程正在进程何操作(具体anr的准确时间可以在event.log中搜索anr)
4.trace中无明显异常,可以从下面的情况考虑
是否由于io,数据库处理导致cpu使用率过高从而导致其他应用进程无法抢占cpu时间片
是否是低内存导致anr(如低内存,可以从system.log中查看进程被kill, 输入某某进程died)
是否由于输入法交互处理不当导致不能返回出现anr
是否由于进程锁等待,死锁情况出现anr