在世界上, 编写可以赢得每一个性能测试的代码是有可能的, 但是仍然感觉在某些显著的时间段内缓慢, 挂起或者冻住, 或者花费太长的时间来处理输入事件. 可能发生的应用响应上最糟糕的事情是 "应用未响应"(ANR)对话框.
在 Android 中, 系统通过显示一个说明您的应用已经停止响应的对话框来防止应用在一段时间内响应不足, 就像图 1 中的对话框一样. 在这个点上, 您的应用已经相当一段时间没有响应了, 所以系统给用户提供了一个选项来终止该应用. 将响应设计到您的应用, 来让系统从来不给用户显示 ANR 对话框是极其重要的.
图 1. 展示给用户的 ANR 对话框
本文描述了 Android 系统如何决定是否应用是不响应的并且提供指导来确保您的应用保持响应.
是什么触发了 ANR?
一般来说, 如果应用不能响应用户输入事件, 系统会显示 ANR. 例如, 如果应用在 UI 线程上的某些 I/O 操作 (频繁地访问网络) 上阻塞了, 以至于系统无法处理进来的用户输入事件. 或者可能应用在 UI 线程上花费了太多时间来构建一个复杂的内存结构或者在游戏中计算下一个移动. 确保这些计算高效总是很重要的, 但是即使是最高效的代码仍然会花费时间来运行.
在任何您的应用可能执行长时间操作的场景下, 您都不应该在 UI 线程中执行这项工作, 而应该创建一个工作线程来处理大部分的工作. 这让 UI 线程 (它驱动用户接口时间循环) 保持运行并且防止系统断定您的代码已经冻住了. 因为这样的线程通常是在 class 级别上完成的, 所以您可以把响应看成是一个 class 问题.(将这和基本的代码性能进行比较, 代码性能问题是一个方法级别的概念.)
在 Android 中, 应用响应被 Activity Manager 和 Windows Manager 系统服务监视, 当检测到有以下条件之一时, Android 将会为特定的应用显示 ANR 对话框:
5 秒内对输入事件 (比如键被按下或者屏幕触摸事件) 没有响应.
BroadcastReceiver 在 10 秒内没有完成执行.
如何避免 ANR
Android 应用通常全部在一个单一的线程中运行(默认为 "UI 线程" 或者 "主线程"). 这意味着您的应用在 UI 线程中正在执行的需要花费很长时间来完成的任何任务, 都可能触发 ANR 对话框, 因为您的应用没有给自己机会来处理输入事件或者意图广播.
因此, 任何在 UI 线程中运行的方法应该尽可能做少量的工作. 尤其是, Activity 应该尽可能少地在如 onCreate()和 onResume()这样的关键生命周期方法中设置. 潜在的诸如网络操作或者数据库操作这样的长时间运行的操作, 或者诸如重新设置 bitmap 大小等这样昂贵的计算, 应该在工作线程中来执行(或者在数据操作的情况下, 通过异步请求).
为更长时间的操作创建工作线程最有效的方式是使用 AsyncTask 类. 简单地继承 AsyncTask 并且实现 doInBackground()方法来执行工作. 为了将进度改变发送给用户, 您可以调用 publishProgress(), 它调用了 onProgressUpdate()回调方法. 从 onProgressUpdate()方法 (它在 UI 线程中运行) 的实现, 您可以通知用户. 例如:
- private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
- // Do the long-running work in here
- protected Long doInBackground(URL... urls) {
- int count = urls.length;
- long totalSize = 0;
- for (int i = 0; i < count; i++) {
- totalSize += Downloader.downloadFile(urls[i]);
- publishProgress((int) ((i / (float) count) * 100));
- // Escape early if cancel() is called
- if (isCancelled()) break;
- }
- return totalSize;
- }
- // This is called each time you call publishProgress()
- protected void onProgressUpdate(Integer... progress) {
- setProgressPercent(progress[0]);
- }
- // This is called when doInBackground() is finished
- protected void onPostExecute(Long result) {
- showNotification("Downloaded" + result + "bytes");
- }
- }
要执行该工作线程, 简单地创建一个实例并且调用 excute()方法:
1 new DownloadFilesTask().execute(url1, url2, url3);
您可能希望创建您自己的 Thread 或者 HandlerThread 类, 虽然这比 AsyncTask 更加复杂. 如果您这样做, 您应该通过调用 Process.setThreadPriority()方法并且传入 THREAD_PRIORITY_BACKGROUND 值来给 "后台" 优先级设置线程优先级. 如果您没有通过这个方法来将该线程设置为更低的优先级, 那么该线程可能仍然会拉低您应用的速度, 因为它默认情况下会以和 UI 线程相同的优先级来运行.
如果您实现 Thread 或者 HandlerThread, 请确保当正在等待工作线程完成时, UI 线程不会阻塞 -- 不要调用 Thread.wait()或者 Thread.sleep(). 当等待工作线程完成时, 主线程不应该阻塞, 而应该为其它线程提供一个 Handler, 当工作线程完成时将其传回主线程. 通过这种方式设计应用将允许应用的 UI 线程保持对输入事件的响应, 并且这样避免了 5 秒的输入事件超时所引起的 ANR 对话框.
BroadcastReceiver 执行时间的特别限制强调了广播接收器应该做什么: 小的, 离散的后台工作量, 比如保存设置或者注册通知. 所以, 当其它方法在 UI 线程中被调用时, 在广播接收器中应用应该避免潜在的长时间运行操作或者计算. 但是, 如果潜在的长时间运行的 action 需要处理来响应 intent 广播, 您的应用不应该通过工作线程处理密集的任务, 而应该通过启动 IntentService.
当 BroadcastReceiver 对象执行太频繁时, 另外一个常见的 BroadcastReceiver 对象问题会发生. 频繁的后台执行会降低其它应用可用内存的数量. 更多关于如何有效地让 BroadcastReceiver 对象有效 / 失效, 请查阅[按要求操作广播接收器]
★ 提示: 您可以使用 StrictMode 来协助找到潜在的长时间运行操作, 比如您可能无意间在主线程中执行的网络或者数据库操作.
加强响应
一般来说, 100 到 200 毫秒时阈值, 超过这个阈值用户将察觉到应用缓慢. 所以, 在为了避免 ANR 你应该做的之外, 这里有一些附加的提示, 让您的应用看起来对用户是响应的:
如果您的应用正在后台处理工作来响应用户输入, 显示它的进度(比如在 UI 中使用 ProgressBar).
特别是对于游戏, 在工作线程中计算移动.
如果您的应用有一个耗时的初始化设置阶段, 考虑尽快显示一个初始化屏幕或者展示主视图, 表明正在加载并且异步地填充信息. 在其中任意一种情况, 您应该指明取得的进展, 以免用户以为应用冻住了.
使用性能功能如[Systrace] 和[TraceView] 来确定应用响应的瓶颈.
来源: https://www.cnblogs.com/andy-songwei/p/10799715.html