Handler源码解析

587 阅读10分钟

Handler的意义

Handler不仅Android独创的,在Spring里也有Handler,web开发中的ajax也跟Handler的思想差不多。

Handler使得Android开发难度大大降低,在android中几乎看不到多线程死锁问题,是因为Handler为我们解决的了线程间的通信。

其实Handler不仅仅是为了线程间的通信。线程间的通信可以说只是Handler提供的一个辅助功能。
Handler的本质是 - 消息管理机制,他管理的安卓上所有的消息(事物),而android里的一切都会封装成消息(事物)
,(如点击屏幕,显示页面,刷新屏幕等)。

或者说 - Android中所有的代码都是跑在Handler上的。

线程间的通信

android中线程间的通信主要就是Handler,Handler通信实现的方案实际上是内存共享的方案
Message其实就是一块内存,而内存是没有属于线程之说的,内存在线程间是共享的,通过A线程将Message存到一个MessageQueue的容器中,B线程在这个容器里,将Message(内存)取出来,实现线程间的通信。

线程间通信在android中还有EventBus/RxBus等。
在java里主要是wait/notify。而这部分在Android里的handler已经将需要这部分功能进行了Linux层的封装。 所以不需要我们自己wait/notify去实现线程间的通信。

从开天辟地到app启动

从手机开机

(有一回面试被问过,手机启动经历了什么事。)
Android系统会在开机时由Native层创建第一个进程init进程,随后init进程会解析一个叫init.rc的本地文件创建出Zygote进程
Zygote(受精卵)的职责就是孵化进程(一切应用进程均由Zygote进程孵化)。当孵化出的第一个进程SystemServer进程后退居幕后,通过Socket静等创建进程的呼唤。

SystemServer进程是Android系统的核心之一,大部分Android提供的服务都在该进程中,SystemServer中运行的进程公共有六十多种,主要包括:ActivityManagerService(AMS),WindowManagerService(WMS),PackagManagerService(PMS)等;这些系统服务都是以一个线程的方式存在Systemserver进程中。

开机 -> init进程 -> Zygote进程 -> SystemServer进程 ->(AMS)/ (WMS)/(PMS)...

点击桌面app图标

1.手机的桌面其实也是一个app,叫lancher,所以点击app图标就等于在activity中点击了一个button。
2.如果对应的应用进程还没有创建则会通过Binder IPC通知到AMS创建应用进程
3.AMS会发送消息通知Zygote创建应用进程
4.Zygote会创建应用,给他分配一个JVM
5.java程序要启动,就得启动一个main()函数,而这个main()函数就是ActivityThread.main()

点击桌面图标 (Binder IPC)-> AMS -> Zygote -> JVM -> ActivityThread.main()

补充:为什么要给每个应用分配一个jvm,因为这样每个app都是独立的,一个崩了不会影响到另一个,好管理,隔离等。

app启动 - ActivityThread.main()

看源码可知,main里面就是Looper.prepareMainLooper();、 Looper.loop();
就是创建Looper,然后去启动它。
所以Android中的所有代码都是跑在Handler上的,包括系统的,自己写的

image.png

Handler的结构

Handler的结构图大概是这样的。
每一个Thread里都有一个ThreadLocalMap内部的变量,
ThreadLocal里存了一个Looper,
Looper里有一个唯一的MessageQueue,
MessageQueue是一个单链表实现的优先级队列,里面存了Message链表 image.png

Handler的原理

像一个传送带送货物。
handler.sendMessage(message);不断的往传送带上放message
只要给Looper通上电(Looper.loop),传送带就启动了
在传送带的末端,handler.dispatchMessage()不断的取message
image.png

Handler源码解析

Handler的使用

发送消息:在线程中handler.sendMessage(message);
接收消息:new Handler,然后在匿名内部类里,重写方法handleMessage,接收message。

image.png

在接收的地方,new Handler,其实是要将Handler给绑定到Looper上的,在主线程中能直接用是因为ActivityThread.main()里给我们做了。如果要子线程中new Handler接收消息,那么需要在子线程中 Looper.prepare()、 Looper.loop();

image.png

从sendMessage发送消息开始

现在已知情况:
发送消息:handler -> sendMessage() ->....
接收消息: ... -> handler.handleMessage()

  1. 发送消息的方法有很多,sendMessage/post等等,但是最终都会走到sendMessageAtTime

image.png

  1. handler.enqueueMessage又会调到messasgeQueue.enqueueMessage。

image.png

  1. 重点!!!messasgeQueue.enqueueMessage这个方式是往消息队列里放消息,也就是真正发送消息的地方

image.png

image.png

便于理解:

消息队列 - 单链表实现的优先级队列

messasgeQueue数据结构: 单链表实现的优先级队列

消息队列是这样的结构:
image.png

MessageQueue里存了一个Message

image.png

Message里有一个next是Message类型的

image.png

所以总结:
MessageQueue里有一个Message(mMessages),而mMessages中的next里存有一个Message1, Message1里有一个next,next里存有一个Message2...

所以这就是(单链表实现的队列)
Messge-》next-》Message -》Next(Message)...

优先级
所谓的优先级其实是Message执行的时间,每个Message里都存有Message应该执行的时间when。
创建Message的时候可以设置执行时间,然后插入队列的时候,会根据执行时间,按顺序插入到消息队列里。

image.png

image.png

这里的代码就是之前的for(;;)
如果下个节点为空(到尾巴了) 或者 下个节点的时间>我的时间,break;(将msg插入到这个位置)

image.png

接收消息handleMessage

现在已知情况:
发送消息:handler -> sendMessage() -> messasgeQueue.enqueueMessage
接收消息: ... -> handler.handleMessage()

  1. 接收消息的起点在Looper.loop,给传送带通上电,无限循环取消息

image.png

image.png

image.png

  1. 重点!!!messasgeQueue.next()对应前面messasgeQueue.enqueueMessage存消息,这个是在消息队列里取出消息

image.png

image.png

image.png

补充:
所以要退出looper死循环,停止handler的唯一方法就是msg=null
image.png

而让msg=null的唯一方法是mQuitting=true
image.png

而mQuitting=true的唯一方法是messasgeQueue.quit
image.png

  1. handler.dispatchMessage()发送msg

在Looper.loop里发送消息的代码msg.target.dispatchMessage(msg);
image.png

而这个msg.target就是Handler image.png

在 handler.sendMessage(message);的时候最终会调到handler.enqueueMessage,而在这里
msg.target = this;将自己给赋值给msg,所以每一个message都有一个handler的引用,这样又会引发内存泄露,后面讲

image.png

  1. dispatchMessage里都会调到handleMessage,也就是自己写的实现

image.png

总结调用流程

发送消息:handler -> sendMessage() -> messasgeQueue.enqueueMessage
接收消息: looper.loop()-> messasgeQueue.next()-> handler.dispatchMessage()- -> handler.handleMessage()

各部分详解

Message

Message可以看成是一块内存。handler机制通过对这块内存的共享实现了线程间的通信。

Message是一个单链表实现的优先级队列,Message里存了一个Message,里存了一个Message,这样实现的链表。而优先级是由Message的先后顺序决定的,而决定其先后顺序的是Message里存的一个long when代表的是Message执行的时刻,插入的时候根据when来前后插入。

Message的创建

  1. 直接new,但是无限new的话会让内存爆炸
  2. Message.obtain()建议使用

image.png

image.png

缓存的池子:msg缓存的池子是当msg执行完事需要回收的时候,将数据清空然后放到缓存池子里,没有去销毁。
这种操作在Android中其实很常见,比如RecyclerView四级缓存的缓存池,也是清空itemView然后复用,不是销毁在重新new。Glide等等都是这样用的。
好处:JVM知识:jvm使用的是标记回收算法,如果一直创建、回收,这样会使得内存碎片化很严重,完整的长的内存越来越少,造成频繁GC、内存抖动。

Message的销毁
image.png

这里的缓存池跟Message next是一样的操作,只不过池子里的Message是空的。

image.png

MessageQueue

虽然说handler机制是永动机,但是其实他还是会有阻塞休眠的时候。

比如当.next取不到消息的时候,或者执行时间还没到的时候
image.png

image.png

quit
停下永动机的唯一方法,上面说了

同步屏障

handler机制不仅给我们用的,整个系统都用。而Message链表都是在那排队的,当遇到十万火急的事件需要处理怎么办,就得插队执行,就像救护车在路上,别的车都得让出位置。
这就是同步屏障。

image.png
具体代码: image.png 设置同步屏障
image.png

系统一些紧急的事件会先跑。比如刷新UI,60赫兹的屏幕刷新频率,1秒钟刷新60次,也就是1/60=16.67ms。

Looper

初始化

为了保证一个线程只能有一个Looper,所以Looper不能随便new,所以其构造函数是私有的

image.png

只能通过Looper.prepare才能初始化Looper

image.png

因为MessageQueue是final的,只能赋值一次

image.png

ThreadLocal

可以看到Looper其实是存在ThreadLocal里的,判断有没有是sThreadLocal.get()

image.png

而这个sThreadLocal是 static final的,也就是每个线程有且仅有一个,并且是赋值以后就不能更改的。

image.png

ThreadLocal的详解请看之前的文章:juejin.cn/post/695650…

简单的说ThreadLocal里有一个ThreadLocalMap
ThreadLocalMap里有一个内部类Entry
Entry数组存了<key,value>的键值对
而这个key就是ThreadLocal,value就是存的值,也就是looper

image.png

image.png

每个Thread里都有一个全局变量ThreadLocalMap,线程的用来存上下文变量和一些别的值(比如现在的looper)

image.png

所以唯一的sThreadLocal造就了唯一的Looper。(prepare里sThreadLocal.get() != null就抛异常,空才sThreadLocal.set(new Looper(quitAllowed)))。唯一的Looper造就了唯一的MessageQueue(在私有构造函数Looper里new,MessageQueue的final)多方面原因造就了 一个线程只有一个Looper、MessageQueue

面试题

1:一个线程有几个 Handler?

无数个,随便new,但是Handler机制只有一个

2:一个线程有几个 Looper?如何保证?

一个looper,看上面looper初始化

3:Handler 内存泄漏原因? 为什么其他的内部类没有说过有这个问题?

Handler new的时候标黄 image.png

翻译

image.png

因为这样直接new出来的是匿名内部类,而内部类如果要访问外部的对象、方法。会自动带上this.

image.png

而由于Handler与Activity生命周期不一样,所以会导致内存泄露。

生命周期不一样的原因:message可以设置执行的时间的,如设置20分钟后执行。
而在handler.sendMessage();的时候会将handler给msg持有

image.png

也就是message 持有-> handler 持有-> Activity.this 所以会内存泄露

解决:
1.将handler写成静态内部类的形式,因为static内部类不会持有外部引用;
2.使用弱引用包裹外部对象;比如要更新textView.settext();用WeakReference.activity.tv.setText();

4:为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?

放在
Looper.prepare(); -> threadLocal.set(new Looper)
获取Looper, Looper.myLooper(); -> threadLocal.get()
将Looper与Handler绑定 new Handler(Looper);
Looper.loop(); -> 启动无限循环
中间new,看上面。最好是直接用 HandlerThread

5:子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

必须MessageQueue.quit,见上文

6:既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?

锁,MessageQueue里添取消息都加锁

7:我们使用 Message 时应该如何创建它?

Message.obtain()见上文

8:Looper死循环为什么不会导致应用ANR

这其实是一个无关的话题。
ANR指应用无响应,比如input事件在5S没处理,或者前台Broadcast10s未处理完成等都会导致ANR。
子线程的Handler死循环会导致CPU空跑,也会卡死。但不会ANR 甚至连ANR闪退的消息都是由Handler传递的。