前言html
本文翻译自Android开发者官网的一篇文档,主要用于介绍ANR相关的一些要点。android
中国版官网原文地址为:https://developer.android.google.cn/training/articles/perf-anr。数据库
路径为:Android Developers > Docs > 指南 > Best practies > Performance > Keeping your app responsive网络
正文app
在世界上,编写能够赢得每个性能测试的代码是有可能的,可是仍然感受在某些显著的时间段内缓慢、挂起或者冻住,或者花费太长的时间来处理输入事件。可能发生的应用响应上最糟糕的事情是“应用未响应”(ANR)对话框。异步
在Android中,系统经过显示一个说明您的应用已经中止响应的对话框来防止应用在一段时间内响应不足,就像图1中的对话框同样。在这个点上,您的应用已经至关一段时间没有响应了,因此系统给用户提供了一个选项来终止该应用。将响应设计到您的应用,来让系统历来不给用户显示ANR对话框是极其重要的。性能
图1.展现给用户的ANR对话框测试
本文描述了Android系统如何决定是否应用是不响应的而且提供指导来确保您的应用保持响应。google
是什么触发了ANR?url
通常来讲,若是应用不能响应用户输入事件,系统会显示ANR。例如,若是应用在UI线程上的某些I/O操做(频繁地访问网络)上阻塞了,以致于系统没法处理进来的用户输入事件。或者可能应用在UI线程上花费了太多时间来构建一个复杂的内存结构或者在游戏中计算下一个移动。确保这些计算高效老是很重要的,可是即便是最高效的代码仍然会花费时间来运行。
在任何您的应用可能执行长时间操做的场景下,您都不该该在UI线程中执行这项工做,而应该建立一个工做线程来处理大部分的工做。这让UI线程(它驱动用户接口时间循环)保持运行而且防止系统判定您的代码已经冻住了。由于这样的线程一般是在class级别上完成的,因此您能够把响应当作是一个class问题。(将这和基本的代码性能进行比较,代码性能问题是一个方法级别的概念。)
在Android中,应用响应被Activity Manager和Window Manager 系统服务监视,当检测到有如下条件之一时,Android将会为特定的应用显示ANR对话框:
如何避免ANR
Android应用一般所有在一个单一的线程中运行(默认为“UI线程”或者“主线程”)。这意味着您的应用在UI线程中正在执行的须要花费很长时间来完成的任何任务,均可能触发ANR对话框,由于您的应用没有给本身机会来处理输入事件或者意图广播。
所以,任何在UI线程中运行的方法应该尽量作少许的工做。尤为是,Activity应该尽量少地在如onCreate()和onResume()这样的关键生命周期方法中设置。潜在的诸如网络操做或者数据库操做这样的长时间运行的操做,或者诸如从新设置bitmap大小等这样昂贵的计算,应该在工做线程中来执行(或者在数据操做的状况下,经过异步请求)。
为更长时间的操做建立工做线程最有效的方式是使用AsyncTask类。简单地继承AsyncTask而且实现doInBackground()方法来执行工做。为了将进度改变发送给用户,您能够调用publishProgress(),它调用了onProgressUpdate()回调方法。从onProgressUpdate()方法(它在UI线程中运行)的实现,您能够通知用户。例如:
1 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { 2 // Do the long-running work in here 3 protected Long doInBackground(URL... urls) { 4 int count = urls.length; 5 long totalSize = 0; 6 for (int i = 0; i < count; i++) { 7 totalSize += Downloader.downloadFile(urls[i]); 8 publishProgress((int) ((i / (float) count) * 100)); 9 // Escape early if cancel() is called 10 if (isCancelled()) break; 11 } 12 return totalSize; 13 } 14 15 // This is called each time you call publishProgress() 16 protected void onProgressUpdate(Integer... progress) { 17 setProgressPercent(progress[0]); 18 } 19 20 // This is called when doInBackground() is finished 21 protected void onPostExecute(Long result) { 22 showNotification("Downloaded " + result + " bytes"); 23 } 24 }
要执行该工做线程,简单地建立一个实例而且调用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你应该作的以外,这里有一些附加的提示,让您的应用看起来对用户是响应的:
结语
本文最大限度保持原文的意思,因为笔者水平有限,如有翻译不许确或不稳当的地方,请指正,谢谢!