likes
comments
collection
share

并发编程 · 基础篇(中) · 三大分析法分析Handler

作者站长头像
站长
· 阅读数 16

Tips: 关注微信公众号小木箱成长营,回复Handler可获得Handler免费思维导图

一、序言

Hello,我是小木箱,欢迎来到小木箱成长营并发编程系列教程,今天将分享并发编程 · 基础篇(中) · 三大分析法分析Handler

三大分析法分析Handler主要分为三部分,第一部分是5W2H分析Handler,第二部分是MECE分析Handler,第三部分是SCQA分析Handler。

首先,5W2H分析Handler针对Handler提出了六个高价值问题。

然后,MECE分析Handler分为两部分,第一部分是Handler常见API,第二部分是Handler消息机制。

最后,SCQA分析Handler,还原企业面试现场,利用B站平台,以SCQA形式解答33个Handler高频面试题。

其中,Handler消息机制主要分为三部分,第一部分是Handler发送Message,第二部分是Looper轮询读取,第三部分是Handler回收Message。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

如果学完小木箱的三大分析法分析Handler,那么任何人都能通过android Handler相关技术面试。

二、5W2H分析Handler

5W2H又叫七何分析法,5W2H是二战的时候,美国陆军兵器修理部发明的思维方法论,便于启发性的理解深水区知识。

5W2H是What、Who、Why、Where、When、How much、How首字母缩写之和,广泛用于企业技术管理、头脑风暴等。

今天小木箱尝试用5W2H分析法分析Handler。

2.1 What: 怎么定义Handler?

Android Handler is mainly used to update the main thread from background thread or other than main thread.

Google官方文档是这么定义Handler的: 在android中,Handler主要用于从后台线程向主线程同步Message。

总结性的说: android为了更好进行线程通讯,Handler基于双向链表数据结构的 "优先级消息队列",提供了一套线程通讯机制。

2.2 Why: 为什么用Handler?

为什么会有Handler存在呢?在Android中,线程分为两种,第一种是主线程,主线程主要用来创建和更新UI用途。

第二种是后台线程,后台线程主要用于处理耗时的操作,网络请求、文件读写和音视频播放等后台工作。

后台线程有效地提高应用程序性能,保证UI流程度。

Android硬件规定手机每隔16ms会刷新一次,Android为了不丢帧,主线程耗时不能超过16ms。

因此,Android不允许后台线程更新UI。

为了将Message从一个线程传递到另一个线程,实现线程间的通信,Handler承担了线程切换的职责。

除此之外,Handler还可以发送Message实现定时任务。

2.3 When: 何时使用Handler?

如果需要执行一个任务在指定的时间间隔或在特定的线程中执行任务时,那么我们考虑使用Handler机制。

比如: 领导要你实现一个Android消息轮询库。

2.4 Where: Android框架使用Handler的地方?

在Android框架中,HandlerThread 、IntentService 、EventBus 、RxJava、 DataBinding 和Dagger2都是基于Handler实现的。

2.5 How : 怎样使用Handler?

Handler使用非常简单,创建了一个Handler实例并重写了 handleMessage 方法 ,然后在适当的时机调用Handler的 send 或者 post 系列方法就可以了

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

2.6 How Much: Handler真的完美吗?

Handler有两个缺陷,第一个是不支持跨进程,第二个是造成代码臃肿。

因为Handler是基于内存的,而进程之间的内存是隔离的,所以Handler不支持跨进程通信。

同时,如果编写大量的if-else块来处理Message不同的情况,Handler会造成代码变得很臃肿。

三、MECE分析Handler

MECE全称Mutually Exclusive Collectively Exhaustive,中文意思是“相互独立,完全穷尽”。

对于一个重大意义话题,MECE能够做到不重、不遗漏的分类,而且MECE能够根据分类有效把握问题的核心。

接下来,小木箱按照API、消息机制、高级应用和设计缺陷分类分析Handler, 如果听完小木箱分析Handler,那么Handler不再是任何人面试的拦路虎。

3.1 Handler常见API

Handler常见API有四个,第一个是Message,第二个是MessageQueue,第三个是ThreadLocal,最后一个是Looper。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.1.1 Message

首先,我们来说说第一个API,Message,Message要说的东西不多。

按照MECE原则,Message内容主要分为三大类: Message消息定义、Message数据结构和Message创建方式

Message消息定义

首先,我们说说Message消息定义,Message是消息的载体,内部参数可以看如下源码:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Message数据结构

然后,我们说说Message数据结构,Message数据结构是单链表结构,Message会通过next持有了下一个Message引用。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Message创建方式

最后,我们说说Message创建方式,Message有两种,第一种创建方式是构造函数创建Message,第二种是通过Message.obtain方法创建Message

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

但是不建议使用构造函数创建Message,有两个原因,第一个原因是构造函数创建Message对象不能被重用,导致浪费内存。 第二个原因是构造函数创建Message破坏Handler "优先级消息队列" ,构造函数创建的Message不会被添加到Handler的 "优先级消息队列" 中,因为Handler没有地方接收Message。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

反之,通过Message.obtain方法创建Message会存储在消息池recycle中。

3.1.2 MessageQueue

说完Message,我们说一说第二个API,MessageQueue,按照MECE原则,Message内容主要分为三部分。

第一部分是MessageQueue定义,第二部分是MessageQueue流程,第三部分是MessageQueue思考。

MessageQueue定义

首先,我们来说说MessageQueue的定义,Handler的MessageQueue "优先级消息队列" 作用是存放Message对象,Handler的MessageQueue数据结构不是队列,Handler的MessageQueue是双向链表数据结构,为什么这么设计呢?因为Handler的MessageQueue可以从头部和尾部同时进行查找操作,查找速度比单链表更快。

思考: 不同线程的多个 Handler往MessageQueue中添加数据,那么MessageQueue是如何确保线程安全的?

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

如上图代码所示,MessageQueue是基于消息循环实现的,MessageQueue使用双重检查锁和wait/notify机制来确保线程安全。

当一个线程调用MessageQueue.enqueueMessage方法添加消息时,MessageQueue会先检查锁。

如果锁没有被占用,MessageQueue会获取锁,然后将消息添加到消息队列中,最后释放锁。

如果另一个线程正在使用锁,那么MessageQueue会阻塞,直到获得锁,然后才能继续添加消息。

另外,MessageQueue还使用wait/notify机制来通知另一个线程,当消息被添加到消息队列时,另一个线程将被唤醒。

MessageQueue流程

然后,我们来说说MessageQueue流程。

MessageQueue流程我们可以看看如下流程图:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

实例化Handler的时候。

首先,Handler会创建一个MessageQueue。

然后,MessageQueue会调用Looper的 loop方法。

接着,启动一个消息循环来处理Message。

最后,如果没有Message,那么主线程会释放CPU然后回调native的Looper睡眠方法进入休眠状态。

如果有Message,那么会回调native的唤醒方法唤醒CPU。

MessageQueue思考

最后,我们说说对MessageQueue思考。

思考: MessageQueue.next方法 会因为发现了延迟Message,而进行阻塞。那么为什么后面加入的非延迟Message没有被阻塞呢

MessageQueue.next方法会获取 "优先级消息队列" (queue的next方法)中的下一条Message,如果发现存在延迟Message,会处理延迟Message,然后再处理非延迟Message。

消息队列处理Message是有优先级的,因此,加入的非延迟Message不会被阻塞。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.1.3 ThreadLocal

说完MessageQueue,我们说一说第三个API,ThreadLocal,按照MECE原则,ThreadLocal内容主要分为三部分。

第一部分是ThreadLocal作用,第二部分是ThreadLocal原理,第三部分是ThreadLocal和Looper关系。

ThreadLocal作用

ThreadLocal是Java中一个用于线程内部存储数据的工具类,作用是隔离线程。

我们看一下如下测试用例:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

输出结果:

当前线程: main: true

当前线程: 小木箱: false

当前线程: 小石头: null

ThreadLocal原理

因为线程访问ThreadLocal时候,当前线程的ThreadLocalMap,会把ThreadLocal变量作为key,传进来的泛型作为value进行存储。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

因为线程隔离导致每个线程可以访问其线程局部变量,而其他线程无法访问该线程的局部变量。

也就是说,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

ThreadLocal和Looper关系

因为一个 Looper只能保证只有一个MessageQueue,在线程使用 Looper.prepare方法创建 loop的时候,Looper会从ThreadLocal中获取。

如果ThreadLocal里面有Looper就会抛出异常,那么保证一个线程只有一个 Looper或MessageQueue即可

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.1.4 Looper

说完ThreadLocal,我们说一说第四个API,Looper,Looper内容主要分为两部分。

第一部分是Looper定义,第二部分是Looper生命周期。

Looper定义

Looper可以实现线程之间的Message传递。

Handler的Looper接收从其他线程通过dispatchMessage发送Message,并将Message放入一个MessageQueue中,然后在当前线程中按顺序处理(handleMessage)Message。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Looper生命周期

按照MECE原则,Looper生命周期分为Looper创建、Looper启动和Looper终止。

Looper创建

Looper创建方式有两种,第一种是主线程ActivityThread创建Looper,第二种是自己创建Looper。

主线程ActivityThread创建Looper,使用的是prepareMainLooper方法,通过prepareMainLooper方法可以在任何地方获取到主线程的Looper,主线程的Looper不能退出。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

自己创建Looper,使用的是prepare方法,最终会调到prepare(boolean quitAllowed)方法,prepare(boolean quitAllowed)方法是private,外部不能直接调用,区别是主线程创建的Looper不能退出,而自己创建的可以退出。

Looper的内部维护了MessageQueue,初始化Looper,即初始了MessageQueue

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Looper启动

Looper启动的启动调用的是loop方法,prepareloop方法是配套使用的,两者必须成对存在,loop的流程如下:

首先,获取当前线程的Looper对象,没有则抛异常。

然后,进入一个死循环: 不断调用MessageQueue的next方法来获取Message。

最后,调用message的目标handler的dispatchMessage方法来处理Message。

Looper启动机制如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Looper的loop方法源码如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Looper终止

Loop常用的方法有两种,第一种是quit,第二种是quitSafely。

quit和quitSafely区别在于quit会直接退出Looper,而quitSafely首先设定一个mQuitting标记,然后把MessageQueue中的已有Message处理完毕,最后安全退出。

详细代码如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

quitquitSafely方法最终都调用了quit(boolean safe)方法,quit(boolean safe)方法先判断是否能退出,然后再执行退出逻辑。

如果mQuitting==true,那么这里会直接return掉。

mQuitting变量只有在quit方法,才会被重新赋值。

因此一旦looper退出,就无法正常运行looper。

当执行退出逻辑,CPU会唤醒MessageQueue,然后MessageQueue的next方法、Looper的loop方法伴随退出,导致线程终止,详细流程图如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2 Handler消息机制

Handler消息机制主要分为三个流程,第一个流程是Handler发送Message,第二个流程是Looper轮询读取,第三个流程是Handler回收Message

3.2.1 Handler发送Message

首先,我们说说第一个流程Handler发送Message,Handler发送Message的方式有两种,第一种是sendMessage,第二种是post

3.2.1 Handler_sendMessage & post

sendMessage直接发送Message实例对象,而post方法,发送的是一个Runnable,Runnable会被封装进一个Message,发送的本质上也是一个Message

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

sendMessage 或者 post等系列发送方法会调用到Handler的enqueueMessage方法,而Handler中的enqueueMessage方法最终调用到MessageQueue的enqueueMessage方法。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Handler发送Message整体流程图参考如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.2 MessageQueue_enqueueMessage

说完Handler的sendMessage & post方法,我们再说说sendMessage & post方法的底层实现enqueueMessage,MessageQueue enqueueMessage等待队列详细流程参考如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

MessageQueue的enqueueMessage等待队列源码分析如下,总共可以分为五个步骤:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

第一步,如果Message中的Handler为空,那么抛非法参数异常。

第二步,MessageQueue同步锁处理,如果当前线程已经消亡,那么抛非法参数异常并返回false。

第三步,对Message的when重新赋值,记录正确的时间。

第四步,将新Message插入链表,如果messageQueue是空或者正在等待下个延迟Message,那么需要CPU唤醒MessageQueue。

第五步,根据Message的when,找到在链表中插入位置进行插入,MessageQueue维护 "优先级消息队列" ,确保Message是升序的。

3.2.3 MessageQueue_next

Message存放到 "优先级消息队列" 之后,要对Message进行读取,Looper的Loop方法会从MessageQueue中循环读Message,loop方法调用了queue.next方法

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

next方法目的是获取MessageQueue中的一个Message,next方法有一个死循环,如果 "优先级消息队列" 中没有Message,那么next方法会一直阻塞。

如果 "优先级消息队列" 中有新Message到来,那么next方法会被唤醒,next方法会返回新的Message,并将Message从 "优先级消息队列" 中移除。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.2 Looper轮询读取

3.2.2.1 从Java层观测Handler

然后,我们再说说第二个流程Looper轮询读取,Looper的loop方法是一个for死循环,loop方法做了三件事。

第一件事是调用 MessageQueue的next方法取Message。

第二件事是通过Message的target,也就是Handler去dispatchMessage分发Message。

第三件事是handleMessage回收Message。

其中,MessageQueue的next方法也是一个死循环,首先会调用linux的epoll机制

其中,nextPollTimeoutMillis表示阻塞的时间,-1表示无限时间,直到有事件发生为止,0表示不阻塞

如果没有Message或者处理时间未到,next方法会阻塞,nativePollOnce函数的睡眠时间是Java层透传的

3.2.2.2 从native层观测Handler

核心实现在Pollinner类中,native有四种状态: 初始化、休眠、唤醒和消亡。

我们着重说一下休眠和唤醒。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

先说说休眠状态,如果监听文件描述符没有发生1和0读写事件,那么当前线程会在epoll wait中进入休眠状态。

然后说说唤醒状态,如果当前线程有新的Message需要处理,那么CPU被唤醒,然后延续之前调用路径,回溯到Java层。

3.2.3 Handler回收Message

3.2.2.1 Looper_loop

Looper和线程是一对一的关系,Looper调用的dispatchMessage方法会运行在不同的线程,所以Message的处理就会被切换到Looper所在线程。

Looper的loop方法调用了msg.target.dispatchMessage(msg) 方法,msg.target 就是发送该Message的 Handler,这样Message最终会回调到Handler的dispatchMessage方法。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.3.2 Looper_dispatchMessage

msg.target.dispatchMessage(msg) 方法,msg.target 就是发送该Message的 Handler,Message最终会回调到Handler的dispatchMessage方法中。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.3.3 Looper_handleCallback

如果Message的callback不为null就通过handleCallBack来处理Message,Message的callback是一个Runnable对象,实际上是Handler的post系列方法传递的Runnable参数。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.3.4 Callback

如果mCallback不为null调用mCallback的handleMessage处理Message,Callback是个接口。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.3.5 new Handler(callback)

通过Callback可以采用如下方式来创建Handler。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.3.6 Handler_handleMessage

最后,调用Handler的handleMessage方法来处理Message

Handler回收Message流程总结图如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.2.4 Handler机制总结

Handler机制主要分为三个流程,第一个流程是Handler发送Message,第二个流程是Looper轮询读取,第三个流程是Handler回收Message。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

首先,我们说说第一个流程Handler发送Message,Handler发送Message有两种方式,第一种方式是Handler的sendMessage,第二种方式是Handler的post发送Message。

Handler的sendMessage可以发送定时和不定时Message两种。

Handler的post本质也是sendMessage形式,只不过传入的Runnable参数包装成Message的Callback。

Message对象传给MessageQueue的enqueueMessage进行优先级入队,enqueueMessage作用是维护一个Message链表,enqueueMessage根据Message的when时间正序排序,延迟Message是延时时间+当前时间进行精准计算。

然后,就第二个流程Looper轮询读取,Looper的loop方法是一个for死循环,loop方法做了三件事。

第一件事是调用 MessageQueue的next方法取Message。

第二件事是通过Message的target,也就是Handler去dispatchMessage分发Message。

第三件事是handleMessage处理Message。

其中,MessageQueue的next方法也是一个死循环,首先会调用linux的epoll机制

如果没有Message或者处理时间未到,next方法会阻塞,nativePollOnce函数的睡眠时间是Java层透传的,核心实现在Pollinner类中,native有两种状态: 休眠和唤醒。

先说说休眠状态,如果监听文件描述符没有发生1和0读写事件,那么当前线程会在epoll wait中进入休眠状态。

然后说说唤醒状态,如果当前线程有新的Message需要处理,那么CPU被唤醒,然后延续之前调用路径,回溯到Java层。

最后,对新Message进行外理。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

看完源码,有两点可以深度反思,第一点是获取Message只能通过Message的obtain获取,不要直接new,因为 Message.obtain会从缓存里面去取。

第二点是可以使用IdleHandler在 "优先级消息队列" 空闲时提前做一些操作。

实际开发中,点击消息中心图标,会跳到h5页面。

在url后面加密拼接uid和phoneNumber,加密操作是同步的,所以可以通过IdleHandler提前做这一操作,并且返回 false,表示只做一次。

3.3 Handler高级应用

3.3.1 Handler同步屏障机制

Handler发送的Message会存放到MessageQueue中,MessageQueue维护了一个优先级队列。

优先级队列存储了单链表的Message按照时间大小进行升序,Looper则按顺序,每次从优先级队列中取出一个Message进行分发,处理完一个就处理下一个,有没有办法让指定Message优先消费?

有! 同步屏障机制! 下面又得搬出5W2H分析法分析同步屏障机制。

3.3.1.1 What: 同步屏障机制定义

Android的Handler同步屏障机制是一种用来防止多线程间数据竞争的机制,同步屏障机制可以保证多个线程之间的数据同步。

当一个线程的数据在另一个线程中被修改时,Handler会发出一个信号,以便其他线程可以检查这些数据。

如果数据发生变化,那么Handler会把这些变化发送给其他线程,以确保所有线程都拥有最新的数据。

3.3.1.2 Who: 同步屏障机制原理分析

MessageQueue_next

如果当前msg不为空并且msg.target的Handler对象为空,那么执行同步屏障并且在消息队列中查询下一个异步消息,循环遍历查询下一个异步消息,通过循环体执行相关链表的工作。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

在这里我们提出了第一个问题。

发送消息的一系列方法会给msg.target对象赋值,msg.target什么时候赋值为空的呢?

其实target属性为空的Message就是同步屏障,同步屏障可以使得异步Message优先被处理,通过MessageQueue的postSyncBarrier可以添加一个同步屏障。

在异步消息处理完之后,同步屏障并不会被移除,需要我们手动移除。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

如果不移除同步屏障,那么同步屏障会一直在那里,同步消息就永远无法被执行。下面我们跟一下同步屏障源码:

MessageQueue_postSyncBarrier

post和get方法最终都会走MessageQueue的postSyncBarrier的方法,MessageQueue的postSyncBarrier的方法中没有给msg.target对象赋值。

但是postSyncBarrier方法 target属性为空的同步屏障Message,同步屏障Message是特殊的Message,不被消费,作为特殊标识短暂的存储在MessageQueue中。

遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

MessageQueue_enqueueMessage

在这里我们提出了第二个问题。

如何将把同步屏障消息变成异步消息?

有两种方式:第一种是在Handler的构造方法中,传入async为true,那么这个时候发送的Message就都是异步的的消息,第二种是给Message通过setAsynchronous 方法标志为异步。

第一种通过msg.setAsynchronous方法设置为true,可以把一个同步屏障消息变成异步消息

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

Handler(Looper looper, Callback callback, boolean async)

第二种如果满足Handler的mAsynchronous属性为true,那么同步屏障消息会在Handler的两个构造方法中重新赋值

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

因此,同步屏障消息设置为异步消息。

3.3.1.3 How: 同步屏障机制测试用例

假设当前Message CrazCodingBoy分发给Handler后执行了耗时操作,那么本该到点消费的Message CrazCodingBoy被阻塞了,Message CrazCodingBoy在其他Message sendMessage方法之后,等其他Message消费完再消费当前Message CrazCodingBoy,详细测试用例参考如下:

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

输出结果:

count: 10

3.3.1.4 When: 同步屏障机制使用场景

ViewRootImpl快速响应 UI 刷新

在进行UI绘制的时候,以下是ViewRootImpl中执行UI绘制的方法使用到了同步屏障

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

绘制消息放入优先级消息队列之前,首先先放入了一个同步屏障,然后在发送异步绘制消息,最后使得界面绘制的消息会比其他消息优先执行,避免了因为 MessageQueue 中消息太多导致绘制消息被阻塞导致画面卡顿,当绘制完成后,就会将同步屏障移除

3.3.1.5 How Much: 同步屏障机制应用价值

Handler同步屏障机制还可以用于多线程编程中的并发编程,帮助开发者实现多线程之间的同步,提高程序的性能。

3.3.1.6 Why: 同步屏障机制使用原因

为了保证消息的顺序性和正确性,避免消息的乱序处理和重复处理。

同步屏障机制能够保证在消息到达Handler之前,所有的消息都已经处理完毕,而不会出现消息重复处理的情况。

这样,就能够保证每一条消息只处理一次,从而保证Handler处理消息的正确性和顺序性。

3.3.2 IdleHandler应用

IdleHandler定义

IdleHandler是一种Android中的回调机制,IdleHandler可以让Android开发应用处于空闲状态时执行特定的操作。

IdleHandler可以用来执行一些定期的任务。

IdleHandler源码分析

Android IdleHandler使用 Handler.post(Runnable) 方法来将一个Runnable对象放入MessageQueue的next优先级消息队列中。

当应用程序空闲时,Runnable 对象就会被执行。

当任务完成后,可以使用 Handler.removeCallbacks(Runnable)来从MessageQueue的next优先级消息队列中移除Runnable对象。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

IdleHandler测试用例

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

输出结果:

queueIdle: 空闲时做一些轻量级别耗时操作

IdleHandler应用场景

最典型的两个案例是: IdleHandler可以获取View宽高和网络连接检测

3.3.3 Looper活学活用

Looper高级使用有两个,第一个是

第一个是通过LoopergetMainLooper方法获取主线程Looper,可以判断当前线程是否在主线程

第二个是将 Runnable post到主线程执行

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.3.4 HandlerThread

3.4 Handler设计缺陷

3.4.1 Crash现场还原

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

空指针异常是原因多线程并发,当主线程执行到sendEnptyMessage时,子线程的Handler没有创建。

因此,我们获取到Handler,再去消费Message就可以了,测试用例我们让主线程休眠再执行,可以解决Crash问题。

子线程使用Handler,有两点需要注意

第一点是必须调用 Looper.prepare()创建当前线程的 Looper,并调用Looper.loop()开启消息循环

第二点是必须在使用结束后调用Looper的quit方法退出当前线程,否则,如果不退出当前线程,线程的Looper处理完所有的消息后,会处于阻塞状态,因为线程是重量级的,如果一直阻塞,会影响应用性能。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.4.2 后台线程弹Toast

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

输出结果:

1: "Can't toast on a thread that has not called Looper.prepare()"

2: 正常运行

分析源码我们发现,在后台线程弹吐司时候,必须初始化后台线程的Looper,否则会报异常

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

3.4.3 后台线程弹Dialog

同理在后台线程弹对话框时候,必须初始化后台线程的Looper,不然也会报异常,因为创建Handler,需要先创建Looper并开启消息循环,主线程默认创建了并开启消息循环,而后台线程并没有。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

那么主线程是如何创建Looper的呢?我们分析一下源码

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

ActivityThread通过ApplicationThread和AMS进行进程间通信的方式完成ActivityThread的请求后,会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程去执行

四、SCQA分析Handler

SCQA模型是麦肯锡芭芭拉·明托在《金字塔原理》中提出的“结构化表达”模板工具,常用于方案、文案、广告、演讲、讲故事、写作和面试等。

SCQA是Situation、Complication、Question和Answer4个英文单词简称。分别是:

  • S(Situation)背景—由⼤家都熟悉的情景、事实引⼊

  • C(Complication)代表冲突—指的是实际情况和我们的要求有冲突⼊

  • Q(Question)代表问题—怎么办

  • A(Answer)代表答案—我们的解决⽅案

我们在面试的时候一般有场景题、矛盾题和定义题。分别对应着SCQA、CQA和QA三种模型。小木箱总结了33个高频Handler面试题,答案后期将同步到B站,感兴趣可以提前关注一下。

并发编程 ·  基础篇(中) ·  三大分析法分析Handler

五、结语

三大分析法分析Handler主要分为三部分,第一部分是5W2H分析Handler,第二部分是MECE分析Handler,第三部分是Handler设计缺陷,第四部分是SCQA分析Handler。

首先,5W2H分析Handler针对Handler提出了六个高价值问题。

然后,MECE分析Handler分为两部分,第一部分是Handler常见API、第二部分是Handler消息机制、第三部分是Handler高级应用和Handler设计缺陷。

最后,SCQA视频分享了33个Handler高频面试题。

其中,Handler消息机制主要分为三部分,第一部分是Handler发送Message、第二部分是Looper轮询读取和第三部分是Handler回收Message。

本文写作目的是用最平滑的语言,帮助大家从原理到实现学习Handler。

企业面试中,Handler算是送分题,作为一名高级Android开发,连Handler都答不会,基本不太可能通过面试,当然怎样利用Handler完善Android容灾体系,文章没有过多的讲解,感兴趣可以听一下第12期字节跳动技术沙龙录播课。

下一节,小木箱将带大家学习并发编程 · 基础篇(下) · android线程池那些事。

我是小木箱,如果大家对我的文章感兴趣,那么欢迎关注小木箱的公众号小木箱成长营。小木箱成长营,一个专注移动端分享的互联网成长社区。