概述:

有时候可以写出通过世界上所有性能测试的代码, 但是依然感觉迟缓, 卡顿或者凝固, 或者花费很久才能处理输入. APP可以出现的最坏的情况就是一个” 应用程序无响应(Application Not Responding)”(ANR)对话框了. 在Android中, 如果APP有一段时间没有响应, 系统会显示一个这样的对话框:


这代表APP长时间不能响应, 所以系统会给用户提供一种可以退出APP的方法. 让APP可以一直响应用户的操作十分关键, 这样系统就不会显示这个对话框了. 下面将介绍为啥会出现这样的问题, 并如何避免该问题.

是什么引发了ANR?

通常会在用户的输入操作不响应的时候显示. 栗如, 如果一个APP在UI线程中执行某些I/O操作中卡住了(频繁的网络访问), 导致系统不能处理用户的输入事件. 或者APP花费太多时间来在UI线程中计算下一个游戏步骤等. 保证这些计算的效率十分的重要, 但是即便是最高效的代码还是要花时间.

在任何情况下, 当你的APP执行一个可能长时间的操作时, 你都应该避免在UI线程中执行它, 而是应该创建一个工作线程并在其中执行大部分的工作. 这可以让UI线程保持运行并避免系统认为你的UI卡住了. 因为通常线程时类级别的, 所以你可以认为响应是一个类问题(与基础代码性能对比, 它们是方法级别的问题). 在Android中, APP响应是由Activity管理器和窗口管理器系统服务负责监测的. Android将会在某个APP发生下列这些问题的时候显示一个ANR对话框:

l  5秒钟之内不响应一个输入事件.

l  一个BroadcastReceiver在10秒内还没有完成执行.

如何避免ANR:

Android APP通常都运行在默认的UI线程中. 这意味着APP在UI线程中做的任何消耗较多时间的事情都会导致触发ANR问题, 因为你的APP不给输入事件或者intent广播留时间. 因此, 任何在UI线程中的方法都应该尽量做轻量级的任务. 尤其是应该在关键的生命周期方法中做尽量少的事情, 比如onCreate()和onResume()方法. 可能会执行长时间的操作, 比如网络或者数据库操作, 或者运算量很大的计算操作比如缩放Bitmap, 这些都应该在工作线程中执行(或者在通过异步请求数据库操作).

创建一个工作线程用于长时间操作的最高效的方式是使用AsyncTasklei. 只需要简单的继承AsyncTask并实现其中的doInBackground()方法来执行工作即可. 如果需要发送进度给用户, 可以调用publishProgress(), 这会调用onProgressUpdate()回调方法. 从该方法中, 就可以修改UI来提醒用户了(它是在UI线程中运行的. 栗如:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do thelong-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 iscalled each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This iscalled when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

要执行这个工作线程, 只需要创建一个实例并执行execute()方法.

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

尽管更加复杂, 但是有时候你可能也会想要自定义自己的Thread或者HandlerThread类. 如果有这样的需求, 那么你应该通过Process.setThreadPriority()方法并传入THREAD_PRIORITY_BACKGROUND作为参数来设置thread优先级为”background”.如果你不通过这种方式来设置线程为一个较低的优先级, 那么该线程可能还是会减慢你的APP的速度, 因为它默认情况下跟UI线程是同一个优先级的.

如果你实现了自己的Thread或者HandlerThread, 要确保你的UI线程没有因为等待工作线程完成而阻塞– 不要调用Thread.wait()或者Thread.sleep(). 而是应该在UI线程中提供一个Handler来接收其它线程的完成消息. 通过这种方法设计你的APP将会让你的APP UI线程保持对输入的响应并因此而避免由5秒没响应而产生的ANR对话框.

BroadcastReceiver有一个特殊的约束, 就是它所执行的内容应该是小而离散型的, 比如保存设置或者注册一个Notification. 就好像其它在UI线程中调用的方法那样, APP应该避免在Broadcast receiver中执行可能长时间的操作或者计算, 如果需要长时间的响应一个broadcast你的APP应该使用一个IntentService.

提示: 你可以使用StrictMode来帮助查找那些长时间执行的操作比如网络或者数据库操作.

强化响应能力:

通常, 在一个APP中, 100到200ms是一个让用户感知到响应缓慢的阈值. 这里有一些额外的建议可以让你的APP运行起来更加流畅(不在避免ANR的范围内):

l  如果你的APP正在后台执行工作来响应用户的操作, 那么应该为用户显示一个进度条.

l  尤其是对游戏而言, 应该在工作线程中完成对移动的计算.

l  如果你的APP拥有一个事件比较耗时的初始化启动阶段, 考虑显示一个启动界面或者尽可能快的渲染显示主界面, 表示加载正在进行中, 并异步的填充信息. 不管那种情况, 你应该显示一些东西来表明正在做事情, 免得用户以为APP卡住了.

l  使用性能工具比如Systrace和Traceview来判断APP响应中的瓶颈.

 

参考: https://developer.android.com/training/articles/perf-anr.html

 

更多相关文章

  1. Android(安卓)NDK开发入门实例
  2. [置顶] 那些你应该知道却不一定知道的——View坐标分析汇总
  3. 数据共享的方法
  4. Android(安卓)小项目之--Mini音乐播放器
  5. Mac Android(安卓)studio NDK 开发
  6. Android开发网上的一些重要知识点_4
  7. 【Android游戏开发十六】Android(安卓)Gesture之【触摸屏手势识
  8. ApiDemos导入时No projects are found to import
  9. Android帮助文档翻译——开发指南(二)Activity

随机推荐

  1. php中的类及trait
  2. 第十周作业
  3. Python VS Java如何选择?Python学习分析!
  4. 2021-03-08:在一个数组中,任何一个前面的数
  5. vue基础知识 指令
  6. 2021年值得推荐的几个可视化软件
  7. 拒绝浪费背后是对企业错误行为的一次次修
  8. 对华为等的一种构想
  9. 【3.1-3.7】上周精彩回顾
  10. 2021年国内好用的可视化工具