APP性能优化--ANR
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就聊到这里,如果有好的建议,请留言。