本文档介绍了 Android 系统如何确定应用是否无响应,并展示了如何保持应用响应。
无论您的代码编写得多么好,您的应用都可能仍然感觉迟缓、挂起、长时间冻结或处理输入的时间过长。如果您的应用处于前台且无响应,用户会看到“应用无响应”(ANR) 对话框(如图 1 所示)。ANR 对话框允许用户强制退出应用。如果应用不在前台,则会将其静默停止。至关重要的是,在应用设计中融入响应能力,以最大限度地减少 ANR 对话框的出现。
ANR 触发器
通常,如果应用无法响应主线程(也称为 UI 线程)上的用户输入,从而阻止系统处理传入的用户输入事件,则系统会显示 ANR。
例如,如果应用在UI线程上执行阻塞式I/O操作(例如网络访问),则可能会发生ANR。另一个例子是应用在UI线程上花费太多时间构建复杂的内存结构或计算游戏中的下一步操作。
在Android中,应用响应能力由ActivityManager
和WindowManager
系统服务监控。当Android检测到以下任一情况时,会为应用显示ANR对话框:
- 在5秒内未响应输入事件——例如按键或屏幕点击事件。
- 对于前台意图,
BroadcastReceiver
在10到20秒内未完成执行。更多信息,请参见Broadcast receiver超时。
避免ANR
以下是避免ANR的一些通用技巧。有关诊断和调试不同类型ANR的更多详细信息,请参阅本节中的其他页面。
始终保持主线程不被阻塞,并策略性地使用线程。
不要在应用的主线程上执行阻塞或长时间运行的操作。相反,创建一个工作线程,并在那里执行大部分工作。
尽量减少主线程和其他线程之间的任何锁竞争。
最大限度地减少主线程上与UI无关的工作,例如处理广播或运行服务。在UI线程中运行的任何方法都应在此线程上尽可能少地执行工作。特别是,活动必须在关键生命周期方法(例如
onCreate()
和onResume()
)中尽可能少地进行设置。有关在后台线程上调度工作并与UI进行通信的可用解决方案的更多信息,请参阅后台工作概述。在组件之间共享线程池时要小心。不要对可能长时间阻塞的操作和时间敏感的任务(例如接收广播)使用相同的线程。
保持应用快速启动。最大限度地减少应用启动代码中的缓慢或阻塞操作,例如在dagger初始化期间运行的方法。
如果您使用
BroadcastReceiver
,请考虑使用Context.registerReceiver
在非主线程中运行广播接收器。更多信息,请参见BroadcastReceiver中的ANR。- 如果您使用
goAsync()
,请确保在ANR超时之前快速调用PendingResult.finish
。
- 如果您使用
BroadcastReceiver中的ANR
BroadcastReceiver
的执行时间受到限制,因为广播接收器旨在在后台执行少量离散的工作,例如保存设置或注册Notification
。因此,与在UI线程中调用的其他方法一样,应用必须避免在广播接收器中执行可能长时间运行的操作或计算。不要通过UI线程执行长时间运行的任务,而应在后台执行以供以后执行。有关可能解决方案的更多信息,请参阅后台工作概述。
另一个常见的BroadcastReceiver
对象问题是它们执行过于频繁。频繁的后台执行会减少其他应用可用的内存量。有关如何有效启用和禁用BroadcastReceiver
对象的更多信息,请参阅广播概述。
增强响应能力
通常,超过100到200毫秒的用户就会感觉到应用变慢。以下是一些使您的应用对用户看起来更具响应能力的其他技巧:
如果您的应用正在后台响应用户输入进行工作,请显示正在取得进展,例如在您的UI中使用
ProgressBar
。特别是对于游戏,请在工作线程中计算移动。
如果您的应用具有耗时的初始设置阶段,请考虑显示启动画面或尽快渲染主视图。指示加载正在进行中并异步填充信息。无论哪种情况,我们都建议以某种方式指示进度正在进行中,以便用户不会认为应用已冻结。
使用性能工具,例如Perfetto和CPU Profiler来确定应用响应能力中的瓶颈。