五、ANR产生的原因及其定位分析

ANR 全称Application Not Responding(应用程序无响应),一般页面卡顿时间超过(一般是5秒)一定时间就会出现ANR对话款。Logcat一般会发现ANR以及traces.txt等字样。出现ANR主要原因是因为我们在主线程中做了太多耗时操作。

A、ANR产生的原因

只有当应用程序的UI线程相应超时才会引起ANR,超时产生原因一般有两种
(1)当前的事件没有机会得到处理,例如UI线程正在响应另外一个事件,当前事件由于某种原因被阻塞了。
(2)当前事件正在处理,但是由于耗时太长没能及时完成

根据ANR产生的原因不同,超时事件也不尽相同,从本质上讲,产生ANR的原因有三种,大致可以对应到Android 中四大组件中的三个(activity/view,BroadcastReceiver和service)。

(1)KeyDispatchTimeOut类型

最常见的一种类型,原因是View的按键事件或触摸事件在特定的事件(5秒)内无法得到响应

(2)BroadcastTimeOut类型

原因是BroadcastReciver的onReceive()函数运行在了主线程中,在特定的事件(10秒)内无法完成处理

(3)ServiceTimeOut类型

比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20秒)内无法完成处理

B、典型的ANR问题场景

(1)场景一:

应用程序UI线程存在耗时操作,例如在UI线程中进行网络请求,数据库操作或者文件操作等,可能会导致UI线程无法及时处理用户输入等。当然在Android4.0之后,如果在UI线程中进行网络操作,将会抛出NetworkOnMainTreadException异常。

(2)场景二:

应用程序的UI线程等待子线程释放某个锁,从而无法处理用户的输入。

(3)场景三:

耗时的动画需要大象的计算工作,可能会导致CPU负载过重

C、ANR的定位和分析

当发生ANR时,可以通过结合Logcat日志和生成的位于手机内部存储的/data/anr/traces.txt文件进行分析和定位。

(1)Logcat日志信息

查看日志

(2)traces.txt日志信息

有助于问题已定位的信息主要内容

a、发生ANR的进程名称、ID,以及时间。

b、其他

五、ANR产生的原因及其定位分析_第1张图片
QQ图片20180921175250.png

c、主工程基本信息

五、ANR产生的原因及其定位分析_第2张图片
QQ图片20180921175347.png

d、主线程的详细信息

五、ANR产生的原因及其定位分析_第3张图片
QQ图片20180921175423.png

e、线程的调度信息

五、ANR产生的原因及其定位分析_第4张图片
QQ图片20180921175502.png

f、线程的上下文信息

五、ANR产生的原因及其定位分析_第5张图片
QQ图片20180921175543.png

g、线程的堆栈信息

五、ANR产生的原因及其定位分析_第6张图片
QQ图片20180921175624.png

D、ANR的避免和检测

(1)不要在主线程中做耗时操作

为了避免开发中可能发生的ANR的问题,除了切记不要再主线程中做耗时操作,我们也可以借助一些工具来进行检测,从而更有效的避免ANR的引入

(2)StrictMode(代码检测)

严格模式StrictMode 是Android SDK提供的一个用来检测代码是否存在违规操作的工具类,StrictMode 主要检测两大类为题

a、线程策略ThreadPolicy

检测可能存在的主线程耗时操作,解决这些检测到的问题能够减少应用发生的ANR的概率。需要注意的是我们只能在DEbug版本中使用它,发布到市场版本必须关掉。


五、ANR产生的原因及其定位分析_第7张图片
QQ图片20180921175750.png

b、虚拟机策略VmPolicy
五、ANR产生的原因及其定位分析_第8张图片
QQ图片20180921175827.png

c、StrictMode使用

使用很简单,我们只需要在应用初始化的地方例如Application或者MainActivity类中onCreate方法中执行如下代码

@Override
protected void onCreate(Bundle savedInstanceState) {
    if (BuildConfig.DEBUG) {
        /**开启线程模式*/
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
        /**开启虚拟机模式*/
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
    }
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_anr);
}

调用detectAll表示检测所有的检测策略,我们也可以根据应用需求只开启一些策略

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
    /**开启线程模式,某些策略*/
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectCustomSlowCalls()
            .penaltyLog()
            .build());

    /**开启虚拟机模式,某些策略*/
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectActivityLeaks()
            .detectLeakedRegistrationObjects()
            .penaltyLog()
            .build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr);
}

BlockCanary(非侵入式性能监控函数库)

BlockCanary是一个非侵入时的性能监控函数库,他的用法和LeakCanary类似,只不过LeakCanary检测内存泄漏,而BlockCanary主要用来监控应用主线程的卡顿,它的基本原理利用主线程的消息队列处理机制,通过对比消息分发开始和结束的事件点来判断是否超过设定的时间,如果是,则判断为主线程卡顿。
集成顺序:

a、build.gradle添加依赖

//主线程卡顿监控依赖
implementation 'com.github.moduth:blockcanary-android:1.5.0'
//如果仅在debug 包启用BlockCanary 进行卡顿监控和提示的话,这样写
//debugCompile 'com.github.moduth:blockcanary-android:1.5.0'
//releaseCompile 'com.github.moduth:blockcanary-no-op:1.5.0'

b、在Application中调用

@Override
public void onCreate() {
super.onCreate();
/**初始化调用*/
BlockCanary.install(this,new AppBlockCanaryContext()).start();
}

public class AppBlockCanaryContext extends BlockCanaryContext{
/**
 * 实现各种上下文,包括应用标识符、用户uid、网络类型、卡曼判断阈值、Log保存位置等
 */
}

你可能感兴趣的:(五、ANR产生的原因及其定位分析)