ANR问题分析

  • ANR有哪些类型?
  • 什么情况下会产生ANR?
  • ANR如何解决?

ANR概念

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

出现ANR必须解决

ANR类型

KeyDispatchTimeout(常见)

input事件在5S内没有处理完成发生了ANR。
logcat日志关键字:Input event dispatching timed out

BroadcastTimeout

前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
logcat日志关键字:Timeout of broadcast BroadcastRecord

ServiceTimeout

前台Service:onCreate,onStart,onBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreate,onStart,onBind等生命周期在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service

ContentProviderTimeout

ContentProvider 在10S内没有处理完成发生ANR。
logcat日志关键字:timeout publishing content providers

为什么出现ANR

1:主线程频繁进行耗时的IO操作:如网络请求,数据库读写

UI线程进行潜在的耗时操作都很可能造成ANR,如网络请求,操作数据库、IO操作、复杂的layout、庞大的for循环、高耗时的计算(如改变位图尺寸)等。

这些耗时操作都应该在子线程中来完成,然而并不是说主线程阻塞在那里等待子线程的完成(也不是调用Thread.wait()或是Thread.sleep()),而是主线程应该为子线程提供给一个Handler或者使用AsyncTask等异步操作的方式,以便子线程完成操作时能够提交结果给主线程,主线程根据结果更新UI。以这种方式设计应用,将可以保证主线程保持对输入事件的响应的敏感性,并能避免由于输入事件的5秒超时引发的ANR对话框。

具体例子:

  • (1)Realm数据库官方称速度非常快,可以在UI线程中操作,测试时的确没有问题,但是上线后,发现一大堆anr问题(项目接入bugly)。
  • (2)在onlayout读写一个文件,用sysytrace测试执行时间,只有微秒级别,测试时没有任何问题,但是上线后一大堆anr问题。
  • (3)主线程使用SharedPreference,测试时没有任何问题,但是上线后一大堆anr问题。

原因:
为什么测试时没有任何anr问题,一上线就报各种anr问题?
测试用的手机都是性能比较好的手机,而且手机没有安装各种一大堆软件,内存占用少,硬件性能好。而用户的手机可能是性能不大好的机子,而且可能还安装了大量的软件,手机速度下降等等。

总结:
凡是进行IO读写操作(数据库,文件,网络,序列化等),都不要在UI线程操作。

2:多线程操作的死锁,主线程被block;

并发编程导致的死锁引起的ANR,比如调用thread的join() / Sleep() / Wait() 或者等待lock的时候

3:主线程被 Binder 对端block;

Binder通信机制默认是同步的,会阻塞调用的线程,当然也有异步的oneway机制。

4:System Server中WatchDog出现ANR;

framework层的开发遇到的比较多。

5:service binder的连接数量达到上线无法和和System Server通信

Binder被占满导致主线程无法和SystemServer通信

6:系统资源已耗尽(管道、CPU、RAM、IO),无法获取到系统资源

大量的线程死循环的去做任务,导致应用获取不到CPU的时间片去处理用户输入事件。

ANR问题如何解决

线下开发过程中,如果出现ANR,Android Studio的logcat会有输出信息提示(但并不一定真的是那一行代码导致的anr,需要根据提示信息综合判断),并且还会在/data/anr/traces_*.txt 中保存有anr相关信息,结合logcat中的提示信息判断anr是由什么原因导致的。

traces_*.txt

*一般是firstPid,即发生anr的pid

ActivityManagerservice 中实现 通过appNotResponding(), dumpStackTraces() 两个主要方法来生成应用的anr

traces_SystemServer_WDT.txt(watchdog)

Watchdog中实现
system_server进程栈信息

traces.txt(dalvik.vm.stack-trace-file)

系统定义的默认trace文件路径

分析技巧

整体思路:
通过logcat日志和traces文件确认anr发生时间点
traces文件和CPU使用率
/data/anr/traces.txt
主线程状态
其他线程状态

如何根据logcat日志和traces文件分析anr?
1.根据anr的几种类型,在logcat日志中搜索对应的关键字。
比如:Input event dispatching timed out
Timeout of broadcast BroadcastRecord
Timeout executing service
timeout publishing content providers
2.在traces文件中搜索对应的关键字。
比如:waiting、held、held by等

比如搜索waiting关键字:
在这里插入图片描述
可以发现就是发送广播时造成的堵塞,引起的ANR。

再比如下面这种,io占用了CPU 87%:
在这里插入图片描述
一般就是在主线程进行IO操作引起的ANR。

关键信息

ANR时间:07-20 15:36:36.472
进程pid:1480
进程名:com.xxxx.moblie
ANR类型:KeyDispatchTimeout

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

Android线程状态

在这里插入图片描述

ANR监控方案

ANR是如何监控的?
一般有两种监控方案,一种是原生的,FileObserver:监控某个目录/文件的状态有没有发生改变,或者是在某个目录 有没有创建或者删除文件。
一般通过自定义类继承FileObserver:
class MyFileObserver extends FileObserver //路径传data/anr
这样当文件发生变化时说明有ANR产生(这时可以上传trace.txt文件到服务器用于分析anr),当文件发生相应的变化时会回调FileObserver 中的public void onEvent(int event, @Nullable String path)方法。

另一种是,watchdog方案。

ANR监控方案1-watchdog

watchdog 和leakcanary原理很相似。
在这里插入图片描述
在这里插入图片描述
Watchdog.java
Watchdog有个假唤醒机制,while循环判断,如果还没有wait到足够的时间,那么继续wait:

//Watchdog.java

                // NOTE: We use uptimeMillis() here because we do not want to increment the time we
                // wait while asleep. If the device is asleep then the thing that we are waiting
                // to timeout on is asleep as well and won't have a chance to run, causing a false
                // positive on when to kill things.
                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);
                }
                

这个小技巧在很多地方都有使用。

ANR监控方案2-FileObserver

FileObserver.java

Android系统在此基础上封装了一个FileObserver类来方便使用Inotify机制。FileObserver是一个抽象类,需要定义子类实现该类的onEvent抽象方法,当被监控的文件或者目录发生变更事件时,将回调FileObserver的onEvent()函数来处理文件或目录的变更事件

public class ANRFileObserver extends FileObserver {
    private String TAG = "ANRFileObserver";


    /**
     *
     * @param path 传 data/anr/ 这个目录
     */
    public ANRFileObserver(String path) {
        super(path);
    }

    public ANRFileObserver(String path, int mask) {
        super(path, mask);
    }


    /**
     * ANRFileObserver监控的路径/文件发生改变时会回调onEvent()方法,对应的各个事件类型如下
     * @param event
     * @param path
     */
    @Override
    public void onEvent(int event, @Nullable String path) {
        switch (event) {
            case FileObserver.ACCESS://文件被访问
                Log.i(TAG, "ACCESS: " + path);
                break;
            case FileObserver.ATTRIB://文件属性被修改,如 chmod、chown、touch 等
                Log.i(TAG, "ATTRIB: " + path);
                break;
            case FileObserver.CLOSE_NOWRITE://不可写文件被 close
                Log.i(TAG, "CLOSE_NOWRITE: " + path);
                break;
            case FileObserver.CLOSE_WRITE://可写文件被 close
                Log.i(TAG, "CLOSE_WRITE: " + path);
                break;
            case FileObserver.CREATE://创建新文件
                Log.i(TAG, "CREATE: " + path);
                break;
            case FileObserver.DELETE:// 文件被删除,如 rm
                Log.i(TAG, "DELETE: " + path);
                break;
            case FileObserver.DELETE_SELF:// 自删除,即一个可执行文件在执行时删除自己
                Log.i(TAG, "DELETE_SELF: " + path);
                break;
            case FileObserver.MODIFY://文件被修改
                Log.i(TAG, "MODIFY: " + path);
                break;
            case FileObserver.MOVE_SELF://自移动,即一个可执行文件在执行时移动自己
                Log.i(TAG, "MOVE_SELF: " + path);
                break;
            case FileObserver.MOVED_FROM://文件被移走,如 mv
                Log.i(TAG, "MOVED_FROM: " + path);
                break;
            case FileObserver.MOVED_TO://文件被移来,如 mv、cp
                Log.i(TAG, "MOVED_TO: " + path);
                break;
            case FileObserver.OPEN://文件被 open
                Log.i(TAG, "OPEN: " + path);
                break;
            default:
                //CLOSE : 文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
                //ALL_EVENTS : 包括上面的所有事件
                Log.i(TAG, "DEFAULT(" + event + "): " + path);
                break;
        }
    }
}

FileObserver会不会被selinux挡住?
Android 5.0之后肯定会。

实例分析ANR

实例1:

先看cpu:

CPU usage from 76016ms to 0ms ago (2020-01-01 13:06:16.027 to 2020-01-01 13:07:32.043):
...
26% TOTAL: 22% user + 3.6% kernel + 0% iowait + 0.1% softirq

从CPU使用数据可以看出不是因为CPU使用率高资源耗尽或者IO读写堵塞引起的ANR。

看是否是由于线程死锁导致的,可以在traces.txt文件中搜索 held 关键字:

suspend all histogram:	Sum: 482us 99% C.I. 15us-232us Avg: 48.200us Max: 239us
DALVIK THREADS (13):
"main" prio=5 tid=1 Runnable
  | group="main" sCount=0 dsCount=0 flags=0 obj=0x742b2a28 self=0x715052814c00
  | sysTid=16842 nice=-10 cgrp=default sched=0/0 handle=0x7150d8012548
  | state=R schedstat=( 6802667952 210121864 1103 ) utm=610 stm=70 core=2 HZ=100
  | stack=0x7ffefec17000-0x7ffefec19000 stackSize=8MB
  | held mutexes= "mutator lock"(shared held)
  at com.zero.interviewdemo.MainActivity.clickTest(MainActivity.java:26)
  at java.lang.reflect.Method.invoke(Native method)
  at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:397)
  at android.view.View.performClick(View.java:6597)
  at android.view.View.performClickInternal(View.java:6574)
  at android.view.View.access$3100(View.java:778)
  at android.view.View$PerformClick.run(View.java:25885)
  at android.os.Handler.handleCallback(Handler.java:873)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:193)
  at android.app.ActivityThread.main(ActivityThread.java:6669)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

可以看到是由于线程死锁导致的anr。

实例2:

可以参考:什么是ANR,如何避免ANR的 trace.txt文件解读

参考:
Iowait的成因、对系统影响及对策
Android 系统中的 WatchDog 详解
性能优化-Android之ANR分析解决 traces.txt文件分析
Android ANR监测方案解析

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值