Keeping Your App Responsive中文翻译

来源:互联网 发布:macbook必备软件 知乎 编辑:程序博客网 时间:2024/04/28 08:55

平方X翻译说明:
仅供参考,对正确性概不负责,翻译不对的欢迎指正,勿喷。

译于20160802,原文《Keeping Your App Responsive》
Develop>Training>Best Practices for Performance
关键字:
安卓ANR

保持应用响应

或许可以写出胜任所有性能测试的代码,但有的时候仍可能会感觉迟钝、挂起或冻结,有关应用的响应,可能发生最差情况就是“应用未响应”(ANR)对话框。
在安卓中,如果你的应用在一段时间内没有响应,系统守护就会显示一个写着应用停止响应的对话框,如图1.这时,因为你的应用长时间没有响应,所以系统给用户提供一个退出应用的选择。对应用进行严格的响应设计,这样系统就永远不会给用户显示ANR对话框。
本文介绍了安卓系统如何确定应用没有响应,并且为确保应用保持响应提供指南。

什么原因引发ANR?

一般情况下,如果一个应用无法响应用户的输入,系统就会显示ANR。比如,如果一个应用在UI线程阻塞了一些IO操作(通常是网络访问)使得系统不能处理传入的用户事件。或者是应用在UI线程中花费了太多的时间来创建一个复杂的内存结构,或是在游戏花费太多时间计算下一步的行动。确保计算总是高效的一直很重要,但即使是高效的代码,也需要时间来运行。
如果你的应用可能要进行潜在的漫长的操作,就不应该在UI线程中执行,而应该创建一个工作线程,在工作线程中执行大部分的工作。这样就能保持UI线程(它驱动了用户交互事件的循环)的运行并且防止因为代码卡住而被系统结束。因为这样的线程通常是在类级别完成的,你可以把响应当做一个类级别的问题。(和基本代码的性能比较,它是方法级别考虑的)
在安卓中,应用的响应由系统服务Activity Manager和Window Manager监控。如果一个应用被检测到下列情况之一,就会显示ANR对话框。

  • 一个输入事件(比如按键或触屏事件)在5秒内没有被响应(译注1)
  • 一个广播接收器在10秒内没有结束运行(译注1)

译注1:

对于这一个5s和10s进行了实测,发现是不正确的。
写一个死循环。

                long i=0L;                while (true){                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    Log.d("test","i="+ (++i));                }

1,首先要一个输入事件。

如果只是死循环,没有用户输入是不会诱发ANR的。

2,输入事件不一定能传进去

可能因为死循环在卡死,也可能因为我的Thread.sleep,反正有时候点击屏幕不一定能传入事件,要多点几下。

3,5秒钟有记录,几秒延时后显示ANR对话框

实测的时候,当一个事件输入后,5秒后Log显示如下

I/art: Thread[2,tid=9093,WaitingInMainSignalCatcherLoop,Thread*=0x7f87d0d000,peer=0x12ced0a0,"Signal Catcher"]: reacting to signal 3I/art: Wrote stack traces to '/data/anr/traces.txt'

模拟器延时1-3秒左右出现ANR,可能是我电脑卡了。
MIUI延时8-10秒,可能是故意设置的。
在测试广播接收器的时候,发现要比Activity的延时短1、2秒。

Service也一样

包括start和bind

广播接收器也一样

必须要有用户输入,且要等5秒再加延时,而不是上面说的10秒。

如何避免ANR

通常地,安卓应用都在一个单线程中运行,默认的“UI线程”或者“主线程”。这就意味着,应用做的任何需要很长时间才能完成的操作,都会诱发ANR,因为你的应用没有给它机会处理用户输入事件和intent广播。
因此,在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");    }}

要执行这个工作线程,只需要创建一个实例,并调用execute():

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

如果不使用AsyncTask可能会很复杂,但你可能要自已创建线程或HandlerThread。如果你要这样做,你应该通过调用Process.setThreadPriority() 方法并且传递THREAD_PRIORITY_BACKGROUND来把线程的优先级设为“后台”。如果你不通过这程方式来给线程设置较低的优先级,线程仍然会拖慢你的应用,因为它默认和UI线程有相同的优先级。
如果你使用了Thread或者HandlerThread,确保你的UI线程不会阻塞在等工作线程完成——不要调用Thread.wait()或者Thread.sleep()。取而代之的,你的主线程应该为其他线程提供一个Handler,来认它们在完成时发回信息。这样设计的应用允许UI线程保持对输入的响应,从而避免5秒输入事件超时的ANR。
关于广播接收器的执行时间,要特别强调的是,广播接收器是为了在后台做一些小的、离散量的工作,比如保存设置或者注册通知。因此,作为在UI线程被调用的方法,应用应该避免在广播接收器中做潜在的耗时操作或计算。如果你的应用在响应一个intent广播时要进行潜在的长时间的操作,取代通过工作线程做密集型任务的做法是,让应用启动一个IntentService。

提示:你可以通过使用StrictMode来找出可能偶然在主线程做的长时间操作,比如网络或数据库操作。

加强响应

一般情况下,用户在应用中可以觉查出缓慢的上限是100至200毫秒。因此,下面是一些关于避免ANR和让应用似乎对用户响应的额外的提示:

  • 如果你的应用在后台工作以响应用户输入,显示出当前的进展(比如UI中的一个进度条)。
  • 特别对于游戏来说,在工作线程中进行移动的计算。
  • 如果你的应用在启动时有耗时初始化设置过程,考虑显示一个启动画面,或者先尽可能快地渲染出主视图,显示正在加载并且异步地填满信息。在这两种情况下,都应该以某种方式指明当前的进展,以使用户知道应用是否卡住了。
  • 使用性能工具(比如Systrace 和Traceview)来确定应用响应能力的瓶颈。
0 0