************消息机制原理基本上算是面试必备问题了,之前看源码,看别人文章博客,以为自己搞明白了,也写了点笔记,但是到真正说出来的时候总是不顺畅,感觉不光是表达能力问题,整个体系其实也没有一个完整认识,所以写篇博客,按自己的思路来分析一下**********
1.概念
总的来说就是通过Handler 和Looper 实现线程间消息传递。作用呢,最多的当然是将子线程耗时处理的结果传递到主线程,都知道的是Android的主线程要避免耗时超做,像最多的网络请求,循环遍历操作等,而操作结束后又会根据结果对主线程做一些改动,但是子线程是禁止UI操作的,这时候就需要Handler来登场,将操作结果发送到主线程。
2.原理及源码分析
我们从功能开始分析,一步步解析消息从发送,处理,转发,最终到主线程我们能够获取到的过程
首先是Handler,从字面意思就是消息发送者,当子线程处理完得到结果时,我们通过Handler对象将消息发送出去,调用sendMessage() 等方法,然后就会在我们创建的Handler中收到消息。那么我们先看Handler的创建,Handler提供了几种构造方法,传入不同参数,可以 根据需要来创建
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到构造方法中获取了一个Looper对象,Looper中的MessageQueue对象,这里要知道的是,Looper对象是根据线程创建,不同线程有自己的Looper,如果你创建Handler时没有指定Looper, 那么你在哪个线程创建的Handler,你最终sendMessage后消息就会发送到对应线程
private Handler mHandler;
class LooperThread extends Thread {
@Override
public void run() {
Looper.prepare();//子线程不会自动创建Looper对象,需要prepare以后Looper.myLooper才能获取到Looper对象
mHandler = new Handler() {
// mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
LogUtil.LogE("LooperThread name:"+currentThread().getName());
}
};
Looper.loop();
}
}
com.yh.mohudaily E/hiall: LooperThread name:Thread-4267
********在这里我创建了一个子线程类,内部初始化Handler,然后在主线程sendMessage 接收到消息时打印当前线程名称,可以看到,handleMessage时的线程还是子线程。但是当我指定Looper为主线程Looper对象创建Handler时,handlerMessage的线程就会变成主线程。所以Handler对象创建时要主要所在线程。******
创建完Handler后我们继续看发送消息的方法。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
所有send方法最终都会进入到这个方法,在这里获取构造方法中取出的消息队列MessageQueue ,然后进入enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在这里将handler对象放入msg中(很重要的一步),然后让消息进入消息队列。
在messageQueue中
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {//判断当前队列是否为空,when=0时,when比队列最上面一条消息小时 都要讲消息插入到队列最前
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { //否则就遍历消息,比较各个消息的when值 按从小到大顺序插入到队列
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
......
}
return true;
}
消息队列主要是两个方法,插入消息(enqueueMessage)和取出消息(next);具体插入方法如上,消息进入队列后handler的处理暂时走完(为什么说暂时,很明显msg携带了handler对象,后面还要用到)。
在handler中将消息插入队列,那么怎么取出消息呢?我们是从Looper对象中得到消息队列的,现在进入到Looper对象中去
Looper没有提供构造方法,只有myLooper()静态方法获取Looper对象,Looper对象的创建是在Looper.prepare() 中完成,私有化构造方法中初始化了一个消息队列,指定当前线程。(同时将Looper对象存入到ThreadLocal类中,关于这个类原理后面在解释,他的作用就是对线程Looper对象的存取)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
创建完后调用Looper.loop();进入一个无限循环。
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.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
在这个无限循环里面会不断调用消息队列的取出方法,获取队列里面的消息对象。消息队列的next方法本身也是一个无限循环,不断从队列中获取消息,队列为空时next方法会阻塞等待,直到有消息进入队列时就会取出消息返回消息对象(需要注意的是当Looper的quit方法调用时,消息队列会退出,这时候next才会返回null),然后通过msg中携带的Handler对象将消息发送出去,dispatchMessage方法其实就是调用了一个空实现方法handlerMessage 也就是我们需要实现的处理消息的方法。到这里其实我们需要处理的结果在被封装到Message中后终于回到了主线程。
3 补充
关于Looper对象的获取,我们验证当前线程是否是主线程时会根据线面这行代码来判断
return Looper.myLooper() == Looper.getMainLooper();
其实getMainLooper 返回的sMainLooper 也是通过myLooper方法获取,也就是从ThreadLocal中拿到,那这两个方法拿到的Looper对象为什么会不同呢?sMainLooper 对象的赋值是在prepareMainLooper方法中,这个方法在Activity创建时就被调用,指定了创建的Looper线程为主线程,而myLooper 获取时就是以当前线程去从ThreadLocal类中拿的Looper,所以才可能出现两个Looper对象不相等从而验证线程。
关于ThreadLocal 他能根据不同线程存储线程数据,每个线程互不影响,具体实现我也不是十分明白,这里就不做解释了,有兴趣的自己去看看吧。