前言
架构学习,绕不开的一个就是Handler,我们带着问题一个个来深入探究一下handler。
问题一:为什么要设计Handler ?
首先我们从设计的角度去思考下,为什么要设计Handler呢?大家在平时用的最多的场景就是用子线程请求完网络后,然后用handler把数据发送到主线程更新UI显示。那问题来了,为什么一定要在主线程更新UI呢?这个问题涉及到UI的渲染的流程,这个后面再细细讨论,我们现在理解就是关于窗口的很多渲染操作系统都是在主线程做的,如果我们在子线程更新UI就会造成紊乱。既然这样在我们的app中有很多都会涉及到子线程的耗时操作,那么子线程和主线程的通信就成为了一个必须要解决的问题,那android就设计了handler来解决这个问题。(这个为什么要设计handler的问题还需要我们来细细探究下)
问题二: 如何设计子线程和主线程通信的主体架构?
那上面我们知道了需要设计Handler,那如果给我们这样的需求,设计一个主线程和子线程通信的框架,他有如下需求。
1.主线程和子线程可以互相发送消息
2.消息有即时消息和延迟消息
对于第一个需求我们肯定会想到用生产消费者,发送消息的是生产者,接收消息的是消费者,如果没有消息就阻塞在那里,那现在消费者是主线程他如果阻塞在那里,界面岂不是卡主了?
其实第一个需求里面还有一个要实现的是要每个线程都需要能做为生产者和消费者,因为他们可以相互发送和接收消息。
对于第二个需求我们首先想到的应该是队列把消息按照延迟的时间插入到我们的队列里面,会涉及到队列的排队操作。
下面我们就来对这些问题,进行思考,看看Handler究竟是怎么实现的。
系统Handler机制的实现
我们先来整体看下handler在系统源码里面的流程,然后再针对每个点去理解他的设计。那我们找到App启动后第一个启动的类ActivityThread从他开始我们一步步看下Handler机制的实现。
ActivityThread.class
public static void main(String[] args) {
....
Looper.prepareMainLooper();
...
Looper.loop();
}
上面是ActivityThread类中涉及到Looper的地方,省略了其他代码。下一步我们跟进去Looper里面
Looper.class:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//对应上面的prepare(false)
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// 对应上面 sMainLooper = myLooper();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//这个就是循环的去取Message里面的数据,这里会涉及到死循环阻塞的问题。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//msg.target就是handler 等会我们看handler源码
msg.target.dispatchMessage(msg);
...
}
}
//looper的构造方法,在prepare里面调用了,创建了MessageQueue对象
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
上面代码涉及到了开始我们思考的问题,如何保证这套handler的逻辑在每个线程都能用呢?系统是使用ThreadLocal的方式,为每一个线程创建一套自己的Looper,每一个线程每次调用prepare的时候就会new 一个looper放到ThreadLocal里面,然后调用Looper.loop()就会开始从ThreadLocal里取出当前线程的Looper然后取出MessageQueue去不断的取出messagequeue的数据。看上面源码可以知道,系统启动的时候创建了一个主线程的Looper,这也是为什么我们直接可以在主线程使用handler发送消息的原因,如果需要在子线程使用,那么久需要自己去prepare,loop 了。ThreadLocal是一个值得我们深究的内容,需要弄懂他的使用和设计看下这个https://www.jianshu.com/p/cc9d74533bf8
上面代码引出了我们上面思考的问题,怎么来设计他的阻塞机制?代码可以看到,queue.next是真正去实现阻塞的地方,那么这个代码就会在messagequeue里面实现了。看下messagequeue.next
MessageQueue.class
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
我们只留了里面一行代码,发现他的阻塞等待机制是用native实现的,next里面还有关于队列的出队操作把它弄成了一篇单独的文章。我们现在理解就是,上来就会阻塞掉,然后我们想的是肯定有地方去把这个阻塞唤醒,然后继续走下去,取出当前的message 返回给loop方法里面发送给handler,就实现了通信。
上面我们是从app启动的角度分析了handler的流程,到目前执行到了messagequeue的next方法,阻塞在这里等待消息了,那我们现在只有从handler角度分析了,handler sendMessage
Handler.class
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
//这里把当前handler赋值给了msg.target,看到这里我们能知道Looper.loop方法里面为什么最后是通过msg.target.dispatchMessage()把消息发送给了handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
...
return queue.enqueueMessage(msg, uptimeMillis);
}
//这就是msg.target.dispatchMessage执行的地方,我们平时都是在handleMessage里面处理消息
//从这个源码我们可以看出如果我们定义了msg.callback,就会在我们callback执行了
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
//构造方法,取出handler创建线程的Looper和MessageQueue
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
...
}
从上面可以了解到Handler在sendMessage的时候会把数据塞到Messagequeue队列里面。我们看下messageQueue的enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
...
if (needWake) {
nativeWake(mPtr);
}
return true;
}
关于消息入队的操作我们放一篇文章里面说,这里重点看下有一个nativeWake,那这个毫无疑问就是唤醒操作了,在上线分析MessageQueue的next操作里面,会有nativiePool阻塞,那这里只要添加数据就会唤醒next从而可以让消息被发送到handler的handleMessage里面。
总结
从上面我们已经了解了handler的整体设计机制了,回顾下:首先ActivityThread 会创建一个mainLooper,创建的时候同时创建MessageQueue。设置到ThreadLocal中,然后从ThreadLocal里面取出Looper开启loop循环,loop会不断的去MessageQueue里面通过next取消息,取到的消息会从msg.target也就是handler 执行dispatchMessage()方法,在next中如果队列没有消息会阻塞住。 我们在使用handler sendMessage的时候,会调用MessageQueue的enqueueMessage()方法,在此方法中不但会让Message入队,也会通过nativewake的方式唤醒next方法,这样消息就可以到达handler的handleMessage里面了。需要注意的是,主线程中Looper是系统创建的,子线程如果想使用必须得自己调用Looper.prepare() 以及Looper.loop() 就可以使用了。
那上面我们还有好几个关键的地方需要仔细分析一下
1.ThreadLocal的实现原理
2.MessageQueue对于入队和出队的操作,以及延迟消息的处理
3.关于阻塞和唤醒,都是通过native层来实现的,怎么实现不阻塞主线程也能等待呢?这个需要去了解native源码。
接下来我们就有三篇文章来一一分析其中的原理。
网友评论