保持您的应用程序响应
编写能够赢得世界上所有性能测试的代码是可能的,但是仍然感觉迟钝,挂起或冻结很长时间,或者处理输入需要很长时间。应用程序的响应能力可能发生的最糟糕的事情是“应用程序无响应”(ANR)对话框。
在Android中,系统会通过显示一个说明您的应用已停止响应的对话框(例如上图中的对话框)来防范一段时间内响应不足的应用程序。此时,您的应用程序在相当长的一段时间内没有响应时间因此系统为用户提供退出应用程序的选项。设计应用程序的响应性至关重要,因此系统永远不会向用户显示ANR对话框。
本文档描述了Android系统如何确定应用程序是否没有响应,并提供了确保应用程序保持响应的指南。
什么触发ANR?
通常,如果应用程序无法响应用户输入,系统将显示ANR。例如,如果应用程序阻止UI线程上的某些I / O操作(通常是网络访问),则系统无法处理传入的用户输入事件。或许应用程序花费太多时间构建精心设计的内存结构或计算UI线程中游戏中的下一步行动。确保这些计算有效是非常重要的,但即使最有效的代码仍然需要时间来运行。
在任何情况下,如果您的应用程序执行一个潜在的长时间的操作,那么您不应该在UI线程上执行这项工作。而是创建一个工作线程,并在那里完成大部分工作。这使得UI线程(驱动用户界面事件循环)保持运行,并阻止系统断定您的代码已冻结。因为这种线程通常是在类级别完成的,所以你可以将响应性视为一种类问题。(将其与基本代码性能进行比较,这是一个方法级别关注。)
在Android中,应用程序响应性由ActivityManager和WindowManager系统服务监视。当Android检测到以下某种情况时,它将显示特定应用程序的ANR对话框:
- 在5秒内无响应输入事件(如按键或屏幕触摸事件)。
- 一个
BroadcastReceiver
尚未在10秒内完成执行。
如何避免ANR
Android应用程序通常完全在单个线程上运行,默认为“UI线程”或“主线程”。这意味着您的应用程序在UI线程中执行的任何操作都需要很长时间才能完成,因为您的应用程序没有给自己处理输入事件或意图广播的机会。
因此,在UI线程中运行的任何方法都应该在该线程上尽可能少地工作。特别是,活动应该尽可能少地建立关键的生命周期方法,如onCreate()
和 onResume()
。潜在的长时间运行操作(如网络或数据库操作)或计算成本高昂的计算(如调整位图大小)应在工作线程中完成(或者在数据库操作的情况下,通过异步请求)。
为更长时间的操作创建工作线程的最有效方法是使用 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");
}
}
要执行此工作线程,只需创建一个实例并调用 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
如果需要对意图广播采取可能长时间运行的操作。
另一个共同的问题是BroadcastReceiver
对象执行过频繁时发生。频繁的后台执行可以减少其他应用程序可用的内存量。有关如何启用和禁用的更多信息,请参见BroadcastReceiver
对象,请参见按需操纵广播接收机.
提示: 您可以使用 StrictMode
帮助查找可能长时间运行的操作,例如您可能在主线程上意外执行的网络或数据库操作。
加强响应能力
通常,100到200ms是用户将感知应用程序缓慢的阈值。因此,除了应该采取的措施之外,还有一些其他提示可以避免ANR,并使您的应用程序看起来对用户响应:
- 如果您的应用程序在后台运行以响应用户输入,请显示正在进行的操作(例如使用
ProgressBar
在你的用户界面)。 - 特别是对于游戏,对工作线程中的移动进行计算。
- 如果您的应用程序具有耗时的初始设置阶段,请考虑尽快显示启动画面或渲染主视图,指示正在加载并异步填充信息。在任何一种情况下,您都应该以某种方式表明正在取得进展,以免用户认为应用程序被冻结。
- 使用性能工具,如 systrace 和 Traceview 确定应用响应的瓶颈。
关于进程和线程,android官网讲解: 进程和线程
================Talk is cheap, show me the code================