ndroid 中的 ANR 全称为 Application Not Responding,即应用程序无响应。在 Android 里,应用程序的响应性是由 Activity Manager 和 Window Manager 系统服务监视的。当出现以下情况时,Android 会针对特定的应用程序显示 ANR:在 5 秒内没有响应输入的事件(如按键按下、屏幕触摸);BroadcastReceiver 在 10 秒内没有执行完毕;Service 的各个生命周期函数在特定时间(20 秒)内无法完成处理。
为避免 ANR,可以采取以下措施:
- 运行在主线程里的任何方法都应尽可能少做事情。Activity 应在关键生命周期方法(如 onCreate () 和 onResume ())里尽可能少地做创建操作。
- 潜在的耗时操作,如网络或数据库操作、高耗时的计算如改变位图尺寸,应在子线程里(或通过异步请求的方式)来完成。主线程应为子线程提供一个 Handler,以便完成时能够提交给主线程。
- IntentReceiver 应在后台做小的、琐碎的工作如保存设定或者注册一个 Notification,避免在 BroadcastReceiver 里做耗时的操作或计算。如果响应 Intent 广播需要执行一个耗时的动作,应启动一个 Service。
- 单独开工作者线程,通过独立的 Thread 或使用类似 AsyncTask 的方式来处理耗时的内容。
- 耗时的操作尽量分段处理,使用类似状态机的方法。
- UI 线程中不要处理过多的内容,遇到复杂的 UI 操作可参考分段处理方式。
- 使用异步任务,将耗时操作转移到后台线程(如使用 AsyncTask、HandlerThread、ExecutorService 等)、异步任务、后台服务,确保主线程专注于 UI 更新和事件处理。
- 避免死锁和过度同步,在多线程编程中,合理使用锁机制和同步策略,确保线程安全地访问共享资源。
- 资源竞争管理,避免多个线程同时访问共享资源导致的资源竞争问题。
解决 ANR 的方法有:
- 异步化处理,使用异步任务将耗时操作转移到后台线程,避免主线程阻塞。
- 分析 traces.txt 文件,ANR 发生时,系统会在设备上生成 traces.txt 文件,它记录了所有线程的状态,通过 ADB 工具将其导出分析,可以定位到具体哪个线程可能引起阻塞。
- 使用性能分析工具,如 Android Profiler 实时监控 CPU、内存、网络、磁盘 I/O 等资源使用情况,寻找可能导致 ANR 的性能瓶颈;Systrace 系统层级的跟踪工具,能够追踪系统各组件间的交互和调度,帮助找出主线程阻塞的源头;使用 Android 提供的 ANR 检测工具,如 Traceview,可以获取应用程序的执行堆栈信息,通过分析堆栈信息,可以准确找到导致 ANR 的代码位置。
- 通过在开发工具中进行调试和单步执行,可以逐步跟踪代码的执行过程,找到导致 ANR 的具体位置和原因。
Android ANR 的定义和触发条件
ANR,即 Application Not Responding,应用程序无响应。在 Android 中,应用程序的响应性是由 Activity Manager 和 Window Manager 系统服务监视的。当出现以下情况时,Android 就会针对特定的应用程序显示 ANR:
- 在 5 秒内没有响应输入的事件(例如,按键按下,屏幕触摸)。这种情况通常发生在主线程被长时间占用,无法及时处理用户的输入事件。比如,当用户点击屏幕上的按钮时,主线程应该尽快响应这个事件。如果主线程正在执行一个耗时的操作,如进行大量的计算或者进行网络请求,就可能无法在规定的时间内响应这个点击事件,从而触发 ANR。
- BroadcastReceiver 在 10 秒内没有执行完毕。当接收到广播时,BroadcastReceiver 的 onReceive 方法会在主线程中执行。如果这个方法执行时间过长,就会触发 ANR。例如,在 onReceive 方法中进行复杂的数据库操作或者网络请求,都可能导致 ANR 的发生。
- Service 前台 20 秒后台 200 秒未完成启动。当启动一个服务时,如果服务的各个生命周期函数在规定的时间内没有完成执行,也会触发 ANR。比如,在服务的 onCreate 方法中进行耗时的初始化操作,可能会导致服务无法在规定时间内启动,从而触发 ANR。
- ContentProvider 的 publish 在 10s 内没进行完。ContentProvider 在发布数据时,如果在规定时间内没有完成操作,也会触发 ANR。
如何在主线程减少操作避免 ANR
运行在主线程里的任何方法都尽可能少做事情。特别是,Activity 应该在它的关键生命周期方法(如 onCreate () 和 onResume ())里尽可能少的去做创建操作。
在 Activity 的 onCreate () 和 onResume () 方法中,应该避免进行耗时的操作。例如,不要在这些方法中进行网络请求、数据库操作或者大量的计算。这些操作应该放在子线程中执行,以避免阻塞主线程。
例如,可以在 onCreate () 方法中只进行一些必要的初始化工作,如设置布局、初始化一些基本的变量等。如果需要从网络获取数据来填充界面,可以在 onCreate () 方法中启动一个子线程,在子线程中进行网络请求,当数据获取完成后,再通过 Handler 或者其他方式将数据传递给主线程,更新 UI。
此外,在主线程中也应该避免大量创建新对象。大量创建新对象会消耗大量的内存和时间,可能导致主线程卡顿,从而触发 ANR。可以考虑使用对象池等技术来减少对象的创建和销毁次数。
利用子线程避免 ANR 的方法
为了避免 ANR,应该把耗时任务放在后台线程执行。在 Android 中,可以使用 AsyncTask、HandlerThread、或 Thread 来执行异步操作。
- 使用 AsyncTask:AsyncTask 是一个异步任务类,比 Handler 更轻量,更适合简单的异步操作。内部实现了对 Thread 和 Handler 的封装,方便后台线程操作后 UI 的更新。在用 AsyncTask 进行 UI 更新时,不用额外创建 Handler,直接用 AsyncTask 内部封装好的几个方法。例如,可以在 doInBackground 方法中执行耗时的操作,如网络请求或数据库操作,在 onPostExecute 方法中更新 UI。
- 使用 HandlerThread:HandlerThread 是一个带有消息循环的线程。可以通过创建 HandlerThread,然后获取其 Looper,创建一个 Handler,在 Handler 中处理耗时任务。例如,可以在 Handler 的 handleMessage 方法中执行耗时的操作,当操作完成后,通过发送消息给主线程的 Handler,更新 UI。
- 使用 Thread:可以直接创建一个新的 Thread,在 Thread 的 run 方法中执行耗时任务。当任务完成后,需要在主线程中更新 UI 时,可以通过 Handler 或者 runOnUiThread 方法来实现。例如,可以在 Thread 的 run 方法中进行网络请求,当请求完成后,通过 runOnUiThread 方法在主线程中更新 UI。
IntentReceiver 避免 ANR 的方式
IntentReceiver 执行时间的特殊限制意味着它应该做小的、琐碎的工作如保存设定或者注册一个 Notification。应用程序应该避免在 BroadcastReceiver 里做耗时的动作。
如果响应 Intent 广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。例如,当接收到一个广播时,如果需要进行网络请求或者数据库操作等耗时操作,不应该在 BroadcastReceiver 的 onReceive 方法中直接执行这些操作,而是应该启动一个 Service,在 Service 中执行这些耗时操作。
避免在 Intent Receiver 里启动一个 Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应 Intent 广播时需要向用户展示什么,你应该使用 Notification Manager 来实现。
使用异步任务避免 ANR
使用异步任务可以避免 ANR 问题。异步任务是一种在后台线程执行耗时操作的机制,可以避免在主线程中执行耗时操作导致界面卡顿的问题。
在 Android 中,媒体播放通常涉及到加载媒体文件、解码、缓冲、播放等多个步骤,这些操作都是耗时的。如果在主线程中执行这些操作,会导致界面无响应,用户体验差。因此,使用异步任务来处理媒体播放可以保证界面的流畅性。
异步任务播放媒体的步骤如下:创建一个继承自 AsyncTask 的子类,用于执行媒体播放操作。在 AsyncTask 子类中,重写 doInBackground () 方法,在该方法中执行耗时的媒体播放操作,如加载媒体文件、解码等。在 doInBackground () 方法中,可以使用 MediaPlayer 类或其他媒体播放库来实现具体的媒体播放功能。在 AsyncTask 子类中,可以重写其他方法,如 onPreExecute ()、onPostExecute () 等,用于在任务执行前后进行一些准备工作和处理结果。在主线程中,通过创建 AsyncTask 子类的实例,并调用 execute () 方法来启动异步任务。
UI 线程处理过多内容如何避免 ANR
UI 线程尽量只做 UI 相关的操作,避免耗时操作,比如过度复杂的 UI 绘制、网络操作、文件 IO 操作等。
如果 UI 线程处理过多内容,可能会导致主线程卡顿,从而触发 ANR。为了避免这种情况,可以采取以下措施:
- 优化 UI 布局:避免布局层级过深,使用 ConstraintLayout 等高效的布局方式,减少布局的复杂性。
- 延迟加载布局:使用 ViewStub 等技术,延迟加载布局,避免在初始化时加载过多的布局,从而减少 UI 线程的负担。
- 异步加载数据:对于需要从网络或数据库获取的数据,可以在子线程中进行加载,加载完成后再更新 UI。
- 使用高效的视图组件:如 RecyclerView 等,提高数据的展示效率,减少 UI 线程的负担。
避免死锁和过度同步防止 ANR
在多线程编程中,合理使用锁机制和同步策略,避免死锁和过度同步的情况发生。确保线程安全地访问共享资源。
例如,可以使用 ReentrantLock 等锁机制,避免多个线程同时访问共享资源导致的死锁问题。在使用锁时,要注意锁的范围,避免长时间持有锁,导致其他线程无法访问共享资源。
同时,要避免过度同步,即不要在不必要的地方使用同步机制,导致程序性能下降。可以通过分析程序的逻辑,确定哪些地方需要使用同步机制,哪些地方可以不用。
资源竞争管理避免 ANR
避免多个线程同时访问共享资源导致的资源竞争问题。
可以通过以下方式来管理资源竞争:
- 使用锁机制:如 ReentrantLock、synchronized 等,确保同一时间只有一个线程访问共享资源。
- 使用信号量:可以限制同时访问共享资源的线程数量。
- 使用线程安全的集合类:如 ConcurrentHashMap 等,避免在多线程环境下出现数据不一致的问题。
异步化处理解决 ANR
- 使用异步任务:将耗时操作转移到后台线程(如使用 AsyncTask、HandlerThread、ExecutorService 等)、异步任务、后台服务。确保主线程专注于 UI 更新和事件处理。
- 创建一个固定大小的线程池,大小为 4。例如,可以使用 Executors.newFixedThreadPool (4) 创建一个固定大小为 4 的线程池。
- 假设有一个耗时任务,可以创建一个 Runnable 对象,在 run 方法中实现耗时操作,如 doSomeHeavyWork ()。
- 将耗时任务提交给线程池执行,可以使用 executorService.execute (longRunningTask) 将耗时任务提交给线程池执行。
- 避免死锁和过度同步:在多线程编程中,合理使用锁机制和同步策略,避免死锁和过度同步的情况发生。确保线程安全地访问共享资源。
- 例如,可以使用 ReentrantLock 等锁机制,避免多个线程同时访问共享资源导致的死锁问题。在使用锁时,要注意锁的范围,避免长时间持有锁,导致其他线程无法访问共享资源。
- 同时,要避免过度同步,即不要在不必要的地方使用同步机制,导致程序性能下降。可以通过分析程序的逻辑,确定哪些地方需要使用同步机制,哪些地方可以不用。
- 资源竞争管理:避免多个线程同时访问共享资源导致的资源竞争问题。
- 可以通过使用锁机制、信号量、线程安全的集合类等方式来管理资源竞争。
分析 traces.txt 文件解决 ANR
当 ANR 发生时,系统会将 ANR 信息输出到 traces.txt 文件中。这个文件记录了在发生 ANR 时刻系统各个线程的执行状态。
可以通过以下步骤分析 traces.txt 文件:
- 获取 traces.txt 文件:可以使用 adb 命令将 traces.txt 文件从设备中导出到本地。例如,可以在命令行中进入 Android SDK 的 platform-tools 目录,然后执行 adb pull /data/anr/traces.txt 命令,将 traces.txt 文件导出到当前目录。
- 分析文件内容:打开 traces.txt 文件,可以看到文件中记录了发生 ANR 的时间、进程 id、进程名称等信息。后面还记录了各个线程的基本信息,如线程名称、线程的优先级、线程锁 id 和线程状态等。
- 通过分析这些信息,可以确定导致 ANR 的原因。例如,如果发现某个线程长时间处于阻塞状态,可能是因为这个线程在等待某个资源,或者在执行一个耗时的操作。
- 可以根据文件中的调用栈信息,确定导致 ANR 的具体代码位置。然后,可以针对这个问题进行修复。
使用性能分析工具解决 ANR
可以使用一些性能分析工具来帮助定位和解决 ANR 问题。
- Traceview:系统性能分析工具,用于定位应用代码中的耗时操作。可以通过在 Android Studio 中运行应用,然后在工具栏中选择 “Profile” 按钮,启动 Traceview 工具。Traceview 可以显示应用程序中各个方法的执行时间,帮助开发者找出耗时的操作,从而进行优化。
- Systrace:Android 4.1 新增的应用性能数据采样和分析工具。可以通过在命令行中执行 systrace.py 脚本,启动 Systrace 工具。Systrace 可以收集系统级别的性能数据,包括 CPU 使用情况、线程状态、图形渲染等信息,帮助开发者分析应用程序的性能问题。
通过调试单步执行解决 ANR
在调试过程中,如果遇到 ANR 问题,可以使用调试工具进行单步跟踪,分析问题所在。
例如,可以在 Android Studio 中设置断点,然后使用调试工具进行单步跟踪。通过观察程序的执行流程,可以确定导致 ANR 的原因。
如果在断点或者单步跟踪时 APP 退出,可能是因为出现了 ANR。可以通过打开手机开发者模式,在 “开发人员选项” 中,向下滚动到 “调试” 部分,然后在 “选择调试应用” 中选择你的应用。这样,当在断点中暂停时,它不会触发任何 ANR。
结论:ANR 是 Android 中应用程序无响应的情况,会严重影响用户体验。为了避免和解决 ANR 问题,可以从多个方面入手。在主线程中减少操作,避免进行耗时的操作,如网络请求、数据库操作和大量计算等。利用子线程执行耗时任务,如使用 AsyncTask、HandlerThread 或 Thread 等方式。在 IntentReceiver 中避免做耗时操作,如有需要可以启动 Service 来处理。使用异步任务可以有效地避免 ANR 问题,如异步任务播放媒体等。同时,要注意 UI 线程的负担,避免处理过多内容导致卡顿。避免死锁和过度同步,合理管理资源竞争,通过异步化处理、分析 traces.txt 文件和使用性能分析工具等方式,可以帮助定位和解决 ANR 问题。在调试过程中,要注意避免因调试导致的 ANR 问题。通过以上措施,可以有效地提高 Android 应用程序的性能和稳定性,避免 ANR 的发生。