前言:俗话说的好,万丈高楼平地起,学好数理化,走遍天下都不怕;只有先打好基础的状况下,才能够更深刻地去学习知识;今天带你们设计一下Handler的机制,会经过一种特别的的方式,来进行讲解,但愿能够对你有所帮助;android
1,首先提出一个疑问,为何面试官都很爱问Handler相关的知识?由于Handler机制是Android一个很是重要的通讯机制,不少框架的底层实现都是经过Handler来更新UI的;git
2,那么问题来了,Android在哪里使用Handler消息机制,为何要使用这个Handler?下面咱们一步步的来深刻了解,揭开Handler的真面目吧!github
1,在Android的世界,存在着无数的线程,其中有一个特殊的线程, 被赋予了特殊的使命,它就是传说中的主线程;为何说它特殊呢?由于它掌握着能够更新UI(视图)的权力;那么问题来了,其它的线程说我也要更新UI呢?总不能也给这个线程赋予更新UI的权力吧,若是每一个线程都去更新UI,很容易就乱套了,那怎么解决呢?很简单,交由主线程去更新就好了;那么怎么通知主线程去更新呢?请继续往下看;面试
2,Google工程师造了主线程以后,相应的也创造了一些工具给主线程使用,是什么工具呢?就是相似通信工具的Handler,没错,Handler就通信工具,至于通信工具用来作什么,很简单,就是用来发送消息️;当其它的线程须要更新UI的时候,只须要打个电话给主线程,把你要传递的消息(Message)告诉它(主线程)就好了;️bash
3,通信工具是怎么运做的呢?当通信工具发送消息的时候,会把消息传送到最近的通讯基站(MessageQueue),这时候还有一个角色出场了,就是通讯卫星(Looper),通讯卫星是一个无时无刻不在工做的辛勤劳动者,通讯卫星(Looper)的做用就是从通讯基站(MessageQueue)里面取出消息,而后发送给主线程的通信工具,这时候主线程家里的(callBack)接受到其它线程发来的消息后,更新UI;多线程
固然上面纯属我的举例,用于加深理解!框架
下面咱们来看看Handler的设计!工具
假如你Google工程师,你面临着一个难题,多个线程在一块儿工做,你们都有更新UI的需求,时不时的你更新一下UI,我再更新一下UI,这时候有可能会致使更新的东西被别的线程给覆盖了,这就是多个线程同时操做会出现的问题;那这个问题要怎么解决呢?oop
既然你们都有更新UI的需求,那为什么不统一管理,使用一个线程来更新UI便可,其它线程须要更新UI的时候,告诉那个能够更新UI的线程,让它来更新就行,这样就能够避免上面那种状况的出现;既然有了思路,那接下来要怎么实现呢?请继续往下看!post
先规定一个线程为主线程,赋予它能够更新UI的权力,而后再设计一个能够发送消息的通信工具,将其命名为Handler,Handler的职责就是发送消息,通知能够更新UI的线程,咱们须要更新UI了;那么设计出来的效果以下,里面有一个能够发送消息的方法enqueueMessage();
public class Handler {
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
}
复制代码
如今有了发送消息的工具,接下来还得设计另外一个工具用来接收消息;
在设计以前,有一个疑惑,若是有多个线程同时要操做更新UI的话,那确定是没办法同时操做;好比说:有好几我的要去售票处买火车票,可是售票处只有一个窗口,没办法作到同时给那么多人售票,这时候就可让人们进行排队,有序的购票,这样你们都能买到票,又不会出现混乱的状况;这里咱们也能够采用这种方法来解决问题;设计一个消息队列MessageQueue,让Handler发送的消息,在这里进行排队,设计以下:
public final class MessageQueue {
Message mMessages
// 存储消息的方法
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
Message p = mMessages;
if (p == null) {
// 将第一个消息添加进队列
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
}
// 将传进来的消息连接到上一个消息的后面,进行排队
prev.next = msg;
}
}
return true;
}
}
复制代码
消息队列(MessageQueue)里面主要设计了一个链表的结构,让进来的消息能够连接到上一个消息的后面,从而达到排队的目的,也就是enqueueMessage()方法;
再看一下消息的本体结构设计,设计了一个排队的标记next,标记谁排在它的后面,用于连接队列:
public final class Message implements Parcelable {
public int what;
...
Message next;
}
复制代码
如今有了发送消息的Handler,也有了接收消息的队列(MessageQueue),那么还须要一个能够把消息从消息队列里面取出来的工具;
在设计以前,有一个疑问:线程有可能会发送消息,有可能不会发送消息,我并不知道消息队列里面是否有消息;
由此得知,这个工具须要不停的查看消息队列里面有没有消息,有的话就将其取出来,避免耽误了其它线程更新UI的需求,这就要求这个工具须要时刻不停的工做着,那么就将其设计为不停工做的轮循器(Looper);
轮循器里面设计了一个无限循环的机制,能够不停的从消息队列里面取出消息,那么设计出来的效果以下:
public final class Looper {
public static void loop() {
...
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 不停的循环从队列里面取出消息来;
for (;;) {
...
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// 回调给主线程
msg.target.dispatchMessage(msg);
} finally {
...
}
...
}
}
}
复制代码
轮循器Looper里面有一个无限循环的方法,能够一直从消息队列(MessageQueue)里面取出消息出来;
这时候又有疑问了,轮循器(Looper)取出来的消息要怎么发给主线程?
咱们能够经过设计一个回调,来将这个消息回调给主线程;
接下来在Handler里面设计一个接口,能够将Looper发送的消息回调到主线程,经过handleMessage()方法,这个方法最终是经过Handler里的dispatchMessage来进行回调;
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
...
}
}
复制代码
Handler里的dispatchMessage()则设计由Looper来进行调用;
那么到这里一个完整的消息机制就设计完了,可是这样子就结束了吗?然而事情并无这么简单,且听我细细道来!
上面设计的只是给主线程使用的一套消息机制,一个轮循器(Looper) + 队列(MessageQueue),那么当其它线程之间也须要进行通信呢?总不能都使用主线程的消息机制吧,这样会乱套的;
那这样的话就给每个线程设计单独一个轮循器(Looper) + 队列(MessageQueue),用于自身的通信;那么问题又来了,这么多线程,对应这这么多个轮循器(Looper) + 队列(MessageQueue),要怎么管理也是一个问题;这么多个消息机制,哪一个是属于本身线程的,哪一个是属于其它线程的,必需要划分好界限才行,否则就会出现a线程想发送消息给b线程,结果发送到c线程去了,这样子就混乱了;
既然如此,那么咱们将线程和轮循器(Looper) + 队列(MessageQueue)绑定起来,经过设计一个管理器来管理这些轮循器(Looper) + 队列(MessageQueue),将每个线程对应的每个轮循器(Looper) + 队列(MessageQueue)作好一一对应关系;
那么咱们就设计一个线程管理类ThreadLocal来管理这些关系,看一下设计出来的效果:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
}
复制代码
设计两个方法,经过键值对的方式来进行存储与取出,以线程为键,以Looper为值,将其存储起来;
而后在Looper里面将这个ThreadLocal设置为全局惟一的变量,这样其它的线程随时能够经过ThreadLocal来获取本身的Looper;
效果以下:
public final class Looper {
// 全局惟一的变量,线程随时能够获取到
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void loop() {
...
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 不停的循环从队列里面取出消息来;
for (;;) {
...
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
...
}
...
}
}
}
复制代码
如今每一个模块的工具都已经设计完成了,接来下要让这几个模块协同工做,组装成一套完成的消息机制;
Handler:发送消息;
MessageQueue:接收消息的消息队列;
Looper:轮循器,不断的从消息队列里面取出消息;
Message:消息结构体,支持链表结构;
ThreadLocal:管理线程的轮循器(Looper);
这里咱们把轮循器(Looper) + 队列(MessageQueue)进行绑定,为一个线程进行服务; 而后在Handler里面经过ThreadLocal来获取本身的Looper,这样发送的消息就会进入Looper里的消息队列(MessageQueue),而后在经过Looper的循环,经过Handler里的接口回调给相应的线程;
让咱们看看设计后的结构图:
到这里一套完整的消息机制就设计完成了;
下面来看一下运做的关系图:
Handler的设计很巧妙,源码里的细节不少,我就不贴出来了,这里只是讲一下Handler结构设计,建议能够本身跟着源码去走一遍,用于加深理解!
1,每个线程都有一个本身的轮循器Looper和消息队列MessageQueue,用于接收其它线程发来的消息,每个线程都有本身惟一的Looper和MessageQueue;
2,Handler能够无限建立,由于建立的Handler会和线程的Looper进行绑定;
3,Handler发送消息后,会在消息队列里面进行排队,并不会当即被响应到;
4,Handler的消息队列MessageQueue里的消息存在滞后性,所以会存在内存泄露的风险;
5,Handler的Message是链表的结构,用于在消息队列MessageQueue里面进行排队;
6,ThreadLocal用于管理线程的Looper,用来保证其一一对于的关系;
兄dei,若是个人文章对你有帮助的话,点个赞呗️,也能够关注一下个人Github和博客;
欢迎和我沟通交流技术;