APP性能优化--ANR

作者: 初夏的雪 | 来源:发表于2020-11-02 19:12 被阅读0次

1.什么是ANR?

ANR (Application Not Responding) ,顾名思义,应用程序没有响应。

官方定义:
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。 用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现ANR,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。

​ 如下图所示:

可亲可气的ANR提示框

2.ANR在什么场景下会出现?

ANR主要在以下四种场景中发生,以及发生的时限如下图所示:
ANR场景.png

3.该如何避免ANR的发生?

​ 1)运行在主线程里的任何方法都尽可能少做事(即:UI主线程尽量只做和UI相关的工作,尤其是在Activity的生命周期回调中,如OnCreate(),OnResume()等);
​ 建议:可以将耗时的操作放到子线程做,通过handler来通知主线程

2)尽量避免在BroadcastReceiver中做耗时的操作和计算;
建议:因为receier的生命周期很短,可以将耗时操作放到一个servic中来做。

3)尽量避免在IntentReceiver里面启动一个Activity;
建议:因为新启动的activity会从当前用户正在运行的程序中抢夺焦点,如果你需要给用户展示些什么东西的话,你可以使用Notification Manager来实现。

4)耗时操作:IO读写,SharePreference 的读写,数据库操作,网络请求、下载、或者其他比较耗时的计算,都应该放在单独的线程中处理。尽量用Handler来处理主线程与子线程的交互,耗时操作可以放在子线程中

4.ANR日志分析

当应用程序发生ANR时,系统会马上去抓取线程的信息,并保存在/data/anr/traces.txt文件中,那我们就开始取出文件分析吧。

背景说明:

​ 笔者单独写了一个点击耗时操作的demo,用于ANR的发生,代码非常简单:

public void onclick(View view) {
    try {
        Thread.sleep(10000000);
    }catch ( InterruptedException e)
    {
        e.printStackTrace();
    }
}

4.1 读取trace文件

在发生ANR应用的android studio中,点击terminal终端窗口,在窗口中输入:

输入指令:

adb  pull /data/anr/traces.txt  XXXXXXXXXX

说明:XXXXXXX为导出文件存放的全路径,可以不写,则默认在工程的根目录中。
 输出结果:
 
 D:\android_workspace\projects\ANRProject>adb pull /data/anr/traces.txt 
 /data/anr/traces.txt: 1 file pulled, 0 skipped. 15.1 MB/s (138264 bytes in 0.009s)

说明:trace文件中的内容并不一定仅仅是当前应用的,还会有其他的应用;
      可以通过包名、案发现场的时间点,或者其他的关键字来查找对应的进程

4.2 分析trace文件

应用进程发生ANR的基本信息

----- pid 31264 at 2020-11-02 14:45:18 -----
Cmd line: com.leon.anrproject
Build fingerprint: 'HONOR/KNT-AL20/HWKNT:8.0.0/HUAWEIKNT-AL20/556(C00):user/release-keys'
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=4753 post zygote classes=207
Intern table: 46128 strong; 139 weak
JNI: CheckJNI is off; globals=549 (plus 33 weak)
Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so
......

//已分配的对内存大小6M ,其中 2M已经使用,总分配29999 个对象

Heap: 66% free, 2MB/6MB; 29999 objects

//gc回收部分

Dumping cumulative Gc timings
Cumulative bytes moved 2785344
......

ProfileSaver total_number_of_hot_spikes=1
ProfileSaver total_number_of_wake_ups=0

suspend all histogram:  Sum: 351.496ms 99% C.I. 0.041ms-244.121ms Avg: 6.276ms Max: 332.429ms

当前进程总16个线程

DALVIK THREADS (16):
"Signal Catcher" daemon prio=5 tid=3 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x13700020 self=0x714945ca00
  | sysTid=31272 nice=0 cgrp=default sched=0/0 handle=0x713d4724f0
  | state=R schedstat=( 47709895 3769793 92 ) utm=0 stm=4 core=0 HZ=100
  | stack=0x713d378000-0x713d37a000 stackSize=1005KB
  | held mutexes= "mutator lock"(shared held)
  native: #00 pc 000000000039859c  /system/lib64/libart.so (_ZN3art15DumpNativeStackERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEEiP12BacktraceMapPKcPNS_9ArtMethodEPv+212)
...
  native: #10 pc 000000000001eee4  /system/lib64/libc.so (__start_thread+68)
  (no managed stack frames)

//主线程调用栈

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7405eb10 self=0x71494a3a00
  | sysTid=31264 nice=-10 cgrp=default sched=0/0 handle=0x714e4809b0
  | state=S schedstat=( 382595822 34890624 447 ) utm=30 stm=8 core=3 HZ=100
  | stack=0x7ffbcbb000-0x7ffbcbd000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep(Native method)

  - sleeping on <0x007bfd38> (a java.lang.Object)
    at java.lang.Thread.sleep(Thread.java:386)
  - locked <0x007bfd38> (a java.lang.Object)
    at java.lang.Thread.sleep(Thread.java:327)
      at com.leon.anrproject.ANRActivity.onclick(ANRActivity.java:87)
      at java.lang.reflect.Method.invoke(Native method)
      at android.view.View$DeclaredOnClickListener.onClick(View.java:5363)
      at android.view.View.performClick(View.java:6291)
      at android.view.View$PerformClick.run(View.java:24931)
      at android.os.Handler.handleCallback(Handler.java:808)
      at android.os.Handler.dispatchMessage(Handler.java:101)
      at android.os.Looper.loop(Looper.java:166)
      at android.app.ActivityThread.main(ActivityThread.java:7529)
      at java.lang.reflect.Method.invoke(Native method)
      at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

JDWP : 代表线程名;
daemon: 有该词,则代表守护线程
prio : 代表线程优先级;
tid: 线程内部ID;
WaitingInMainDebuggerLoop 代表线程状态

"JDWP" daemon   prio=5   tid=4    WaitingInMainDebuggerLoop

group : 代表线程所属的线程组;
sCount : 代表线程挂起次数;
dsCount: 代表用于调试的线程挂起次数;
obj: 代表当前线程关联的java线程对象;
self: 当前线程地址

  | group="system" sCount=1 dsCount=0 flags=1 obj=0x13740018 self=0x713eefc800

sysTid: 线程正在意义上的tid;
nice: 调度有优先级(此值越小优先级越高)
cgrp: 进程所属的进程调度组
sched: 调度策略
handle: 函数处理地址

  | sysTid=31273 nice=0 cgrp=default sched=0/0 handle=0x712d6b34f0

state : 线程状态
schedstat: CPU调度时间统计(依次:Running ,Runnable,Switch)
utm/stm: 用户态/内核态的CPU时间,单位是:jiffies
core: 该线程的最后运行所在核
HZ: 时钟频率

  | state=S schedstat=( 2509895 23958 6 ) utm=0 stm=0 core=6 HZ=100

stack:线程栈的地址区间; stackSize: 线程栈的大小

  | stack=0x712d5b9000-0x712d5bb000 stackSize=1005KB

mutex: 所持有mutex类型,有独占锁exclusive 和共享锁shared两类

  | held mutexes=

//内核栈

  kernel: __switch_to+0x98/0xc0
  kernel: poll_schedule_timeout+0x48/0x84
  kernel: do_select+0x544/0x5dc
  kernel: core_sys_select+0x1dc/0x394
  kernel: SyS_pselect6+0x238/0x260
  kernel: __sys_trace_return+0x0/0x4

//native栈

  native: #00 pc 00000000000698b4  /system/lib64/libc.so (__pselect6+8)
  native: #01 pc 00000000000278a4  /system/lib64/libc.so (select+148)
  native: #02 pc 0000000000516c38  /system/lib64/libart.so (_ZN3art4JDWP12JdwpAdbState15ProcessIncomingEv+332)
  native: #03 pc 00000000002e9b30  /system/lib64/libart.so (_ZN3art4JDWP9JdwpState3RunEv+444)
  native: #04 pc 00000000002e920c  /system/lib64/libart.so (_ZN3art4JDWPL15StartJdwpThreadEPv+40)
  native: #05 pc 0000000000067134  /system/lib64/libc.so (_ZL15__pthread_startPv+36)
  native: #06 pc 000000000001eee4  /system/lib64/libc.so (__start_thread+68)
  (no managed stack frames)

//日志的完成格式


"FinalizerDaemon" daemon prio=5 tid=5 Waiting
  | group="system" sCount=1 dsCount=0 flags=1 obj=0x12c49eb0 self=0x71494a6200
  | sysTid=31275 nice=4 cgrp=default sched=0/0 handle=0x712d4b14f0
  | state=S schedstat=( 308854 256773 14 ) utm=0 stm=0 core=6 HZ=100
  | stack=0x712d3af000-0x712d3b1000 stackSize=1037KB
  | held mutexes=
  at java.lang.Object.wait(Native method)
  - waiting on <0x0d421111> (a java.lang.Object)
  at java.lang.Object.wait(Object.java:422)
  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:188)
  - locked <0x0d421111> (a java.lang.Object)
  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:209)
  at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:235)
  at java.lang.Daemons$Daemon.run(Daemons.java:103)
  at java.lang.Thread.run(Thread.java:784)
......
----- end 31264 -----

以上对trace文件的内容作了详细的介绍,自己从中找到中文位置来对应下面的日志信息进行分析。

5.监测工具

介绍一些工具,各有优缺点,不过可以学习一下:

ANR-WatchDog , FileObserver,SafeLooper,BlockCanary

好吧,今天的ANR就聊到这里,如果有好的建议,请留言。

相关文章

网友评论

    本文标题:APP性能优化--ANR

    本文链接:https://www.haomeiwen.com/subject/bvwpvktx.html