零零碎碎的东西总是记不长久,仅仅学习别人的文章也只是他人咀嚼后留下的残渣。无意中发现了这个每日一道面试题 ,想了想如果只是简单地去思考,那么不仅会收效甚微,甚至难一点的题目自己可能都懒得去想,坚持不下来。所以不如把每一次的思考、理解以及别人的见解记录下来。不仅加深自己的理解,更要激励自己坚持下去。
Handler 机制
Handler 想必接触 Android 的都已经很熟悉了,我们通常用用于子线程与主线程的通信,并在主线程中处理相关消息,比如更改 UI 等。Handler 的消息机制为我们处理线程中的通信确实方便了许多。
基本使用
最简单的使用就是在主线程中创建 Handler 对象并重写 handlerMessage() 方法处理消息,在子线程中通过 handler 对象的 sendMessage 方法发送消息。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// TODO write your message processing code
...
}
};
new Thread(){
@Override
public void run() {
super.run();
//TODO handling events
...
handler.sendMessage(Message.obtain());
}
};
为了简单示例,Handler 才这样写的。这种创建方法会造成内存泄漏,具体愿意以及解决方案在每日一道面试题(第 1 期)—自定义 handler 如何有效保证内存泄漏问题 都已经说明,这里不再赘述。
而关于 Handler 的原理,我们可能会多多少少的了解到与 Looper、Message、MessageQueue 都是离不开的,那么具体是怎麽配合呢?下面我根据消息机制的过程,结合源码一步一步的解析。
Handler 原理
创建 Handler 对象
在我们使用时,首先就是创建 Handler,那我们看一下 Handler 的构造函数都干了些什么吧。
public Handler(Callback callback, boolean async) {
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());
}
}
//获取本地 TLS 存储区的 Looper 对象引用
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//使用 Looper 中的 MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
好吧,很多构造函数,不过有用的就是这两个,其他都是调用这两个而已。从两个对比可以看出,如果你没有传入 Looper 对象,那么就会通过 Looper.myLooper() 获取。传入的话,就是用自定义的,并且 MessageQueue 一直是使用 Looper 中的 MessageQueue,所以这里出现了第两个重要结论:
- Handler 对象的创建一定需要一个 Looper 对象
- Looper 中一定有 MessageQueue
然后还有两个参数 callBack 和 async,callBack 是 Handler 内部的一个接口,内部只有一个 handleMessage() 方法,作用我们下面讲到再说。然后就是 async,这个就好理解了,就是决定是异步处理消息,还是同步处理消息,默认为同步 false;
public interface Callback {
public boolean handleMessage(Message msg);
}
Looper 对象的创建
上一个步骤中我们知道,Handler 是直接从 Looper.myLooper() 中获得对象的,我们具体探讨一下。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
很简单,是从一个集合中拿到的,这个 sThreadLocal,是 ThreadLocal 类的对象,代表着本地存储区(Thread Local Storage 简称 TLS),线程中的唯一 Looper 对象就存储在这里,每个线程都有一个本地存储区域,并且是私有的,线程之间不能相互访问。我们寻找一下是在哪个地方插入的
public static void prepare() {
prepare(true);
}
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));
}
找到啦,由两个方法的权限访问修饰符可知,我们只能调用第一个,也就是说,第二个方法的参数永远是 true。再来看第二个方法,首先就是一个异常捕获,有的人可能已经很熟悉了,那就是一个线程中只能有一个 Looper。后面就是没有的话就 new 一个 Looper 对象,构造函数中都干了什么呢?
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
也很简单,初始化了 mQueue 也就是 MessageQueue,还有就是当前所在的线程 mThread。从权限访问修饰符可以看出,这是一个私有的构造方法,所以说,我们创建 Looper 方法只有 prepare() 方法啦。
MessageQueue 对象的创建
上述 Looper 对象的创建中,new 了一个 MessageQueue 方法,我们看看都干了什么。
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
也很简单,初始化了两个变量 mQuitAllowed 与 mPtr,第一个代表着消息队列是否可以退出,上面也说了,我们么的办法,只能是 true。第二个 mPtr,涉及到 Native 层的代码,在 native 层也做了一个初始化,具体深入了解可到此处Android 消息机制 2-Handler(Native 层)
发送消息
创建完 Handler,下一步就是发送 Message 了,去看看源码
public final boolean sendMessage(Message msg){};
public final boolean sendEmptyMessage(int what){};
public final boolean sendEmptyMessageDelayed(int what, long delayMillis){};
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis){};
public final boolean sendMessageDelayed(Message msg, long delayMillis){};
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);
}
又是好多发送消息的,不过他们最后都调用了 sendMessageAtTime 方法。第一个参数是要发送的消息,而第二个是一个绝对时间,也就是发送消息的时间。在做了一些判断之后,调用了 enqueueMessage 方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//消息获得发送该消息的 Handler 对象引用
msg.target = this;
//是否同步属性
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在又做了一些 Message 的属性初始化后,调用了 queue 的 enqueueMessage 方法,而这个 queue 就是在 Looper 对象中获得的 MessageQueue 对象。下面是 MessageQueue 中的 enqueueMessage 方法
boolean enqueueMessage(Message msg, long when) {
// 每一个 Message 必须有一个 target
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 (mQuitting) { //正在退出时,回收 msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;//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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
总结就是,MessageQueue 按照消息的触发时间插入队列,队头是最早要触发的消息。一个新的消息加入会根据触发时长从队头开始遍历。
消息轮询
好了,消息已经发送给 MessageQueue 了,那么谁来管理这个 MessageQueue,将其中的消息正确的、准确的分发呢?那就是 Looper,准确的说是 Looper 中的 loop() 方法,这也是我们在子线程中创建 Handler 先要之前要 Looper.perpare(),之后要 Looper.loop() 的原因。
public static void loop() {
final Looper me = myLooper();//获取 TSL 存储区的 Looper 对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//获取相对应的消息队列
................
for (;;) {//消息主循环,除非线程退出,不然会一直循环,没有消息时会阻塞
//获取下一个消息,没有消息时会阻塞,消息队列退出后会返回 null,则该循环也退出
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
// 默认为 null,可通过 setMessageLogging() 方法来指定输出,用于 debug 功能
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
................
try {
//获取消息后根据 msg 的 target 即所属 Hnadler 分发消息
//target 即 Handler 在上面发送消息代码解释中有说明
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
.................
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
................
//分发完此消息,就回收此 Message 对象,留以复用
msg.recycleUnchecked();
}
}
一些代码解释已经很清楚了,在在这里面主要是一个死循环轮询消息。由 MessageQueue 的 next() 方法取出消息,再由 Message 所属的 Handler 对象 dispatchMessage() 方法分发消息。首先我们看 next() 方法。
获取消息
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {//当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;//阻塞时长
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞函数,参数 nextPollTimeoutMillis 表示等待时长,或者消息队列被唤醒,都会返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息中的 Handler 为空时,在 MessageQueue 中寻找下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//下一个消息还没有到时间发送,设置阻塞时长
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//改变 message 状态
msg.markInUse();
return msg;
}
} else {
// No more messages.
//没有消息,设置阻塞时长
nextPollTimeoutMillis = -1;
}
// 如果正在退出,返回空
if (mQuitting) {
dispose();
return null;
}
.............
}
}
nativePollOnce 是一个阻塞函数,参数 nextPollTimeoutMillis 则代表阻塞时长,当值为-1 时,则会一直阻塞下去。
所以说,next 函数在 MessageQ 有消息时,会获取消息并返回,在没有消息时,则会一直阻塞。
分发消息
获取完消息,就要到分发消息了,也就是消息轮询总结中的 dispatchMessage 函数,在 Handler 类中
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里的调用流程就是
- 首先判断 Message 的 callback 是否存在,如果存在,就执行 handleCallback 函数,这个函数就一个简单的 message.callback.run() 方法调用。Message 中的 callback 实际上是一个 Runnable,所以就会执行自定义的 run 函数。这个的作用是在 Handler 的 post 消息上,其实 post 消息与 send 消息并没有太大的不用,只是通过 getPostMessage 方法将 Message 封装了一下,其实就是将自定义的 Runnable 传给 Message 的 callback,这样在分发消息的时候就是直接执行自定义的 Runnable 中的 run 函数。
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
- 然后就是 Handler 类中有一个 Callback 接口,接口中只有一个 handleMessage() 函数。如果成员变量 mCallBack 存在,就会首先执行此接口中的函数。实际使用中我们可以实现此接口,并重写方法。然后通过 Handler 的构造方法传入。
public interface Callback { public boolean handleMessage(Message msg); }
- 最后才是我们熟悉的重写 Handler 类中的 handleMessage 方法,这个方法如果在上面那种处理过后返回的是 true,那么就根本到不了这个函数。所以说上面两种处理方式都可以拦截消息。其中第一种是一定会拦截消息,第二种则由返回值确定。
public void handleMessage(Message msg) { }
总结
转了一圈,从 Handler 到 Looper 又到 MessageQ 最后又回到 Handler,整个消息机制也就差不多这样啦,当然 Message 在其中就是一个实体啦,可以协上数据一起传递消息。我也是根据这个顺序,一步一步的慢慢了解每个步骤的。
最后,附上一张图吧,会更清晰一点