避免Application Not Responding (ANR),保持你的程序能够得到响应。

ANR可能是monkey测试中最容易出现的问题了。Android程序员面试的时候,也极有可能会谈到这个方面。下面的文章主要是对Google Developers的http://developer.android.com/training/articles/perf-anr.html结合自己理解的纯人脑翻译。

Here we go:

你写的代码可能成功通过了所有的性能测试,但程序仍可能在特定的情况下迟钝、挂起或者冻结,又或者花了很久去处理输入。最糟糕的情况莫过于,程序出现了 "Application Not Responding" (ANR) 应用程序无响应的对话框。

避免Application Not Responding (ANR),保持你的程序能够得到响应。_第1张图片

在Android中,系统检测到你的程序在一定时间没有响应就会出现如上的对话框。这时,系统会在一段没有响应的时间之后,让用户可以选择停止程序。在设计程序的时候,避免程序出现这种情况,是非常关键的。这篇文章介绍了“Android如何判定程序是否没有响应 和 如何保证程序保持响应”。

一、什么导致了ANR? 

通常,当程序不能响应用户的输入时,系统就会显示ANR。比如,如果程序阻塞在UI线程中处理I/O(通常是访问网络),而不能处理用户输入事件。又比如, app花了太多时间用于构建内存结构或者在UI线程中计算游戏的下一步动作。让计算高效的执行很重要,但有时即使再高效的代码也需要一定时间去运行。所以,你的app在任何需要一个长时间操作的时候,你不应该在UI线程中去执行任务,而应该创建一个工作线程,让这个线程去执行大部分的工作。 这就保持UI线程(驱动用户界面的事件循环)保持运行,并且防止系统判定你的程序停止运行。因为这样的线程(UI线程)处理通常是在类级别完成的,你可以认为“响应”是最高级别的问题(与之相比的基本代码的运行,是方法级别的)。

在Android中,程序的响应情况由Activity Manager和Window Manager system services监控。当出现如下的任一情况就会显示ANR对话框:

  • 输入事件 (比如按键和触屏事件) 5秒钟没有响应。
  • 一个 BroadcastReceiver 10秒钟没有结束执行
二、如果避免ANR?

Android程序通常运行在一个线程之内(默认是“UI线程”或者“主线程”)。 这意味着在UI线程中需要长时间才能结束运行的任务会触发ANR,因为程序无暇处理输入事件或intent broadcasts。因此,任何在UI线程中跑的方法应当尽可能少地在相应线程处理任务。特别地,activities 应该尽可能少在关键生命周期方法如 onCreate() 和onResume()。潜在需要长时间运行的操作如网络、数据库操作,或者耗费大量计算资源的如重新计算位图等应该在工作线程中执行。 (或者比如在数据库中, 通过异步请求)。

更有效的方法是用 AsyncTask 类为长时间的操作创建一个工作线程。 简单地继承(extend) AsyncTask 和实现(implement) 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 类。如果你这么做,需要把线程优先级设置为 "background" 优先级, 通过调用Process.setThreadPriority() 并传参 THREAD_PRIORITY_BACKGROUND。如果你不这样降低线程的优先级,那么这个线程很可能还是会拖慢你的app,因为它和UI线程的优先级默认是一样的。

如果你使用 Thread 或 HandlerThread, 要确保你的UI线程不会在等待工作线程完成时阻塞——不要调用 Thread.wait() or Thread.sleep()。为了不用阻塞等带工作线程完成,你的主线程应该提供一个 Handler 让其他线程在执行完后通知(post back)主线程。这样设计你的程序,你会让你的UI线程保持对输入的响应,因而避免了ANR的对话框(由输入事件的5秒超时产生)。

在 BroadcastReceiver 执行时间上的特定约束,强调了broadcast receivers应该做的是:在后台,小而离散的工作量,比如保存一个设置或者注册一个Notification。其他在UI线程中被调用的方法也是一样,程序应该避免潜在的长时间操作或者broadcast receiver中的长时间计算。把这些强度大的工作交给工作线程,你的程序应该起一个 IntentService 如果需要长时间的操作来回应 intent broadcast。

提示 :你可以使用 StrictMode 来帮助发现潜在的长时间运行操作如网络、数据库操作,而这些操作可能不是你故意在主线程中去做的。

三、加强响应

通常超过100到200毫秒的阈值,用户会察觉程序的缓慢。所以,这里有更多的一些建议让你不仅仅是避免ANR,而且让你的程序对用户来说是响应及时的:

  • 如果你的程序在后台操作,需要对用户的输入响应,那么在界面要去展示执行的进度 (比如在你的UI线程中用一个 ProgressBar )。
  • 特别是对游戏而言,在工作线程中计算运动(moves)。
  • 如果你的程序有一个耗时的初始按照阶段,可以考虑用一个启动画面或者尽可能快的渲染主界面,表明正在加载并且异步地填充信息,在这两种情况下,你应该以某种方式表示正在取得进展,以免使用者觉得应用程序冻结。
  • 使用性能工具,如 Systrace 和 Traceview 来检查你程序响应的瓶颈。



你可能感兴趣的:(翻译)