有可能写出的代码赢得了每一次性能测试,但是却仍然使得当用户尝试使用应用时,使他们处于怒火之中。这是因为应用的响应不够迅速 - 用户可能会觉的应用程序运行缓慢,应用程序被挂起,或因冻结很长时间而无反应,或者处理输入时间过长。
在Android中,系统会监视那些反应不够迅速的应用。如果系统发现某个应用程序反应太慢,它会显示一个叫做Application Not Responding (ANR)的对话框给用户,如Figure 1所示。用户可以选择等待该应用程序让它继续,也可以选择强行结束该应用程序。虽然用户可以选择等待该应用程序让它继续,但是用户其实是非常不喜欢出现该对话框(ANR提示框)
因此设计具有较好响应性的应用程序是非常重要的,这样系统才不会给用户显示一个ANR对话框。
一般来说,如果一个应用不能快速响应用户输入,则系统显示一个ANR。例如:如果一个应用程序的主线程因为在进行一些I/O操作而被阻塞了(经常是一个网络访问),那么该应用程序的主线程此时就没有能力处理用户的输入事件。一段时间后,系统推断出这个应用冻结了,然后显示一个ANR给用户一个选择杀死此应用或等待该应用程序操作完成后继续。
相似的,如果你的应用程序的主线程消耗太多时间来建立一个复杂的内存中的结构,或者计算游戏中的下一步,系统会断定你的应用已经挂起。总是保证使用上面的技巧这些计算是有效率的是很重要的,但是即使是最有效的代码也需要时间来运行。
这两种情况中,推荐的方法是建立一个子线程来做你在那里的大部分工作。这样保持主线程运行并且阻止系统认定你的代码已经冻结。因为这样的线程经常在类级别中完成,你可以把响应性作为一个类的问题考虑。
What Triggers ANR?
在Android中,应用响应度是由Activity Manager和Window Manager系统服务监视的。Android当发现到下面其中一个条件发生的时候,会特定的应用显示ANR对话框。
- No response to an input event (e.g. key press, screen touch) within 5 seconds.一个输入事件5秒内没有反馈。 但是并不意味着花4秒时间来处理输入事件是安全的。相反对于这种主线程的回调函数,应该让其执行时间尽量的短,以避免主线程,以便主线程能处理一些更重要更紧急的事务。
- A
BroadcastReceiver
hasn't finished executing within 10 seconds In Android, application responsiveness is monitored by the Activity .一个BroadcastReceiver在10秒内没有执行完毕。但是并不意味着在BroadcastReceiver的OnReceiver中花9秒时间来处理事务是安全的。相反对于这种主线程的回调函数,应该让其执行时间尽量的短,以避免主线程,以便主线程能处理一些更重要更紧急的事务。
How to Avoid ANR
通过上面的叙述,我们已经了解了什么是ANR的定义,现在让我们再次认识为什么Android应用中会ANR中发生,并且如何才能设计更好的架构来避免ANR的发生。
Android应用通常整个都运行在一个线程中(主线程)。这意味着,你应用中做的任何事情都在主线程,如果耗时太多就可能触发ANR对话框,因为主线程来不及处理用户输出或者Intent广播及其他回调函数。
因此任何在主线程中调用的函数都应该做尽量少的工作。尤其是四大组件的生命周期函数,比如Activity的生命周期函数onCreate() 和onResume().对于需要长时间运行的操作,如网络或者数据库操作,或者高代价的计算,或者调整bitmap大小,应该放在一个子线程中来执行。然而,这不意味着你的主线程应该在等待子线程完成时而阻塞,你的主线程应该为子线程提供一个 Handler ,当完成时把结果返回给主线程 - 而不该调用 Thread.wait()
或 hread.sleep()。按照这种思路来设计你的Android应用程序,你的应用程序就能对输入事件进行很好的反应,以避免因为无法在5秒内对输入事件进行响应而产生的ANR对话框。其实对于任何用于显示UI的线程都应该遵循以上原则,因为它们都面临着对输入事件不能及时响应的问题。关于Handler的更多内容请阅读《Android线程模型》和《关于Handler技术》和《Looper和Handler》
You can use StrictMode
to help find potentially long running operations such as network or database operations that you might accidentally be doing your main thread.
你可以使用 StrictMode 来帮助你在主线程中查找潜在的耗时操作,比如对网络或数据库操作。
线程对IntentReceiver执行时间上的明确的约束,主要是为了强调它是用于执行非常少量的工作 - 只是用于分离一些在后台工作,比如保存一个设置或者注册一个Notification。因此和其他在主线程中调用的其他方法一样,在BroadcastReceivers内应该避免的长时间操作或计算。对于耗时的工作,因为BroadcastReceivers的生命周期是非常短暂的,因此你也不应该在其中开启一个子线程来做该工作,而是应该启动一个 Service
,再在这个Service中创建一个子线程来做该工作。另外,你也应该避免从一个Intent过滤器启动一个activity,因为它将产生一个新屏幕,从用户当前运行的应用中偷走焦点。如果你的应用中有一些内容作为Intent广播的反馈需要显示给用户,它应该使用Notification Manager来完成。
Reinforcing Responsiveness
一般来说,在应用中,100到200毫秒是用户能不能察觉延迟的临界值。(每个人反应速度不一样,所以会有100ms-200ms这个范围)这有一些额外的窍门是你为了避免ANR而应该做的,会帮助你的应用对用户来说看起来是很灵敏的。
- If your application is doing work in the background in response to user input, show that progress is being made (
ProgressBar
andProgressDialog
are useful for this).如果你的应用正在后台工作来响应用户输入,那么给用户显示你的执行进度是个不错的选择(ProgressBar和ProgressDialog在这点上很有用)。 - .对于游戏来说, 移动的运算应该在子线程中来进行。
- .如果你的应用在初始化阶段较耗时,考虑显示一个splash屏或者尽快让主视图快速显示处理,然后才显示其他的视图。不管是哪一种情况,你应该设法表明程序正在往前执行,以免用户觉得应用冻结了。