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

相关文章
|
16天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
17天前
|
消息中间件 安全 数据处理
Android为什么不能在子线程更新UI
Android为什么不能在子线程更新UI
23 0
|
17天前
|
数据库 Android开发 开发者
构建高效Android应用:Kotlin协程的实践指南
【4月更文挑战第2天】随着移动应用开发的不断进步,开发者们寻求更流畅、高效的用户体验。在Android平台上,Kotlin语言凭借其简洁性和功能性赢得了开发社区的广泛支持。特别是Kotlin协程,作为一种轻量级的并发处理方案,使得异步编程变得更加简单和直观。本文将深入探讨Kotlin协程的核心概念、使用场景以及如何将其应用于Android开发中,以提高应用性能和响应能力。通过实际案例分析,我们将展示协程如何简化复杂任务,优化资源管理,并为最终用户提供更加流畅的体验。
|
18天前
|
开发框架 安全 Android开发
探索安卓系统的新趋势:智能家居应用的蓬勃发展
随着智能家居概念的兴起,安卓系统在智能家居应用领域的应用日益广泛。本文将探讨安卓系统在智能家居应用开发方面的最新趋势和创新,以及其对用户生活的影响。
13 2
|
2天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
2 0
|
2天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
5 0
|
3天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
5天前
|
搜索推荐 开发工具 Android开发
安卓即时应用(Instant Apps)开发指南
【4月更文挑战第14天】Android Instant Apps让用户体验部分应用功能而无需完整下载。开发者需将应用拆分成模块,基于已上线的基础应用构建。使用Android Studio的Instant Apps Feature Library定义模块特性,优化代码与资源以减小模块大小,同步管理即时应用和基础应用的版本。经过测试,可发布至Google Play Console,提升用户便利性,创造新获客机会。
|
6天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
6天前
|
编解码 人工智能 测试技术
安卓适配性策略:确保应用在不同设备上的兼容性
【4月更文挑战第13天】本文探讨了提升安卓应用兼容性的策略,包括理解平台碎片化、设计响应式UI(使用dp单位,考虑横竖屏)、利用Android SDK的兼容工具(支持库、资源限定符)、编写兼容性代码(运行时权限、设备特性检查)以及优化性能以适应低端设备。适配性是安卓开发的关键,通过这些方法可确保应用在多样化设备上提供一致体验。未来,自动化测试和AI将助力应对设备碎片化挑战。

热门文章

最新文章