Android性能优化系列---避免ANR

(请关注我们的微信公众号:Java和Android大牛频道)

本文翻译自Google官方文档

Keeping Your App Responsive

有这样一种情况:即使你写的代码通过世界上的每一个性能测试,但程序在特定的操作和重要的阶段仍然让人感觉运行缓慢,或者需要花很长时间处理输入。这种发生在你的app里的糟糕的响应是“Application Not Responding(ANR)”对话框。

这里写图片描述

Figure 1. An ANR dialog displayed to the user.

在Android里,当app一段时间无响应时,系统会弹出一个对话框,告诉你,你的app长时间没响应。如图一。

出现该对话框,表明你的app已在合理的一段时间内已没有响应用户操作,因此,系统弹出该对话框窗口,给用户选择是否退出应用。设计高响应的应用以便于系统从不展示ANR对话框给用户是很关键的。

这篇文档描述了android系统如何判断app是否是ANR的,也提供了相关如何避免ANR的建议。

What Triggers ANR?

一般地,如果app不能响应用户的输入,系统将展示ANR。例如,应用在UI线程里阻塞在I/O操作(频繁的网络访问)导致系统不能处理来自用户的输入事件。又或者在在游戏app里在UI线程上进行耗时的位置计算(从一个位置移动到另一个位置)。

在你的app里任何潜在的耗时操作,你都不应该在UI线程里执行。而是产生一个非UI线程,在该非UI线程里进行耗时操作。这确保了你的UI线程(能循环驱动用户界面事件)运行,并且避免了系统认为你的应用已被阻塞。因为如此的线程实现在一个类级别上,你可以认为app响应的问题是类级别上的问题(相比于基本的代码性能问题,代码性能问题可以认为是方法级别上的问题)。

在Android里,Activity Manager 和 Window Manager 系统服务会监控app是否有ANR发生并当监测到有ANR发生时展示ANR对话框。下面两种情境将触发ANR:

  • 5秒内不能响应用户输入(例如:键盘按下或者屏幕触摸事件)

  • BroadcastReciever在10秒内不能执行完成

How to Avoid ANRs

Android应用一般情况下默认运行在一个单一线程上(称为UI线程或者主线程)。这意味着任何在你的UI线程里做的耗时操作都能触发ANR。因为在这种情况下,你的app将没有机会处理输入事件和广播意图。

因此,任何在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");
     }
  }

为了执行子线程,简单地产生AnsyncTask的实例,调用execute()方法。

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

有时你可能需要自己产生Thread或者HandlerThread类,虽然这比起AnsyncTask更复杂点。如果你这样做,你需要设置线程优先级为“background”优先级。通过Process.setThreadPriority()来设置线程优先级,传递参数THREAD_PRIORITY_BACKGROUND即可。如果你不设置你的线程优先级为THREAD_PRIORITY _BACKGROUND,该线程仍然可能放慢你的应用。因为它默认和你UI线程一个优先级。

如果,你实现Thead或者HandlerThread,确保你的UI线程在等待子线程运行结果完成时不要被阻塞–即在主线程里不要调用Thread.wait()或者Thread.sleep()。为了避免阻塞你的主线程,你的主线程应该给子线程提供一个Handler用于通知UI线程操作执行完成。按这种方式设计你的应用将能让你的UI线程能响应用户输入并且避免5秒输入响应超时而弹出ANR。

Android专门针对BroadcastReceiver的执行时间限制主要是为了强调广播接收者主要用于做如下事情:小的、零碎离散的后台工作。例如:保存设置或者注册Notification等。像其他在UI线程里调用的方法一样,广播接收者里也应该避免潜在的长时间操作或者计算。但是不同于把耗时操作放在子线程里,为了响应一个耗时操作的广播意图,你的应用应该开启一个intentService。

Tip:你能用StrictMode帮助你发现不小心放在UI线程里的潜在的耗时操作(例如网络或者数据库操作)。

Reinforce Responsiveness

一般地,100-200ms是用户感知缓慢的时间分割点。如此,下面是一些为了避免ANR你应该遵循的一些额外的建议:

  • 1.为了响应用户操作你的app将一些操作放在了后台,你应该给用户显示进度条(例如在你的UI上放一个ProgressBar)

  • 2.特别地开发游戏应用时,位置计算(例如移动)放在后台进程

  • 3.如果你的应用有一个长时间初始化阶段,可以考虑显示一个渐进缓冲的屏幕或者尽可能快的渲染主界面,已表明加载正在进行,然后异步填充数据。不管如何,你都应该表明你的app正在加载,以免让用户觉得你的app好像死掉了一样

  • 4.使用如Systrace和Traceview这样的性能工具分析你的app响应的瓶颈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值