一、ANR 的触发
ANR(Application Not Responding,应用无响应)的触发存在两种情况,一种恰似埋地雷,只有在特定条件下才会触发;另一种仿若安装定时炸弹,时间一到便会引爆。
-
Input 事件触发的 ANR:
- Input ANR 属于“地雷”类型。在 Android 系统中,处理输入事件的类是
InputDispatcher
。当用户进行交互操作时,会执行或被唤醒执行dispatchOnce()
方法,该方法先后会调用dispatchOnceInnerLocked()
和processAnrsLocked()
。而dispatchOnceInnerLocked()
会执行到findFocusedWindowTargetLocked()
,如果此方法找不到能够处理点击事件的窗口,就会给mNoFocusedWindowTimeoutTime
赋值,这个值就如同埋下的“地雷”。在processAnrsLocked()
中,会判断这个“地雷”是否到期,若到期则会触发 ANR。
- Input ANR 属于“地雷”类型。在 Android 系统中,处理输入事件的类是
-
四大组件生命周期引起的 ANR:
- 四大组件引起的 ANR 属于“定时炸弹”类型。在 AMS(Activity Manager Service)执行四大组件之前,会发送一条可能触发 ANR 的消息。当生命周期方法执行完毕后,会移除这条消息。如果这条消息没有被及时移除,就如同“定时炸弹”爆炸,ANR 被触发。
二、ANR 发生的几种情况
-
主线程有耗时操作:
-
CPU 密集型操作:
- 诸如长列表遍历等操作、递归层数过深、大量动画以及复杂的
Canvas.drawPath()
等,会大量占用 CPU 资源。
- 诸如长列表遍历等操作、递归层数过深、大量动画以及复杂的
-
IO 密集型操作:
- 数据库操作、文件读取等也会使主线程陷入长时间等待。
- 死循环:若主线程陷入死循环,自然无法响应其他任务。
- 调用 binder 进行跨进程通信:这种操作可能会耗费较长时间,导致主线程卡顿。
-
CPU 密集型操作:
-
内存引起的问题:
- Android 的内存回收会暂停所有线程,直到内存释放后线程才能继续执行。如果应用占用内存过大,可能会引起频繁的内存回收,从而导致主线程无法及时执行完所需任务。
- 内存泄漏:会导致内存不断被占用,无法释放。
- 大内存对象(Bitmap):如果不妥善处理,会消耗大量内存。
- 频繁的 new 对象:会增加内存分配和垃圾回收的压力。
- Android 的内存回收会暂停所有线程,直到内存释放后线程才能继续执行。如果应用占用内存过大,可能会引起频繁的内存回收,从而导致主线程无法及时执行完所需任务。
-
主线程等待锁释放:
- 当主线程与子线程竞争同一锁对象,而子线程获得锁后迟迟不释放,会使得主线程无法执行完从而导致 ANR。
-
主线程迟迟无法获得 CPU 时间:
- 其他进程占用太多 CPU 资源,或者当前进程开启了太多线程,都可能导致主线程无法及时获得足够的 CPU 时间来执行任务。
三、ANR 监控手段
ANR 的监控手段主要有以下几种:
- track 文件:可以通过分析 track 文件来了解 ANR 发生的情况。
- Android profiler:提供了丰富的性能分析工具,包括对 ANR 的监测。
- 实时监控代码:
- Handler printer 替换:通过替换 Handler 的打印机制,实现对主线程消息处理的监控。
- 定时往主线程中发送消息并检查上一个消息有没有被消费:以此来判断主线程是否出现卡顿。
四、ANR 解决手段
-
针对主线程有耗时操作:
- 将耗时操作改为异步操作,避免阻塞主线程。
- 减少不必要的操作,例如对长列表先进行过滤,只处理需要的元素。
- 优化算法,比如将除法改为乘法,优先使用位运算以提高效率。
- 缓存计算结果,避免重复计算和读取。
- 合并动画,及时停止后台动画以减少资源占用。
- 使用懒操作(Lazy)避免计算集中在某一时刻。
- 非必要情况下,确保递归层数有限,循环一定有出口,防止死循环。
-
针对内存引起的问题:
- 使用 LeakCanary 等工具,及时检测和避免内存泄漏。
- 使用 Glide 等工具管理 Bitmap,在无法使用这些工具时,要确保:
- 图片在不需要时被及时回收。
- 图片以最低要求加载(比如大小不要超过屏幕大小)。
- 避免频繁地创建新对象,尽量使用缓存的对象(在
onDraw
中同理)。
-
针对主线程等待释放锁:
- 优先使用 Volite 代替锁,减少锁的开销。
- 考虑使用 CAS(Compare and Swap)和 CopyOnWrite 的技术代替锁,提高并发性能。
- 优先使用读写锁等轻量级的锁。
- 使用 SurfaceView 等支持异步刷新的 View,避免主线程阻塞。
- 至少确保不会死锁,避免陷入无法解决的等待状态。
-
针对主线程无法获得 CPU 时间:
- 使用线程池管理所有线程,合理分配资源,避免线程过多导致竞争。
- 优化算法,降低每个线程的压力,提高整体性能。
网友评论