您的位置:首页 > 移动开发 > Android开发

保持你的应用可响应

2014-04-23 10:44 375 查看
原文:http://developer.android.com/training/articles/perf-anr.html

写出能在世界上所有性能测试中胜出的代码是可能的,但是这样的代码也可能在执行繁重任务阶段让人感觉缓慢、停滞或冻住,要么就是时间过长而无法响应输入。最糟糕的是你的应用会弹出“应用程序无响应”(ANR)对话框。

在Android系统中,系统通过显示一个“你的应用已经停止响应了”的对话框来防范那些一段时间不能充分响应的应用,该对话框如图一所示。



此时,说明你的应用已经相当长时间无法响应了,所以系统提供给用户选择去退出当前应用。在你的应用中设计可响应性是至关重要的,如此,系统就永远不会显示ANR的对话框给用户了。

本篇文档描述了Android系统是如何确定是否某个应用是无法响应的并且提供了确保你的应用保持有响应的指导方针。

什么会触发ANR?

一般情况下,当某个应用无法响应用户输入的时候就会显示一个ANR的对话框。比如说,如果某个应用在某些I\O操作(频繁的网络访问)上阻塞住了,那么,系统就无法处理到来的用户输入事件。或者,也许该应用在UI线程中花费了太多时间去组建一个复杂的内存中的结构或计算游戏中下一步移动。确保以上那些运算是高效的总是很重要的,但是即使是最高效的代码仍然是需要时间去运行。

在任何情况下,当你的应用要执行潜在的长时操作时,你都不应该在UI线程中执行这段工作。取而代之的,你应该创建一个工作线程并在其中完成绝大部分工作。这样保持了UI线程(它驱动了用户接口事件循环)的持续运行并且阻止了系统去断定你的代码已经冻住。因为这类线程通常都是在“类”这一层被完成,所以你可以将响应作为一个类的问题来考虑。(和基础的代码性能相比,那是方法层次的问题)

在Android中,应用程序的响应被Activity Manager和Window Manager系统服务所监 控着。当Android系统侦 测到如下两个条件之一时,它就会为某个特别的应用显示ANR对话框:

5秒内对输入事件(比如按键或触屏)未响应
10秒内广播接收器BroadcastReceiver未曾执行结束

如何避免ANRs?

Android应用一般默认是完全运行在一个单独的UI线程或者叫主线程中。这就意味着你的应用正在UI线程中做的任何花费很长时间的操作都会引起ANR对话框。原因是你的应用并没有给自己机会去处理输入事件或者分发广播。

因而,UI线程中的任何方法都应该做尽可能少的工作。特别是,activities就应该在它的关键生命周期方法(比如onCreate和onResume)中做尽可能少的工作。网络相关操作、数据库操作或者像调整位图这种大量计算等这些潜在的需要花费很长时间的操作都应该放到工作线程中去完成(要么像数据库操作的话,通过异步请求来完成)。

使用AsyncTask类是为长时操作创建工作线程最有效的方式。只需要简单的继承AsyncTask这个类并且实现doInBackground()方法来执行我们的工作。你可以通过调用publishProgress()方法来告知用户进度更新信息,这个方法会间接地调用onProgressUpdate()回调方法。这样你可以在你自己的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");
}
}
创建一个实例并且调用execute()方法就可以简单的执行我们的工作线程。

new DownloadFilesTask().execute(url1, url2, url3);

尽管和AsyncTask相比稍显复杂,也许你仍然想创建自己的Thread或者HandlerThread类。如果你真是这么想的话,你就应该通过Process.setThreadPriority()方法和传递THREAD_PRIORITY_BACKGROUND标记来设置你的新建线程的优先级为“后台”。如果你不通过以上方式来设置线程为较低优先级的话,那么整个线程将仍然会降低你应用的速度,因为该线程默认是和UI线程拥有相同的优先级。

如果你是实现的
Thread
或者
HandlerThread
,当等待工作线程完成的时候请确保你的UI线程不会被阻塞,请不要调用
Thread.wait()
或者
Thread.sleep()
。你的主线程应当为其他线程提供一个
Handler
当完成时去提交结果,而不是当等待工作线程完成时阻塞住。以这种方式设计你的程序的话将使得你应用的UI线程对输入保持有响应,从而避免了由于5秒钟输入事件超时所引起的ANR对话框。

BroadcastReceiver执行时间上的特定约束强调的则是广播接收者只在后台做小而离散的工作,比如保存一个设置项或者注册一个Notification。所以和在UI线程中被调用的其他方法一样,应用程序应当避免在广播接收器中长时的操作或计算。但是,如果有来自意图广播的潜在长时操作需要处理的话,你的应用程序应该启动一个IntentService,而不是通过工作线程来完成大量任务。

提示:你可以使用StrictMode来帮助发现潜在的长时操作,比如网络操作或者数据库相关操作,而这些操作有可能偶尔在你的主线程中涉及到。

增强响应性

一般情况下,100至200毫秒是用户所能感知到一个应用缓慢的阀值。因此,以下是一些额外的提示,这些提示超越了你应该做些什么去避免ANR并且让你的应用对用户看起来是可响应的。

如果你的应用正在后台工作,请显示当前进度(比如在你的UI中使用ProgressBar
特别的在游戏中,我们需要在工作线程中执行我们的移动相关计算工作
如果你的应用中有一段耗时的初始化设置过程,你可以考虑显示一个过场页面或者尽可能快的渲染主页面,这样是为了表明加载正在进行并且异步的填充相关信息。不论发生何种情况,你都应当以某种方式展现当前的进度情况,让用户察觉到应用已经冻住了。
请使用类似SystraceTraceview这样的性能分析工作来确定你应用的响应性的瓶颈
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ANR 译文 Android