Android 性能优化入门(三)—— ANR 问题分析_android anr
需要清楚 ANR 的概念、类型、如何产生以及如何定位分析。
1、概述
1.1 ANR 的概念
ANR(Application Not Responding)应用程序无响应。如果你应用程序在主线程被阻塞太长时间,就会出现 ANR,通常出现 ANR,系统会弹出一个提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。
1.2 ANR 类型
ANR 有 4 种类型:
- KeyDispatchTimeout(常见):Input 事件在 5 秒内没有处理完成导致发生 ANR
- logcat 日志关键字:Input event dispatching timed out
- BroadcastTimeout:前台 BroadcastReceiver 的 onReceive() 在 10 秒内没有处理完成、后台 BroadcastReceiver 的 onReceive() 在 60 秒内没有处理完成会发生 ANR
- logcat 日志关键字:Timeout of broadcast BroadcastRecord
- ServiceTimeout:前台 Service 的 onCreate、onStart、onBind 等生命周期方法在 20 秒内没有处理完成,后台 Service 的onCreate、onStart、onBind 等生命周期方法在 200 秒内没有处理完成会发生 ANR
- logcat 日志关键字:Timeout executing service
- ContentProviderTimeout:ContentProvider 在 10 秒内没有处理完成发生 ANR。
- logcat日志关键字:timeout publishing content providers
注意,KeyDispatchTimeout 与其他机制不同。对于 Input 来说,即便某次事件的执行时间超过 5 秒,只要用户后续没有再生成新的 Input 事件,就不会触发 ANR。
1.3 ANR 出现的原因
原因主要有以下几种:
- 主线程频繁进行耗时的 IO 操作:如数据库读写
- 多线程操作的死锁,主线程被 block
- 主线程被 Binder 对端 block
- System Server 中 WatchDog 出现 ANR
- Service binder 的连接达到上限无法和 System Server 通信
- 系统资源已耗尽(管道、CPU、IO)
凡是进行 IO 读写的操作,都不要放在主线程中,SharedPreferences 也涉及 IO 操作,也包含在内。此外,网络、序列化也不要放在主线程中。
多核 CPU 的执行效率不一样,以八核为例,一般 0~3 是小核,4~5 是中核,6~7 是大核,手机厂商在游戏模式中会将游戏绑定到大核上运行。
2、ANR 问题的解决
线下的 ANR 问题,有 3 个 log 文件可以寻找相关信息:
- /data/anr/trace_*.txt:一般 firstPid 就是发生 ANR 的 pid。主要是在 ActivityManagerservice 中通过 appNotResponding()、dumpStackTraces() 来生成应用的 ANR
- traces_SystemServer_WDT.txt:WatchDog 中实现,会打印 system_server 进程栈信息
- traces.txt:dalvik.vm.stack-trace-file,是系统定义的默认 trace 文件路径
线上的 ANR 问题,一般只能通过 Bugly 提供信息,而且信息还有可能不全,因此线上 ANR 是很不好解决的。
2.1 分析技巧
ANR 问题除了特别明显的那种,一般都不是一眼就能看出来问题点的,需要多个角度分析。
分析技巧主要有以下几点:
- 通过 logcat 日志,traces 文件确认 ANR 发生时间点
- traces 文件和 CPU 使用率
- /data/anr/traces.txt
- 主线程状态
- 其他线程状态
2.2 关键信息
关键信息1:
ANR时间:07-20 15:36:36.472
进程pid:1480
进程名:com.xxxx.moblie
ANR类型:KeyDispatchTimeout
关键信息2:
main:main标识是主线程,如果是线程,那么命名成“Thread-X”的格式,x表示线程id,逐步递增。
prio:线程优先级,默认是5
tid:tid不是线程的id,是线程唯一标识ID
group:是线程组名称
sCount:该线程被挂起的次数
dsCount:是线程被调试器挂起的次数
obj:对象地址
self:该线程Native的地址
sysTid:是线程号(主线程的线程号和进程号相同)
nice:是线程的调度优先级
sched:分别标志了线程的调度策略和优先级
cgrp:调度归属组
handle:线程处理函数的地址。
state:是调度状态
schedstat:从 /proc/[pid]/task/[tid]/schedstat读出,三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度,不支持这项信息的三个值都是0;
utm:是线程用户态下使用的时间值(单位是jiffies)
stm:是内核态下的调度时间值
core:是最后执行这个线程的cpu核的序号
线程状态:
THREAD_UNDEFINED = -1
THREAD_ZOMBIE = 0, /* TERMINATED /
THREAD_RUNNING = 1, / RUNNABLE or running now /
THREAD_TIMED_WAIT = 2,/ TIMED_WAITING Object.wait()
THREAD_MONITOR = 3, /* BLOCKED on a monitor /
THREAD_WAIT = 4, / WAITING in Object.wait() /
THREAD_INITIALIZING= 5, / allocated, not yet running /
THREAD_STARTING = 6, / started, not yet on thread list /
THREAD_NATIVE = 7, / off in a JNI native method /
THREAD_VMWAIT = 8, / waiting on a VM resource /
THREAD_SUSPENDED = 9, / suspended, usually by GC or debugger
3、ANR 线上监控方案
两种方案,原生的可以通过 FileObserver,也可以通过 WatchDog。
3.1 FileObserver
FileObserver 可以监控某个目录/文件的状态发生改变、创建或删除文件,可以监听 /data/anr/ 目录下的文件变化。这样在发生变化时可以上传所有 ANR 的信息到服务器上,但是有可能是其他应用发生的 ANR。示例代码:
public class ANRFileObserver extends FileObserver { public ANRFileObserver(String path) {//data/anr/ super(path); } public ANRFileObserver(String path, int mask) { super(path, mask); } @Override public void onEvent(int event, @Nullable String path) { switch (event) {case FileObserver.ACCESS://文件被访问Log.i(\"Zero\", \"ACCESS: \" + path);break;case FileObserver.ATTRIB://文件属性被修改,如 chmod、chown、touch 等Log.i(\"Zero\", \"ATTRIB: \" + path);break;case FileObserver.CLOSE_NOWRITE://不可写文件被 closeLog.i(\"Zero\", \"CLOSE_NOWRITE: \" + path);break;case FileObserver.CLOSE_WRITE://可写文件被 closeLog.i(\"Zero\", \"CLOSE_WRITE: \" + path);break;case FileObserver.CREATE://创建新文件Log.i(\"Zero\", \"CREATE: \" + path);break;case FileObserver.DELETE:// 文件被删除,如 rmLog.i(\"Zero\", \"DELETE: \" + path);break;case FileObserver.DELETE_SELF:// 自删除,即一个可执行文件在执行时删除自己Log.i(\"Zero\", \"DELETE_SELF: \" + path);break;case FileObserver.MODIFY://文件被修改Log.i(\"Zero\", \"MODIFY: \" + path);break;case FileObserver.MOVE_SELF://自移动,即一个可执行文件在执行时移动自己Log.i(\"Zero\", \"MOVE_SELF: \" + path);break;case FileObserver.MOVED_FROM://文件被移走,如 mvLog.i(\"Zero\", \"MOVED_FROM: \" + path);break;case FileObserver.MOVED_TO://文件被移来,如 mv、cpLog.i(\"Zero\", \"MOVED_TO: \" + path);break;case FileObserver.OPEN://文件被 openLog.i(\"Zero\", \"OPEN: \" + path);break;default://CLOSE : 文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)//ALL_EVENTS : 包括上面的所有事件Log.i(\"Zero\", \"DEFAULT(\" + event + \"): \" + path);break; } }}
FileObserver 在 5.0 的系统以上会受到 SELinux 的限制,手机厂商可以通过修改 .te 的配置文件规避掉这个限制。
3.2 WatchDog
WatchDog 是一个单例线程,在 Android 中主要用来检查 system_server 进程有没有死锁,或者某些线程有没有被卡住。其内部类 HandlerChecker 把自己加到 Handler 中:
public final class HandlerChecker implements Runnable { private final Handler mHandler; public void scheduleCheckLocked() { if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) { mCompleted = true; return; } if (!mCompleted) { // we already have a check in flight, so no need return; } mCompleted = false; mCurrentMonitor = null; mStartTime = SystemClock.uptimeMillis(); // 将自己加入到 Handler 中 mHandler.postAtFrontOfQueue(this); } @Override public void run() { final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { synchronized (Watchdog.this) { mCurrentMonitor = mMonitors.get(i); } mCurrentMonitor.monitor(); } synchronized (Watchdog.this) { mCompleted = true; mCurrentMonitor = null; } } }
WatchDog 会持续运行,检查 HandlerChecker 中的 run() 有没有被执行:
@Override public void run() { boolean waitedHalf = false; while (true) { final List<HandlerChecker> blockedCheckers; final String subject; final boolean allowRestart; int debuggerWasConnected = 0; synchronized (this) { long timeout = CHECK_INTERVAL; // Make sure we (re)spin the checkers that have become idle within // this wait-and-check interval for (int i=0; i<mHandlerCheckers.size(); i++) { HandlerChecker hc = mHandlerCheckers.get(i); hc.scheduleCheckLocked(); } if (debuggerWasConnected > 0) { debuggerWasConnected--; } long start = SystemClock.uptimeMillis(); while (timeout > 0) { if (Debug.isDebuggerConnected()) { debuggerWasConnected = 2; } try { wait(timeout); } catch (InterruptedException e) { Log.wtf(TAG, e); } if (Debug.isDebuggerConnected()) { debuggerWasConnected = 2; } timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start); } boolean fdLimitTriggered = false; if (mOpenFdMonitor != null) { fdLimitTriggered = mOpenFdMonitor.monitor(); } if (!fdLimitTriggered) { final int waitState = evaluateCheckerCompletionLocked(); if (waitState == COMPLETED) { // The monitors have returned; reset waitedHalf = false; continue; } else if (waitState == WAITING) { // still waiting but within their configured intervals; back off and recheck continue; } else if (waitState == WAITED_HALF) { if (!waitedHalf) { // We\'ve waited half the deadlock-detection interval. Pull a stack // trace and wait another half. ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); // 如果任务没执行,生成 trace 文件 ActivityManagerService.dumpStackTraces(true, pids, null, null, getInterestingNativePids()); waitedHalf = true; } continue; } // something is overdue! blockedCheckers = getBlockedCheckersLocked(); subject = describeCheckersLocked(blockedCheckers); } else { blockedCheckers = Collections.emptyList(); subject = \"Open FD high water mark reached\"; } allowRestart = mAllowRestart; ... } } }
我们可以借鉴 WatchDog 的原理自己检测:
代码如下:
public class ANRWatchDog extends Thread { private static final String TAG = \"ANR\"; private int timeout = 5000; private boolean ignoreDebugger = true; static ANRWatchDog sWatchdog; private Handler mainHandler = new Handler(Looper.getMainLooper()); private class ANRChecker implements Runnable { private boolean mCompleted; private long mStartTime; private long executeTime = SystemClock.uptimeMillis(); @Override public void run() { synchronized (ANRWatchDog.this) { mCompleted = true; executeTime = SystemClock.uptimeMillis(); } } void schedule() { mCompleted = false; mStartTime = SystemClock.uptimeMillis(); mainHandler.postAtFrontOfQueue(this); } boolean isBlocked() { return !mCompleted || executeTime - mStartTime >= 5000; } } public interface ANRListener { void onAnrHappened(String stackTraceInfo); } private ANRChecker anrChecker = new ANRChecker(); private ANRListener anrListener; public void addANRListener(ANRListener listener){ this.anrListener = listener; } public static ANRWatchDog getInstance(){ if(sWatchdog == null){ sWatchdog = new ANRWatchDog(); } return sWatchdog; } private ANRWatchDog(){ super(\"ANR-WatchDog-Thread\"); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 设置为后台线程 while(true){ while (!isInterrupted()) { synchronized (this) { anrChecker.schedule(); long waitTime = timeout; long start = SystemClock.uptimeMillis(); // 预防假唤醒(概率很低),从源码借鉴来的 while (waitTime > 0) { try { wait(waitTime); } catch (InterruptedException e) { Log.w(TAG, e.toString()); } waitTime = timeout - (SystemClock.uptimeMillis() - start); } if (!anrChecker.isBlocked()) { continue; } } if (!ignoreDebugger && Debug.isDebuggerConnected()) { continue; } String stackTraceInfo = getStackTraceInfo(); if (anrListener != null) { anrListener.onAnrHappened(stackTraceInfo); } } anrListener = null; } } private String getStackTraceInfo() { StringBuilder stringBuilder = new StringBuilder(); for (StackTraceElement stackTraceElement : Looper.getMainLooper().getThread().getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append(\"\\r\\n\"); } return stringBuilder.toString(); }}
WatchDog 会有性能损耗。