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

需要清楚 ANR 的概念、类型、如何产生以及如何定位分析。

1、概述

1.1 ANR 的概念

ANR(Application Not Responding)应用程序无响应。如果你应用程序在主线程被阻塞太长时间,就会出现 ANR,通常出现 ANR,系统会弹出一个提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。

1.2 ANR 类型

ANR 有 4 种类型:

  1. KeyDispatchTimeout(常见):Input 事件在 5 秒内没有处理完成导致发生 ANR
    • logcat 日志关键字:Input event dispatching timed out
  2. BroadcastTimeout:前台 BroadcastReceiver 的 onReceive() 在 10 秒内没有处理完成、后台 BroadcastReceiver 的 onReceive() 在 60 秒内没有处理完成会发生 ANR
    • logcat 日志关键字:Timeout of broadcast BroadcastRecord
  3. ServiceTimeout:前台 Service 的 onCreate、onStart、onBind 等生命周期方法在 20 秒内没有处理完成,后台 Service 的onCreate、onStart、onBind 等生命周期方法在 200 秒内没有处理完成会发生 ANR
    • logcat 日志关键字:Timeout executing service
  4. ContentProviderTimeout:ContentProvider 在 10 秒内没有处理完成发生 ANR。
    • logcat日志关键字:timeout publishing content providers

注意,KeyDispatchTimeout 与其他机制不同。对于 Input 来说,即便某次事件的执行时间超过 5 秒,只要用户后续没有再生成新的 Input 事件,就不会触发 ANR。

1.3 ANR 出现的原因

原因主要有以下几种:

  1. 主线程频繁进行耗时的 IO 操作:如数据库读写
  2. 多线程操作的死锁,主线程被 block
  3. 主线程被 Binder 对端 block
  4. System Server 中 WatchDog 出现 ANR
  5. Service binder 的连接达到上限无法和 System Server 通信
  6. 系统资源已耗尽(管道、CPU、IO)

凡是进行 IO 读写的操作,都不要放在主线程中,SharedPreferences 也涉及 IO 操作,也包含在内。此外,网络、序列化也不要放在主线程中。

多核 CPU 的执行效率不一样,以八核为例,一般 0~3 是小核,4~5 是中核,6~7 是大核,手机厂商在游戏模式中会将游戏绑定到大核上运行。

2、ANR 问题的解决

线下的 ANR 问题,有 3 个 log 文件可以寻找相关信息:

  1. /data/anr/trace_*.txt:一般 firstPid 就是发生 ANR 的 pid。主要是在 ActivityManagerservice 中通过 appNotResponding()、dumpStackTraces() 来生成应用的 ANR
  2. traces_SystemServer_WDT.txt:WatchDog 中实现,会打印 system_server 进程栈信息
  3. traces.txt:dalvik.vm.stack-trace-file,是系统定义的默认 trace 文件路径

线上的 ANR 问题,一般只能通过 Bugly 提供信息,而且信息还有可能不全,因此线上 ANR 是很不好解决的。

2.1 分析技巧

ANR 问题除了特别明显的那种,一般都不是一眼就能看出来问题点的,需要多个角度分析。

分析技巧主要有以下几点:

  1. 通过 logcat 日志,traces 文件确认 ANR 发生时间点
  2. traces 文件和 CPU 使用率
  3. /data/anr/traces.txt
  4. 主线程状态
  5. 其他线程状态

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

代码如下:

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

相关推荐
coderlin_3 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
2501_915918414 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview
wen's5 小时前
React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!
android·xml·react native
编程乐学6 小时前
网络资源模板--基于Android Studio 实现的聊天App
android·android studio·大作业·移动端开发·安卓移动开发·聊天app
EndingCoder7 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
没有了遇见8 小时前
Android 通过 SO 库安全存储敏感数据,解决接口劫持问题
android
hsx6668 小时前
使用一个 RecyclerView 构建复杂多类型布局
android
hsx6668 小时前
利用 onMeasure、onLayout、onDraw 创建自定义 View
android
守城小轩8 小时前
Chromium 136 编译指南 - Android 篇:开发工具安装(三)
android·数据库·redis
whysqwhw9 小时前
OkHttp平台抽象机制分析
android