Android 性能优化入门(三)—— ANR 问题分析

2026-08-03 00:56:26 7686

需要清楚 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。示例代码:

java

复制代码

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://不可写文件被 close

Log.i("Zero", "CLOSE_NOWRITE: " + path);

break;

case FileObserver.CLOSE_WRITE://可写文件被 close

Log.i("Zero", "CLOSE_WRITE: " + path);

break;

case FileObserver.CREATE://创建新文件

Log.i("Zero", "CREATE: " + path);

break;

case FileObserver.DELETE:// 文件被删除,如 rm

Log.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://文件被移走,如 mv

Log.i("Zero", "MOVED_FROM: " + path);

break;

case FileObserver.MOVED_TO://文件被移来,如 mv、cp

Log.i("Zero", "MOVED_TO: " + path);

break;

case FileObserver.OPEN://文件被 open

Log.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 中:

java

复制代码

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() 有没有被执行:

java

复制代码

@Override

public void run() {

boolean waitedHalf = false;

while (true) {

final List 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

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 pids = new ArrayList();

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 的原理自己检测:

代码如下:

java

复制代码

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 会有性能损耗。