《Android UI基础教程》——2.6节 防止应用程序无响应(ANR)

简介:

本节书摘来自异步社区《Android UI基础教程》一书中的第2章,第2.6节 防止应用程序无响应(ANR),作者 【美】Jason Ostrander,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.6 防止应用程序无响应(ANR)
Android UI基础教程
一个Android应用程序运行在它自身的进程之上,是与其他应用无关的沙盒应用。应用被单个线程操控:主线程,或者叫做UI线程。要让应用能够快速响应,Android限制了函数调用的时间。如果函数超过了它的时间限制,则会出现一个应用程序没有响应(ANR)的对话框,提示用户选择继续等待或者强制关闭应用。你应该不惜任何代价避免ANR的出现。当你在主线程上执行长时间的操作时ANR会出现,例子包括网络I/O、磁盘I/O、数据库查询以及密集的CPU运算。

提示: 任何时候你收到的Android系统的回调函数都是由主线程完成。这包括活动和服务回调函数、时间处理程序、按键监听程序等。记住不要在这些回调函数中执行任何阻塞操作。如果你确实需要执行这样的操作,开始一个后台线程或者使用AsyncTask来处 理它。

2.6.1 StrictMode
Android 2.3推出了一个新的开发者工具,名字叫做StrictMode。这个工具会检测发生在主线程上的磁盘或者网络操作并且会采取行动来警告开发者。它提供了许多方法来警告开发者,从简单的日志记录到应用程序崩溃等。

StrictMode并不能保证能够找到发生在主线程上的所有的磁盘和网络I/O。尤其是,任何通过Java本地接口(JNI)产生的访问都不会被检测到。需要清醒认识到虽然StrictMode很有用,但是它并不足以创建及时响应的程序。

声明StrictMode**

下面是一个简单的检测所有类型的网络和磁盘I/O的StrictMode。

`StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()`
`    .detectAll()`
`    .penaltyLog()`
`    .penaltyDialog()`
`    .build());`

这将会检测正在执行的线程上的所有网络和磁盘I/O,并且会采取两种行动:打印警告到日志中并对用户显示警告对话框。这个例子设置只能在当前线程设置警告。要检测任意线程上的冲突,使用setVmPolicy调用:

`StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()`
`    .detectAll()`
`    .penaltyLog()`
`    .penaltyDeath()`
`    .build());`

建议在所有创建的工程中都启用StrictMode。在开发初期捕获这些例子更好,不会发生大的架构改变。

禁用StrictMode

尽管StrictMode对于创建快速响应的应用非常有帮助,在市场上发布应用时应当禁用它。否则,用户可能会遇到违反政策的对话框或者甚至会经历应用崩溃。处理这个的一个简单方法就是只在调试模式时启用StrictMode(有一个调试键作为签名)。要检测一个应用是否运行在调试模式,检测ApplicationInfo标志就行。下面的代码片段会检测应用是否使用了调试签名:

`public static boolean isDebugMode(Context context) {`
`    PackageManager pm = context.getPackageManager();`
`    try {`
`        ApplicationInfo info = pm.getApplicationInfo`
`        → (context.getPackageName(), 0);`
`        return (info.flags & ApplicationInfo.FLAG`_`DEBUGGABLE) != 0;`
`    } catch (NameNotFoundException e) {`
`    }`
`    return true;`
`}`

2.6.2 后台任务
你将会遇到一种常见情况是需要执行不能在UI线程上运行的长时间操作——比如下载RSS订阅、写文件或者运行定时器等。运行这些任务需要许多时间,这将会阻止UI线程的更新。你可以采用一些策略来处理这种情况。通常情况下,你会创建一个能够执行该任务的新线程,当完成任务之后再更新UI或者应用程序的状态。下面是实现这一行为的几个策略。

Handler和消息队列

防止阻塞UI线程的一个好方法是让任务线程在后台运行。然而,当任务完成时,你常常需要更新UI。对于UI的更新只能由UI线程执行,否则将会产生异常。可以使用Handler类来做到这一点。Handler允许你发送在一段时间之后再由其处理的消息。这些消息可以立即进行处理,也可以计划好在将来的某段时间进行处理。Handler在handleMessage方法中处理消息。

默认情况下,一个Handler实例是绑定在创建它的线程上的(通常是主线程)。绑定Handler到UI线程上为异步更新UI提供了一个方便的方法。然而,你同样可以选择提供一个可选的Looper实例,让Handler运行在单独的线程上。循环类用于为一个线程运行消息循环。通过使用循环类,你可以发送消息并让它们运行于任何线程实例上。

注意: Looper类创建和管理一个包含单个线程的所有消息的MessageQueue对象。UI线程已经为你创建了一个消息队列和循环类。

Handler具有发送在一段时间之后再进行处理的消息的能力,这一点使得它非常适合实现基于时间的行为。下面是一个TimeTracker应用将会用到的跟踪时间间隔的简单Handler:

`private Handler mHandler = new Handler() {`
`     public void handleMessage(Message msg) {`
`          long current = System.currentTimeMillis();`
`          mTime += current - mStart;`
`          mStart = current;`
`          TextView counter = (TextView) TimeTrackerActivity.this.`
`          → findViewById(R.id.counter);`
`          counter.setText(DateUtils.formatElapsedTime(mTime/1000));`
`          mHandler.sendEmptyMessageDelayed(0, 250);`
`     };`
`};`

这段代码更新了这个Activity类的两个变量,该类被用于保存当前时间。之后它更新了UI(记住Handler回调函数将会默认在UI线程上执行,除非你明确地给出它会运行于另一个线程上)。最后,它指定另一条消息在100毫秒之后执行。SendEmptyMessage方法也传入了一个用于区分的整数参数。由于这里只有一个单条消息,所以第一个参数被设置为0。使用Handler的消息处理的API,你可以为使用定时器创建方便的方法。

1.在TimeTrackerActivity类中创建一个startTimer方法。这个方法将会记录当前的系统时间并且发送一条消息给Handler,开启一个定时器。为了阻止开启定时器两次,在发送下一条消息时移除任何存在的消息。

`private void startTimer() {`
`     ``     ``mStart = System.currentTimeMillis();`
`     ``     ``mHandler.removeMessages(0);`
`     ``     ``mHandler.sendEmptyMessage(0);`
`    }`

2.stopTimer方法从Handler的消息队列中移除所有消息。

`private void stopTimer() {`
`     mHandler.removeMessages(0);`
`}`

3.resetTimer方法将会先调用stopTimer,随后往列表适配器中添加当前时间,并在列表中展示。

`private void resetTimer() {`
`     stopTimer();`
`     if (mTimeListAdapter != null)`
`     ``     ``mTimeListAdapter.add(mTime/1000);`
`     mTime = 0;`
`}`

最终,你将会需要知道定时器是否已经被停止。

4.创建一个检测消息队列中消息的方法。

`private boolean isTimerRunning() {`
`    ``return mHandler.hasMessages(0);`
`}`

你现在明白完成定时器的所有逻辑了。

Activity.runOnUIThread

仅仅为了从一个后台线程中更新UI而创建一个Handler的情况很常见。Android提供了Activity.runOnUIThread方法,为这种情况提供了一条捷径。这个方法采用一个runnable并将其发送给UI线程的处理消息的Handler。主线程会在空闲时运行在runnable中的代码。

AsyncTask

开始一个后台线程执行一些任务并在任务结束之后更新UI,这种情况很常见。你可以只使用一个线程来执行这些任务并使用runOnUIThread方法来将数据展示给用户。但是如果你需要展示进程将会发生什么呢?在这些情况下,向UI消息处理的Handler发送runnable是大材小用了。Android中有一个叫做AsyncTask的类,这个类是针对这种情况特别设计的。

你可以扩展AsyncTask类来创建一个简单的线程,这个线程可以被用来执行后台任务以及在UI线程上打印结果。它包括了在任务被完成前后的UI更新,还有顺便进行的进程更新。下面是一个AsyncTask的基本形式:

`private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {`
`     protected Long doInBackground(URL... urls) {`
`     while (true) {`
`          // Do some work`
`          publishProgress((int) ((i / (float) count) `*` 100));`
`     }`
`     return result;`
`     }`
`     protected void onProgressUpdate(Integer... progress) {`
`          setProgressPercent(progress[0]);`
`     }`
`     protected void onPostExecute(Long result) {`
`          showDialog("Result is " + result);`
`     }`
`}`

传入任务的3个参数分别用来指定执行时的参数类型、设置进程的参数类型以及当后台任务完成时的返回结果类型。你可以在任务运行之前使用onPreExecute来更新UI,使用onProgressUpdate来更新UI进程的指示器,使用onPostExecute在任务结束时更新UI。这些方法都运行于主UI线程之上,所以更新视图并没有风险。所有的代码都运行于doInBackground方法中,你可以将其看作仅仅是一个线程的运行方法。

AsyncTask对于需要更新一个UI组件的快速一次性任务很有用(例如从Twitter下载新的帖子并把这些帖子加载到时间线中去)。

相关文章
|
3月前
|
存储 Android开发 数据安全/隐私保护
如何在Android设备上撤销Flutter应用程序的所有权限?
如何在Android设备上撤销Flutter应用程序的所有权限?
194 64
|
3月前
|
缓存 Android开发 开发者
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
573 62
|
3月前
|
开发工具 Android开发 开发者
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
218 61
|
5月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
294 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
7月前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
本篇将带你实现一个虚拟音乐控制台。用户可以通过界面控制音乐的播放、暂停、切换歌曲,并查看当前播放的歌曲信息。页面还支持调整音量和动态显示播放进度,是音乐播放器界面开发的基础功能示例。
304 80
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
|
6月前
「Mac畅玩鸿蒙与硬件51」UI互动应用篇28 - 模拟记账应用
本篇教程将介绍如何创建一个模拟记账应用,通过账单输入、动态列表展示和实时统计功能,学习接口定义和组件间的数据交互。
261 68
「Mac畅玩鸿蒙与硬件51」UI互动应用篇28 - 模拟记账应用
|
7月前
|
流计算 UED
「Mac畅玩鸿蒙与硬件48」UI互动应用篇25 - 简易购物车功能实现
本篇教程将带你实现一个简易购物车功能。通过使用接口定义商品结构,我们将创建一个动态购物车,支持商品的添加、移除以及实时总价计算。
239 69
「Mac畅玩鸿蒙与硬件48」UI互动应用篇25 - 简易购物车功能实现
|
7月前
|
数据处理
「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具
本篇将带你实现一个评分统计工具,用户可以对多个选项进行评分。应用会实时更新每个选项的评分结果,并统计平均分。这一功能适合用于问卷调查或评分统计的场景。
247 65
「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具
|
6月前
|
人工智能 自然语言处理 API
用自然语言控制电脑,字节跳动开源 UI-TARS 的桌面版应用!内附详细的安装和配置教程
UI-TARS Desktop 是一款基于视觉语言模型的 GUI 代理应用,支持通过自然语言控制电脑操作,提供跨平台支持、实时反馈和精准的鼠标键盘控制。
2220 17
用自然语言控制电脑,字节跳动开源 UI-TARS 的桌面版应用!内附详细的安装和配置教程
|
7月前
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
本篇将带你实现一个自定义天气预报组件。用户可以通过选择不同城市来获取相应的天气信息,页面会显示当前城市的天气图标、温度及天气描述。这一功能适合用于动态展示天气信息的小型应用。
314 38
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件