保持你的应用程序响应

本文翻译自:Keeping Your App Responsive
翻译人员:麦田里的守望者-Rye

保持你的应用程序响应

在Android中,系统会针对一段时间没有响应的应用程序,通过显示对话框来告诉你的应用程序已经停止响应,如图1中的对话框所示。这个时候,一旦你的应用程序有相当长的一段时间未响应,系统就会为用户提供一个退出应用程序的选项。为了让系统决不在用户面前呈现ANR对话框,在你的应用程序中设计及时响应是至关重要的。

在用户面前呈现的ANR对话框
图1 在用户面前呈现的ANR对话框

什么引发了ANR?


一般来说,如果不能响应用户的输入,系统就会显示ANR。例如,如果应用程序在UI线程中做一些阻塞的I/O操作(频繁的访问网络),系统就不能处理传入的用户输入事件。或者可能应用程序花费大量的时间在建立详细的内存结构上,或者在游戏的UI线程中计算下一个移动。对于确保这些计算是高效的总是很重要的,但即使是最高效的代码仍然需要时间来运行。

在任何情况下,你的应用程序执行一个潜在的冗长的操作,你不应该把这项工作放在UI线程中执行,而是创建一个工作线程而且把大量工作都放在这里执行。这样才能保证UI线程运行,防止系统结束导致你的代码冻结。因为这样的线程通常是在类级别上完成的,所以你可以把响应性作为一个类问题。

在Android中,应用程序响应性是被Activity Manager和Window Manager系统服务监控的。当Android检测到下列的条件之一,就将为特定的应用程序显示ANR对话框。

  • 在五秒之内不能响应用户的输入事件(如按键或触摸屏幕事件)
  • 广播在10秒内没有执行完

怎样防止ANRS


Android应用程序通常在一个单一的线程上运行(默认是UI线程或者主线程)。这意味着你的应用程序中的任何事情都在UI线程中完成,如果在UI线程中执行很长一段时间才能完成就会引发ANR,因为你的应用没有给自己机会来处理输入事件或意图广播。

因此,在UI线程中的任何方法都应该在这个线程中只做尽可能少的事情。特别地,activity应该在生命周期方法onCreate()和onResume()中做尽可能少的事情。潜在的长时间运行的操作,如网络和数据库操作,或者计算昂贵的计算,如改变bitmap,应该在工作线程中执行(或在数据库操作的情况下,通过一个的异步请求)。

为了更长时间的操作,更高效的方式是用AsyncTask类创建一个工作线程。仅仅只需继承AsyncTask,实现doInBackground()方法就可执行工作。为了设置进度的改变来告知用户,你可以调用执行onProgressUpdate()回调方法的publishProgress()方法。在你实现的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(Process.THREAD_PRIORITY_BACKGROUND)来将线程的优先级设置为“后台”优先级。如果你不把线程设置为一个较低的优先级,那么这个线程仍然可以减慢你的应用程序,因为它在默认情况下和UI线程运行在同一个优先级

扫描二维码关注公众号,回复: 778801 查看本文章

如果你实现了Thread或者HandlerThread,要确保你的UI线程在等待工作线程完成的时候不会被阻止-不要调用Thread.wait()或者Thread.sleep()方法。在等待工作线程完成的时候而不是阻止,你的主线程应该提供一个为其它线程要提示返回完成的Handler。采用这种方式设计你的应用程序将允许你应用程序的UI线程对用户的输入保留响应而且避免5秒输入超时造成的ANR。

在BroadcastReceiver执行时间具体约束上强调广播接收者应该做到:在后台中做小而离散的工作,例如保存设置或注册Notification。因此,和UI线程中调用的其它方法一样,应用程序应该在广播接收者中避免潜在的长时间操作或计算。但是,而不是通过工作线程来做密集任务,如果一个潜在的长时间运行的动作需要在一个intent broadcast采取响应,那么你的应用程序应该启动一个IntentService。

提示:你可以通过使用StrictMode来帮助找到潜在的长时间运行的操作,例如你可能偶然的在主线程做网络和数据库操作。

增强响应


大体上来说,100-200毫秒是用户在应用程序中意识到卡顿的超出阈值。因此,这里有一些额外的技巧,你应该做来避免ARN而且确保你的应用程序看起来响应了用户:

  • 如果你的应用程序为了响应用户输入事件正在后台工作,可以显示正在取得的进度(比如在UI中使用progressbar)。
  • 对于游戏,在工作线程中做移动计算。
  • 如果你的应用程序有一个耗时的初始安装阶段,考虑显示一个启动画面或者尽可能快的渲染主视图,表明加载正在进行中,并异步填充信息。在任何情况下,你应该以某种方式表示正在取得的进展,以免用户认为应用程序被冻结了。
  • 使用性能工具如Systrace和Traceview来确定你的应用程序中响应性的瓶颈。

猜你喜欢

转载自blog.csdn.net/wangjiang_qianmo/article/details/51706403
今日推荐