“不能在子线程中更新UI”
“主线程不能做耗时操作”
这些话被我们奉为圭臬,但有多少人想过为什么不能在子线程中更新UI?为什么主线程不能做耗时操作?
首先先要从Android中的一个叫ANR的机制一点一点说起!
在说ANR之前 我们还是先要了解一下什么是ANR
ANR全名:Application Not Responding 即“应用程序无响应”。首先我们尝试去Android Developers官网寻找一下对于ANR的权威解释
这是Android开发者官网开发者指南中对ANR以及触发机制的描述, 文档第一段可以看到这句话:
"系统会通过显示一个说明您的应用已停止的响应的对话框来防范一段时间内的响应不足的应用程序”。
读起来虽然很晦涩...但我们还是会有一个疑问:文中所说的防范 具体是防范哪些应用程序? 该应用程序又是执行在哪个线程?。不急,我们尝试从第二段文档中找下答案,注意这句话:
“在您的应用程序执行可能冗长的操作的任何情况下, 您不应该在UI线程上执行工作”。
其中您不应该在UI线程上执行工作这句话还被黑体加粗。
由此,结合文档我们大致明白,之前提到的“防范的应用程序”指的是耗时操作,“执行的线程“是UI线程。再结合第二段文档中的话可以得出一个结论:当你在UI线程当中执行耗时操作的时候,会触发Android防范机制:ANR。
那么今天我们讨论的重点并不在于此,今天我们要从设计师纬度去分析:
为什么要有ANR机制以及ANR机制在Android程序中存在的必要性
那好,我们根据开发经验 大胆想象一下是否可以把App的代码宏观上分为两种类型:
一种是 UI操作(即时反馈)
UI操作(包括但不限于):
- 界面的渲染
- View的绑定,刷新
- 动画
一种是 业务逻辑(耗时操作)
业务逻辑相关(包括但不限于):
- 网络数据的收发和上传
- 数据库的CRUD操作
-
IO的读写
如图,现在我们做一个假设:假设我们的这些代码共用一个线程,会发生什么问题呢?
首先 默认情况下代码执行规则是从左至右,从上至下同步执行,那么如果有耗时操作,代码就会阻塞。反馈到界面上最直观的感受就是响应延迟卡顿,如果你代码书写顺序颠倒 控件拿不到数据,还会报出空指针等等的异常。用户体验不能用糟糕形容,简直就是毫无用户体验。
显然 谷歌工程师不会如此愚蠢。工程师们为了规避这个问题,这就要引入Android中一个重要概念叫做:异步
异步是目的,想实现这个目的就需要用到多线程,那么多线程状态下Android的代码又是如何去执行?
首先我们要知道多线程执行的本质是:CPU在多条线程之间做快速的切换,它是随机并发执行
我们的代码要想在这种环境下异步执行会面临以下问题:
- UI操作的同步问题
- UI操作(View)的不可预期性
解释一下这些问题,我们知道UI操作是即时反馈
如图 我们假设有三条线程,线程一给控件赋值,线程一的控件值依赖于线程二网络请求拿到的数据,线程二的控件赋值又依赖于线程三中数据库查询到的值,请问我如何确保多线程并发访问时一定先执行线程三 再执行线程二 最后执行线程一呢?换句话说我如何解决同步问题?
Ok 肯定有小伙伴说 想那么复杂干嘛,加个锁不就完事儿了嘛...加锁是能保证线程安全,互斥,这当然没问题, 重点是,你如何确定加锁的位置?如果盲目加锁还可能会让控件(View)处于一个不可预期的状态,一不小心还可能造成多线程死锁的现象。
既然无法用代码的方式去解决这个问题,那我们就做减法,从书写方式上寻找突破点 既然不能加锁也不能让线程同步,那我们就索性把UI操作单拎出来,你们玩你们的,事成之后告诉我一个结果就行,控件不就是想要这么一个结果渲染界面嘛。
按着这个思路,其实这个问题很好解决 我们只需要在编写代码的时候遵循一个规则就可以完全规避Android中UI操作在多线程异步之间的冲突。
这个规则就是:只要是涉及到UI操作的代码,我们都单独的放到一个线程中,这个存放UI的线程要想满足UI控件的正常工作必须要满足:线程非安全,不能加锁,不能阻塞!
到此 这个存放UI的线程想必大家都知道了,对!没错!就是我们口口相传大名鼎鼎的:"主线程"又称之为UI线程。
这也就回答了开头"为什么不能在子线程中更新UI?为什么主线程不能做耗时操作"的迷之疑问...但似乎有相当一部分开发者只是机械型的遵循这一规则。
谷歌为了最大化提升用户体验 让开发者都遵守这个规则,保证规则的良性循环 ,ANR机制就诞生了,Android在主线程之间会设置一个5s——20s不等的时间阀值(产生ANR的上下文不同,超时时间也会不同),如果主线程中的程序运行/阻塞的时间超出了这个阀值,就会抛出ANR异常,如下图
所以这也就是Android为什么会有ANR这么一个机制,ANR的必要性也由此显现。
掌握原理及其设计思想之后 写起代码来才能举一反三更加得心应手,不会再为某一个莫名其妙的bug头疼半天,但不得不说 谷歌工程师代码设计的还是非常之精妙的,其中的思路非常宝贵值得我们借鉴。
但以上仅是个人对ANR机制狭义上的理解(也算是自问自答式的圈地自嗨???哈哈),旨在集思广益交流分享,如有不同观点非常欢迎与鄙人探讨学习,如果此文章对你或多或少有些启发,那就点个爱心加关注吧~ 后续会尽可能的分享更多高质量的文章于大家交流学习。