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

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

image.png

小木箱成长营并发编程系列教程(排期中):

并发编程 · 基础篇(上) · android线程那些事

并发编程 · 基础篇(下) · 三大分析法分析线程池

并发编程 · 提高篇(上) · Java并发关键字那些事

并发编程 · 提高篇(下) · Java锁安全性那些事

并发编程 · 高级篇(上) · Java内存模型那些事

并发编程 · 高级篇(下) · Java并发BATJ面试之谈

并发编程 · 实战篇 · android下载器实现

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。

image.png

如果学完小木箱的三大分析法分析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 系列方法就可以了

image.png

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。

image.png

3.1.1 Message

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

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

Message消息定义

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

image.png

Message数据结构

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

image.png

Message创建方式

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

image.png

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

image.png

反之,通过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是如何确保线程安全的?

image.png

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

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

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

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

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

MessageQueue流程

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

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

image.png

实例化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不会被阻塞。

image.png

3.1.3 ThreadLocal

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

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

ThreadLocal作用

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

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

image.png

输出结果:

当前线程: main: true

当前线程: 小木箱: false

当前线程: 小石头: null

ThreadLocal原理

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

image.png

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

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

image.png

ThreadLocal和Looper关系

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

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

image.png

3.1.4 Looper

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

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

Looper定义

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

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

image.png

Looper生命周期

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

Looper创建

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

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

image.png

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

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

image.png

Looper启动

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

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

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

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

Looper启动机制如下:

image.png

Looper的loop方法源码如下:

image.png

Looper终止

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

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

详细代码如下:

image.png

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

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

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

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

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

image.png

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

image.png

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

image.png

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

image.png

3.2.2 MessageQueue_enqueueMessage

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

image.png

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

image.png

第一步,如果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方法

image.png

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

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

image.png

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有四种状态: 初始化、休眠、唤醒和消亡。

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

image.png

image.png

先说说休眠状态,如果监听文件描述符没有发生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方法。

image.png

3.2.3.2 Looper_dispatchMessage

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

image.png

3.2.3.3 Looper_handleCallback

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

image.png

3.2.3.4 Callback

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

image.png

3.2.3.5 newHandler(callback)

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

image.png

3.2.3.6 Handler_handleMessage

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

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

image.png

3.2.4 Handler机制总结

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

image.png

首先,我们说说第一个流程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进行外理。

image.png

看完源码,有两点可以深度反思,第一点是获取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对象为空,那么执行同步屏障并且在消息队列中查询下一个异步消息,循环遍历查询下一个异步消息,通过循环体执行相关链表的工作。

image.png

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

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

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

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

image.png

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

MessageQueue_postSyncBarrier

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

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

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

image.png

MessageQueue_enqueueMessage

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

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

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

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

image.png

Handler(Looper looper, Callback callback, boolean async)

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

image.png

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

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

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

image.png

输出结果:

count: 10

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

ViewRootImpl快速响应UI刷新

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

image.png

绘制消息放入优先级消息队列之前,首先先放入了一个同步屏障,然后在发送异步绘制消息,最后使得界面绘制的消息会比其他消息优先执行,避免了因为 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对象。

image.png

IdleHandler测试用例

image.png

输出结果:

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

IdleHandler应用场景

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

3.3.3 Looper活学活用

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

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

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

image.png

3.3.4 HandlerThread

参考小木箱成长营的并发编程 · 基础篇(上) · android线程那些事#2.4.1 HandlerThread

3.4 Handler设计缺陷

3.4.1 Crash现场还原

image.png

image.png

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

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

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

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

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

实际开发过程中,我们可以搭建一套容灾体系,使用并发编程 · 基础篇(上) · android线程那些事#4.5 UncaughtException兜底方案全局捕获Handler触发的非法参数异常和空指针异常。设计流程图如下:

image.png

3.4.2 后台线程弹Toast

image.png

输出结果:

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

2: 正常运行

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

image.png

3.4.3 后台线程弹Dialog

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

image.png

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

image.png

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站,感兴趣可以提前关注一下。

image.png

五、结语

三大分析法分析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线程池那些事。

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

参考资料

juejin.cn/post/693260…

juejin.cn/post/692408…


相关文章
|
6月前
|
算法 Java 调度
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
73 0
|
4月前
|
数据采集 存储 开发者
构建你的第一个Python网络爬虫:从理论到实践
【8月更文挑战第31天】在数字时代的浪潮中,数据成为了新的石油。本文将引导初学者通过Python编程语言搭建一个基础的网络爬虫,从互联网的海洋中提取有价值的信息。文章不仅会介绍网络爬虫的工作原理和应用场景,还会通过实际代码示例展示如何实现一个简单的爬虫项目。无论你是编程新手还是有一定基础的开发者,都能通过这篇文章获得宝贵的实践经验和技术洞见。
|
6月前
|
Java
JAVA多线程深度解析:线程的创建之路,你准备好了吗?
【6月更文挑战第19天】Java多线程编程提升效率,通过继承Thread或实现Runnable接口创建线程。Thread类直接继承启动简单,但限制多继承;Runnable接口实现更灵活,允许类继承其他类。示例代码展示了两种创建线程的方法。面对挑战,掌握多线程,让程序高效运行。
28 1
|
6月前
|
安全 Java API
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
43 0
|
7月前
|
存储 XML JavaScript
Java入门高频考查基础知识5(扎实技术基础应变一切变化-45题4.2万字参考答案)
数据结构是指在计算机中组织和存储数据的方式,它建立了数据元素之间的关系,以及对这些关系施加的操作。在程序设计中,数据结构是一种非常重要的概念,它对于实现高效的数据存储、检索和操作起着关键性的作用。逻辑结构逻辑结构是数据对象中数据元素之间的逻辑关系,包括线性结构(如数组、链表)、树形结构(如二叉树、平衡树)以及图形结构等。物理结构:物理结构是数据的逻辑结构在计算机中的存储形式,包括顺序存储结构和链式存储结构等。数据操作数据操作是指在一组数据上定义的操作,包括插入、删除、查找、排序等。
106 0
|
设计模式 缓存 负载均衡
你kin你擦!阿里终于肯把内部高并发编程高阶笔记开源出来了
“高并发”三字是近几年开发圈子里热议的一个话题,可能程序员之间闲下来就会讨论所谓的“高并发经验”。值得注意的是即使你和高并发天天打交道,也不一定能获得高并发的经验,高并发只是一个结果,并不是过程。想要玩转高并发,基础最重要,大并发面前,靠得住的只有人,是人来根据具体的应用场景去解决具体的问题。
你kin你擦!阿里终于肯把内部高并发编程高阶笔记开源出来了
|
存储 Java API
并发编程 · 基础篇(下) · 三大分析法分析线程池(1)
并发编程 · 基础篇(下) · 三大分析法分析线程池
177 0
并发编程 · 基础篇(下) · 三大分析法分析线程池(1)
|
安全 Java 程序员
并发编程 · 基础篇(上) · android线程那些事(1)
并发编程 · 基础篇(上) · android线程那些事
121 0
并发编程 · 基础篇(上) · android线程那些事(1)
|
存储 缓存 监控
并发编程 · 基础篇(下) · 三大分析法分析线程池(2)
并发编程 · 基础篇(下) · 三大分析法分析线程池
354 0
|
存储 缓存 安全
并发编程 · 基础篇(上) · android线程那些事(2)
并发编程 · 基础篇(上) · android线程那些事
282 0