Android进阶知识:ANR的定位与解决

一、前言

ANR对于Android开发者来讲必定不会陌生,从刚开始学习Android时的一不注意就ANR,到后来知道主线程不能进行耗时操做注意到这点后,程序出现ANR的状况就大大减小了,甚至于消失了。那么真的是只要在主线程作耗时操做就会产生ANR吗?为何在有时候明明以为本身没在主线程作耗时操做也出现了ANR呢?一旦出现莫名其妙的ANR,怎么定位致使ANR的产生的位置和解决问题呢?那么接下来就来一个个的解决这些问题。java

二、ANR是什么?

ANR全称Application Not Responding即应用程序无响应。在Android中若是应用程序有一段时间没法响应用户操做,系统会弹出弹窗,让用户选择是继续等待仍是强制关闭程序。一款良好应用APP是不该该出现这个弹窗的。react

三、ANR的产生缘由

ANR产生缘由和类型有如下几种:android

一、Activity在5秒钟以内没法响应屏幕触摸事件挥着键盘输入事件就会产生ANR

KeyDispatchTimeout
Reason:Input event dispatching timed outshell

二、BroadcastReceiver在10秒钟以内还未执行完成就会产生ANR

BroadcastTimeout
Reason:Timeout of broadcast BroadcastRecordbash

三、Service各个生命周期在20秒钟以内没有执行完成就会产生ANR

ServiceTimeout
Reason:Timeout executing servicemarkdown

四、ContentProvider在10秒钟以内没有执行完成就会产生ANR

ContentProviderTimeout
Reason:timeout publishing content providers多线程

在以上这几种缘由中出现最多的通常是第一种,并且每每都是由于在写代码时不注意,在主线程作了耗时的操做。ide

四、ANR的定位与解决

关于ANR的定位这里举一个例子来看。这是我以前遇到的一次出现ANR的时候所解决问题的状况和解决步骤。
函数

首先固然是复现 ANR现象,找准 ANR出现的地方,查看对应代码,若是能直接看出来问题所在,找到代码中作的错误操做那么直接修改相应代码就解决问题了。可是若是无法轻易看出问题缘由,接下来就只好去 Logcat中查看对应的错误日志。

07-22 21:39:17.019 819-851/? E/ActivityManager: ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)
    PID: 7398
    Reason: Input dispatching timed out (com.xxxx.performance/com.xxxx.performance.view.home.activity.MainActivity, Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 29.  Wait queue head age: 8579.3ms.)
    Load: 18.22 / 18.1 / 18.18
    CPU usage from 0ms to 8653ms later:
      124% 7398/com.xxxx.performance: 118% user + 6.5% kernel / faults: 4962 minor 7 major
      82% 819/system_server: 28% user + 53% kernel / faults: 10555 minor 11 major
      23% 4402/adbd: 1% user + 22% kernel
      10% 996/com.android.systemui: 4.6% user + 6.2% kernel / faults: 4677 minor 1 major
      4.6% 2215/com.android.phone: 1.5% user + 3.1% kernel / faults: 5411 minor
      6.3% 6268/perfd: 3.4% user + 2.8% kernel / faults: 134 minor
      0.5% 1149/com.miui.whetstone: 0.1% user + 0.3% kernel / faults: 3016 minor 1 major
      0.2% 2097/com.xiaomi.finddevice: 0.1% user + 0.1% kernel / faults: 2256 minor
      0.6% 2143/com.miui.daemon: 0.2% user + 0.3% kernel / faults: 2798 minor
      1.2% 1076/com.xiaomi.xmsf: 0.6% user + 0.6% kernel / faults: 2802 minor
      0% 2122/com.android.server.telecom: 0% user + 0% kernel / faults: 2929 minor
      0% 2244/com.miui.contentcatcher: 0% user + 0% kernel / faults: 1800 minor
      0% 2267/com.mediatek.nlpservice: 0% user + 0% kernel / faults: 2052 minor
      0% 2166/com.xiaomi.mitunes: 0% user + 0% kernel / faults: 1797 minor 3 major
      0% 2190/com.fingerprints.service: 0% user + 0% kernel / faults: 1857 minor
      0.1% 154/mmcqd/0: 0% user + 0.1% kernel
      0.4% 8069/logcat: 0.3% user + 0.1% kernel
      ......
复制代码

从日志第一行开始看,能够看到发生错误的应用包名和类名,这里是ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)。接着看到进程号PID7398。发生ANRReasonInput dispatching timed out就是上面提到的第一种。再往下就是活跃进程的CPU占用率日志。学习

124% 7398/com.xxxx.performance
   82% 819/system_server
   10% 996/com.android.systemui
   4.6% 2215/com.android.phone
   ......
复制代码

光看Logcat中的日志只能看到这些信息,大概知道是在MainActivity出现了问题,但仍是不能清楚的定位到发生ANR的代码行,想要得到进一步的错误信息只能经过查看ANR过程当中生成的堆栈信息文件traces.txt了。

traces.txt文件位置在/data/anr/目录下,能够经过如下adb命令将其拷贝到sd卡目录下获取查看。

adb shell
cat  /data/anr/traces.txt  >/mnt/sdcard/traces.txt  
exit
复制代码

traces.txt里的信息:

DALVIK THREADS (42):
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x75ceafb8 self=0x55933ae7e0
  | sysTid=7398 nice=0 cgrp=default sched=0/0 handle=0x7f7ddae0f0
  | state=S schedstat=( 101485399944 3411372871 31344 ) utm=9936 stm=212 core=1 HZ=100
  | stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0x74/0x8c
  kernel: futex_wait_queue_me+0xcc/0x158
  kernel: futex_wait+0x120/0x20c
  kernel: do_futex+0x184/0xa48
  kernel: SyS_futex+0x88/0x19c
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 00017750 /system/lib64/libc.so (syscall+28)
  native: #01 pc 000d1584 /system/lib64/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+140)
  native: #02 pc 00388098 /system/lib64/libart.so (_ZN3artL12GoToRunnableEPNS_6ThreadE+1068)
  native: #03 pc 000a5db8 /system/lib64/libart.so (_ZN3art12JniMethodEndEjPNS_6ThreadE+24)
  native: #04 pc 000280e4 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_graphics_Paint_native_1init__+156)
  at android.graphics.Paint.native_init(Native method)
  at android.graphics.Paint.<init>(Paint.java:435)
  at android.graphics.Paint.<init>(Paint.java:425)
  at android.text.TextPaint.<init>(TextPaint.java:49)
  at android.text.Layout.<init>(Layout.java:160)
  at android.text.StaticLayout.<init>(StaticLayout.java:111)
  at android.text.StaticLayout.<init>(StaticLayout.java:87)
  at android.text.StaticLayout.<init>(StaticLayout.java:66)
  at android.widget.TextView.makeSingleLayout(TextView.java:6543)
  at android.widget.TextView.makeNewLayout(TextView.java:6383)
  at android.widget.TextView.checkForRelayout(TextView.java:7096)
  at android.widget.TextView.setText(TextView.java:4082)
  at android.widget.TextView.setText(TextView.java:3940)
  at android.widget.TextView.setText(TextView.java:3915)
  at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)
  at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:214)
  at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:205)
  at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:198)
  at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:250)
  at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
  ......
  ......
复制代码

仍是从头开始看,来看每一个字段对应的含义:
线程名:main
线程优先级:prio=5
线程锁ID: tid=1
线程状态:Native 线程组名称:group="main"
线程被挂起的次数:sCount=1
线程被调试器挂起的次数:dsCount=0
线程的java的对象地址:obj=0x75ceafb8
线程自己的Native对象地址:self=0x55933ae7e0
线程调度信息:
Linux系统中内核线程ID: sysTid=7398与主线程的进程号相同
线程调度优先级:nice=0
线程调度组:cgrp=default
线程调度策略和优先级:sched=0/0
线程处理函数地址:handle=0x7f7ddae0f0
线程的上下文信息:
线程调度状态:state=S
线程在CPU中的执行时间、线程等待时间、线程执行的时间片长度:schedstat=(101485399944 3411372871 31344 )
线程在用户态中的调度时间值:utm=9936
线程在内核态中的调度时间值:stm=212
最后执行这个线程的CPU核序号:core=1
线程的堆栈信息:
堆栈地址和大小:stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
最后看到堆栈信息里的这一行:

at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)
复制代码

这里就看清楚了是在AttendanceCheckInFragment中的887行出现的问题,再到对应代码行中就很容易发现ANR的缘由了。

五、ANR的相关问题

  • 在Activity的onCreate方法里调用sleep方法会发生ANR吗?

之前一直认为在主线程作了耗时操做就会发生ANR,那么真的是这样吗?在ActivityonCreate方法里调用Thread.sleep(60 * 1000)让主线程sleep60秒,会致使应用程序ANR吗?写个Demo测试一下。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Log.d("ANR","开始sleep");
            Thread.sleep(60*1000);
            Log.d("ANR","sleep完成");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码

如上代码,运行程序,结果应用没有发生ANR,在sleep了60秒后正常打印日志。

再次运行程序,这回在程序运行后按下返回键查看现象:

此次果真就 ANR了。经过这个例子,显而易见的获得了这个问题的正确答案。在 ActivityonCreate方法里调用 sleep方法或者说作耗时操做,不必定会产生 ANR。其实从 ANR自己意为应用程序没有响应,同时根据上面总结的 ANR缘由就能够看出,耗时操做自己是不会产生 ANR的,致使 ANR的根本仍是应用程序没法在必定时间内响应用户的操做。因此由于主线程被耗时操做占用了,主线成程没法对下一个操做进行响应才会 ANR,没有须要响应的操做天然就不会产生 ANR,或者应该这样说:主线程作耗时操做,很是容易引起 ANR

六、总结

  • 光在主线程作耗时操做不会产生ANR,超时响应用户操做才会产生ANR。
  • ANR的定位方法主要是根据Logcat中日志和ANR过程当中生成的堆栈信息文件traces.txt。
  • 解决问题不如预防问题,写代码的时候要注意预防产生ANR。
  • 预防ANR的产生不光是在Activity中注意要把耗时操做放到子线程中去,还要注意在使用其余三个组件时,在其生命周期中一样不能作太耗时的操做。另外在使用多线程时候要注意同步和死锁的状况,一旦产生死锁主线程一样会引起ANR。
相关文章
相关标签/搜索