概述:
handler
消息机制是由Message
、MessageQueue
、Looper
、Handler
共同组成的一种消息通信机制。handler
常用于同进程之间的不同线程的通信,因为线程之间共享内存地址空间。
角色说明:
-
message
: 系统生成的消息 -
messageQueue
:消息队列,其重要功能是对队列中的消息进行入队(messageQueue.enqueueMessage()
)和出队-
(messageQueue.next()
)操作 -
handler
: 消息辅助类,主要功能是向消息队列中发送各种事件消息和接受消息并处理消息 -
looper
:不断循环执行消息出队操作(looper.loop()
),按分发机制将消息分发给相应的目标handler
进行处理
应用实例:
下面以在工作线程中创建handler
对象为例来理清handler
的工作原理
Runnable mRunnable = new Runnable() {
public Handler handler;
@Override public void run() {
//第一步
Looper.prepare();
//第二步
handler = new Handler() {
@Override public void handleMessage(Message msg) {
// 消息回调===》逻辑处理...
}
};
//第三步
Looper.loop();
}
};
在工作线程创建handler
的代码一共分3个步骤:
- 调用Looper.praper()
- 创建Handler对象
- 调用Looper.loop()
为什么要用这种形式来说明呢?因为这是一个典型的handler
在工作线程处理消息的一种形式,接下来的分析将围绕这样的一段代码展开,从源码的角度来分析这三步都做了那些工作,以下源码基于Android 7.1.1版本。
looper.praper()
以下代码是looper.praper()
的源码部分
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//该判断表明该方法只允许被调用一次,也就是每个线程只允许有一个Looper 对象,重复调用则会抛出异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,保存到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.praper()
内部调用了prepare(boolean quitAllowed),quitAllowed为true表示这个Looper内部的消息循环操作允许退出,false表示不允许退出(quitAllowed
变量可以暂时不用关注);上文代码提到了ThreadLocal,那么先大概了解下什么是ThreadLocal(这里不是所谓的本地线程)
ThreadLocal:线程本地存储区(Thread Local Storage 简称TLS),每个线程都有自己私有的变量存储区,不同线程之间不能彼此访问对方变量存储区域。ThreadLocal常用操作(既然是变量存储区,其操作无非就是CRUD):
set :
以下是 ThreadLocal.set(T value)
的源码部分
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程存储区域
ThreadLocalMap map = getMap(t);
if (map != null) {
//(this)以当前ThreadLocal对象为key保存value到ThreadLocalMap中
map.set(this, value);
} else {
//创建线程存储区域其实就是ThreadLocalMap类型的对象
createMap(t, value);
}
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
解释在源码部分已经给出,这里做下总结说明吧,加深印象。
先获取当前县城的变量存储区域对象,然后对它对非空判断,不为空则直接以当前对象对key进行变量存储操作,为空则先创建一个变量存储区域对象并做存储操作。
get
以下是 ThreadLocal.get()
的源码部分
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取本地存储区域对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//使用当前ThreadLocalMap为key取出相应的(T)value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) return (T) e.value;
}
//初始化TLS本地存储区域的值
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
return value;
}
protected T initialValue() {
return null;
}
以上就是ThreadLocal
的概念及基本用法,可以看出Looper
中sThreadLocal
是一个带有Looper
泛型的静态变量.
接下来回到主线任务handler
,来分析下Looper
的构造函数做了那些操作
以下是Looper
构造函数的部分源码
private Looper(boolean quitAllowed) {
//创建MessageQueue
mQueue = new MessageQueue(quitAllowed);
//获取当前线程对象
mThread = Thread.currentThread();
}
对,你没看错,就两行代码。
创建一个MessageQueue对象,获取当前线程对象。结合上文的分析:每个线程只允许调用一个Looper.preper(),也即是每个线程只允许有一个Looper对象,在Looper的构造函数中创建一个MessageQueue对象,所有此处得出一个结论:
线程、Looper、MessageQueue 一一对应(也就是一个线程有且仅有一个
Looper
一个MessageQueue
)
looper.loop()
以下是loop()的源码部分
public static void loop() {
//获取`ThreadLocal`存储的`Looper`对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//取出Looer对象中的消息队列
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//进入loop的无限循环体
for (;;) {
//取出`MessageQueue`中的下一条`message`,该方法可能会导致阻塞
Message msg = queue.next();
if (msg == null) {
//没有消息,则退出循环
return;
}
···
···
···
···
try {
//如果成功取出 `MessageQuenen`中的消息则进行消息分发
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//将Message放入消息池,以供循环使用
msg.recycleUnchecked();
}
}
从源码可以看出loop()
是在做轮询,不断进行如下操作,直到没有消息退出循环为止:
- 读取
MessageQueue
的下一条Message
- 将
Message
分发给target
(target
是指相应的handler
) - 再把分发后的Message回收到消息池,以便重复利用
Looper.loop()
源码是消息处理的核心部分
说到这里那么问题来了:
问题1.系统怎么知道究竟将消息分发给那个
handler
?
问题2.在loop的循环体中调用Message msg = queue.next() 可能会造成阻塞,那我们平时在使用的时候为何又没有造成阻塞呢?
问题3. 在UI线程中并没有显式调用preper()、loop()等方法为何没有抛出异常?
以上问题在分析完整个流程之后再来解释。
分析完preper
、loop
,接下来我们开始说说handler
这个类,handler
主要是用来发送消息,接受并处理消息;那么我们从handler
的构造函数开始分析它吧:
handler
以下是handler构造函数的部分源码
public Handler() {
//无参构造函数默认调用 Handler(Handler.Callback callback, boolean async)
this(null, false);
}
public Handler(Handler.Callback callback, boolean async) {
//匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露
if (FIND_POTENTIAL_LEAKS) {
final Class extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass())
&& (klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: "
+ klass.getCanonicalName());
}
}
//获取当前线程Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//获取该looper对象的消息队列
mQueue = mLooper.mQueue;
//回调方法
mCallback = callback;
//设置消息是否为异步处理方式
mAsynchronous = async;
}
无参的Handler构造函数会默认调用Handler(Handler.Callback callback, boolean async)
该构造函数做的几件事,流程还是比较明白的:
- 获取当前线程Looper对象、
- 从该Looper对象中获取与之对应的Messagequeue
- 回调函数传值为null
- 设置消息处理不为异步
到此handler
创建的前期工作三步骤已经分析完
接下来就是在相应的线程中通过handler
来发送和处理消息了
sendMessage()
发送消息可以调用多个方法,这里只是用sendMessage() 作为代表罢了
sendMessage(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendMessageDelayed(Message msg, long delayMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
sendMessageAtFrontOfQueue(Message msg)
enqueueMessage(queue, msg, uptimeMillis)
这么多种方法来发送消息,有什么不一样吗?
下面我们通过一张调用流程图来弄清这个问题
从结构图中可以看出所有的方法最终都会走到enqueueMessage
方法中,所以我们只需要来看enqueueMessage
的流程就行
enqueueMessage:
以下是enqueueMessage
的部分源码
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//this表示当前handler对象,所以此处的target指的是发送消息的handler对象
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage
的作用就是将消息添加到消息队列中,其中uptimeMillis为系统当前的运行时间,不包括休眠时间,msg.target = this;代码中已有解释,不在赘述,由此可以解决上文中的问题1:
解决问题1.
Handler
发送消息时最终调用的enqueueMessage
中会给发送的Message
的target
变量赋值为当前的handler
(也就是发送消息的handler
),然后系统会根据target来匹配接受消息的的handler
对象是哪个
注意:通常情况下使用
obtainMessage()
来获取消息池中已经存在message对象,而不是去生成一个新的message对象,这样的一个好处是节约内存空间,提升代码执行效率
handMessage()
发送消息后,系统根据消息分发机制分发消息到相应的handler的handMessage函数中进行处理,像下面代码所示一样
mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
// TODO: 处理消息
}
};
那么所谓的分发机制又是怎么样的呢?还记得上面提到的loop()
方法吗?该方法的主要作用是循环遍历消息队列中是否有消息、如果没有则退出循环体,如果有则调用msg.target.dispatchMessage(msg)来进行消息分发,所以要弄清楚消息是如何分发的,就要弄清楚msg.target.dispatchMessage(msg)的工作流程;
以下是dispatchMessage的部分源码
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//当Message存在回调方法时,调用msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//当handler中mCallback 变量不为null则mCallback的handleMessage()方法;
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用handler自身的回调方法handleMessage()
handleMessage(msg);
}
}
public void handleMessage(Message msg) {
}
dispatchMessage
的分发有3个步骤:
- 判断
msg
是否有回调 - 判断
handler
的成员变量是否被赋值 - 调用
handler
的子类handleMessage
,该方法是一个空方法,所以所有子类必须实现他来接受消息。如果mCallback.handleMessage
返回true,则handler
子类的handleMessage
的就不会收到消息,文章开始的应用实例中的就属于 调用handler
的子类handleMessage
。
到这里looper
、handler
的创建及消息的轮询、发送、接收等流程都分析完了。
下面要说的是messageQueue
的工作原理,messageQueue
是一个单向链表结构主要负责存取message
对象,系统会根据message
距离触发时间的长短决定该消息在队列中位置。下面从源码角度分析其对消息处理的流程。
以下是messageQueue
的构造方法的源码部分
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
messageQueue
的构造方法重点是 mPtr = nativeInit();
所以大部分的工作那应该是在nativeInit()
中处理了,但是nativeInit()
是native代码,还记得上文的loop()
方法吗?哈哈哈,多提几次才记得到,loop()
的轮询体中会调用messageQueue.next()
去进行真正的轮询操作,所以我们从messageQueue.next()
开始分析,该方法的主要作用就是轮询取出message
对象。
next():
以下是messageQueue.next()
的部分源码
Message next() {
final long ptr = mPtr;
//当消息循环已经退出,则直接返回
if (ptr == 0) {
return null;
}
// 循环迭代的首次为-1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
//成功地获取MessageQueue中的下一条即将要执行的消息
return msg;
}
} else {
//没有消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//当消息队列为空,或者是消息队列的第一个消息时
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//没有idle handlers 需要运行,则循环并等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //去掉handler的引用
boolean keep = false;
try {
keep = idler.queueIdle(); //idle时执行的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//重置idle handler个数为0,以保证不会再次重复运行
pendingIdleHandlerCount = 0;
//当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
nextPollTimeoutMillis = 0;
}
}
enqueueMessage
添加一条消息到消息队列,以下是enqueueMessage
的源码部分
boolean enqueueMessage(Message msg, long when) {
// 每一个message都必须关联一个handler对象
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) {
//正在退出时,回收msg,加入到消息池
if (mQuitting) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
msg.next = p;
mMessages = msg;
needWake = mBlocked; //当阻塞时需要唤醒
} else {
//将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
//消息队头存在barrier,并且同时Message是队列中最早的异步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
//消息没有退出,我们认为此时mPtr != 0
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
至此handler的全部流程已经分析完了。
此处需要一副图,他的名字叫流程图,上面BB了那么多,还不如一张图来的直接。
总结一下上面的流程分析:
- handler负责发、接受和处理消息.
编码中通常的做法是在主线程中创建handler
、接受消息、处理消息,在子线程中向主线程发送消息。
- messageQueue 负责存取消息.
handler
发出的消息均会被保存到消息队列messageQueue
中.
- looper负责对messageQueue 做轮询操作。
looper
调用loop()
方法一直轮询消息队列,并在消息出队时将其派发至对应的handler
.