《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下载新的帖子并把这些帖子加载到时间线中去)。

相关文章
|
2月前
|
IDE Java 开发工具
深入探索安卓应用开发:从环境搭建到第一个"Hello, World!"应用
本文将引导读者完成安卓应用开发的初步入门,包括安装必要的开发工具、配置开发环境、创建第一个简单的安卓项目,以及解释其背后的一些基本概念。通过一步步的指导和解释,本文旨在为安卓开发新手提供一个清晰、易懂的起点,帮助读者顺利地迈出安卓开发的第一步。
220 65
|
2月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
1天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
10天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
10天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
14天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
18天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
45 8
|
16天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
26 2
|
19天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
46 5
|
20天前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。