1.ANR是什么?
Application Not Responding,字面意思就是应用无响应,稍加解释就是用户的一些操作无法从应用中获取反馈;在实际的应用中应当去避免这种现象,虽然它暂时不会造成应用崩溃,但是却极大地损坏用户体验;
2.ANR触发原因
出现ANR之后一个直观的现象就是系统会展示出一个ANR对话框,如图:
Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测APP的响应时间,如果APP在特定时间无法处理相应屏幕触摸或键盘输入事件,或者特定时间没有处理完毕,就会出现ANR;
那么哪些场景会造成ANR呢?
- Service Timeout:比如前台服务在20s内未执行完成;
- BroadcastQueue Timeout:比如前台广播在10s内未执行完成
- ContentProvider Timeout:内容提供者,在publish过超时10s;
- InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
因此避免以上四种情况就是解决ANR的关键;
导致ANR无响应的常见原因:
- 主线程阻塞或主线程数据读取
解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreferenceS
- CPU满负荷,I/O阻塞
解决办法:文件读写或数据库操作放在子线程异步操作。
- 内存不足
解决办法:
AndroidManifest.xml
文件<applicatiion>中可以设置android:largeHeap="true"
,以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。
- 各大组件ANR
各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。
3.ANR时系统做了什么?
ANR时展示给用户什么内容,源码分析ANR做了哪些事情,知道ANR做了什么,更有助于我们分析ANR;
3.1展示一个应用无反应的对话框
在Activity内为Button添加点击事件,点击时让主线程休眠30秒,在连续点击几次Button按钮;
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
// 该使用该函数不会抛出InterruptedException异常。
SystemClock.sleep(30 * 1000);
}
});
在点击Button按钮第一次以后,在连续点击几次Button按钮,大概七八秒,终于弹出ANR异常;
3.2将ANR信息输出到Logcat
产生ANR时Logcat会同时如下内容:
10-15 01:47:20.975 4038-4046/com.gome.childrenmanager I/art: Thread[5,tid=4046,WaitingInMainSignalCatcherLoop,Thread*=0xaf00d400,peer=0x12c00080,"Signal Catcher"]: reacting to signal 3
10-15 01:47:20.985 4038-4046/com.gome.childrenmanager I/art: Wrote stack traces to '/data/anr/traces.txt'
10-15 01:47:45.820 4038-4038/com.gome.childrenmanager I/Choreographer: Skipped 42 frames! The application may be doing too much work on its main thread.
可以看到Logcat清晰的记录ANR发生的时间,以及线程的tid和一句话概括的原因:WaitingInMainSignalCatcherLoop主线程等待异常,最后一句话 Skipped 42 frames! The application may be doing too much work on its main thread.告知主线程被阻塞导致帧画面无法刷新;
3.3将ANR信息输出到traces.txt文件中
Wrote stack traces to '/data/anr/traces.txt
通过Logcat输出日志看到会将ANR信息写入traces.txt文件,traces.txt文件是一个ANR记录文件,用于开发人员调试分析ANR产生原因,目录在/data/anr中,可以通过adb pull命令导出:
adb pull /data/anr/traces.txt C:\anr
adb命令默认在Android SDK下platform-tools目录,没有配置adb环境变量的需要进入platform-tools目录执行adb命令;
不指定traces.txt导出默认,默认导出到adb所在命令目录;后面对如何分析traces.txt这个文件做详细描述;
3.4ANR源码分析
最后来看看做出上述反应的源代码,这部分代码位于ProcessRecord类中;
ActivityManagerService监听到ANR信息,调用AnrHelper下的方法appNotResponding(),AnrHelper在后台开启一个独立的线程去处理ANR消息,以便缩短处理时间;
AnrHelper
private class AnrConsumerThread extends Thread {
@Override
public void run() {
AnrRecord r;
r.appNotResponding(onlyDumpSelf);
}
}
private static class AnrRecord {
final ProcessRecord mApp;
void appNotResponding(boolean onlyDumpSelf) {
mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
onlyDumpSelf);
}
}
ProcessRecord.appNotResponding()
ProcessRecord.java
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseArray<Boolean> lastPids = new SparseArray<>(20);
mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
ApplicationExitInfo.REASON_ANR, true));
long anrTime = SystemClock.uptimeMillis();
if (isMonitorCpuUsage()) {
//第一次,更新CPU统计信息
mService.updateCpuStatsNow();
}
final boolean isSilentAnr;
synchronized (mService) {
// 某些特定情况下忽略本次ANR,比如系统关机,比如该进程已经处于anr状态或者crash状态
if (mService.mAtmInternal.isShuttingDown()) {
Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
return;
} else if (isNotResponding()) {
Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
return;
} else if (isCrashing()) {
Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
return;
} else if (killedByAm) {
Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
return;
} else if (killed) {
Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
return;
}
//为了防止多次对相同APP的anr执行重复代码,在此处标注记录,属于上面的特定情况中的一种
setNotResponding(true);
// 记录ANR信息到Event Log中
EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
annotation);
//将当前进程添加到firstPids
firstPids.add(pid);
// 如果它是一个后台的ANR或者仅仅请求导出他自己,不需要加入其它PIDS
isSilentAnr = isSilentAnr();
if (!isSilentAnr && !onlyDumpSelf) {
int parentPid = pid;
if (parentProcess != null && parentProcess.getPid() > 0) {
parentPid = parentProcess.getPid();
}
if (parentPid != pid) firstPids.add(parentPid);
//将system_server进程添加到firstPids
if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
// 添加所有进程到firstpids中
for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
ProcessRecord r = getLruProcessList().get(i);
if (r != null && r.thread != null) {
int myPid = r.pid;
if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
if (r.isPersistent()) {
firstPids.add(myPid); //将persistent进程添加到firstPids
if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
} else if (r.treatLikeActivity) {
firstPids.add(myPid);
if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
} else { //其它进程添加到lastPids
lastPids.put(myPid, Boolean.TRUE);
if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
}
}
}
}
}
}
// 记录ANR输出到main log
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(processName);
if (activityShortComponentName != null) {
info.append(" (").append(activityShortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parentShortComponentName != null
&& parentShortComponentName.equals(activityShortComponentName)) {
info.append("Parent: ").append(parentShortComponentName).append("\n");
}
StringBuilder report = new StringBuilder();
report.append(MemoryPressureUtil.currentPsiState());
//创建CPU tracker对象