Handler消息源码流程分析

Handler在android开发中可谓随处可见,不论你是一个刚开始学习android的新人,还是昔日的王者,都离不开它。关于 handler的源码已经很前人分享过了。如果我没能给大家讲明白可以参考网上其他人写的。

注:文中所贴源码都有精简过,并非完整源码,只保留主思路,删减了一些非法校验细节实现

目录

  • 简单使用方法
  • 源码流程分析

简单使用方法

应用层开发时handle常要用于线程切换调度和异步消息、更新UI等,但不仅限于这些。

使用方法:略

哇哈哈哈,不要打我。为了不占用篇幅,想必识标题来者理当熟悉。若有不明之处且看其他偏基础点的教程便可。


源码流程分析

大王,且先随我看小的从网上盗来的一张图。handler发送Message(消息)至MessageQueue(模拟队列),由Looper(循环器)不断循环取出。然后通知Handler处理。这便是整个的消息机制。没有多复杂。

关键对象源码分析:

  • Looper 消息轮训器
  • MessageQueue 消息暂存队列(单链表结构)
  • Message 消息
  • Handler 收发消息工具
  • ThreadLocal (本地线程数据存储对象)

ThreadLocal

先说ThreadLocal的作用是不同的线程拥有该线程独立的变量,同名对象不会被受到不同线程间相互使用出现异常的情况。

即:你的程序拥有多个线程,线程中要用到相同的对象,但又不允许线程之间操作同一份对象。那么就可以使用ThreadLocal来解决。它可以在线程中使用mThreadLocal.get()mThreadLocal.set()来使用。若未被在当前线程中调用set方法,那么get时为空。

在Looper中是一个静态变量的形式存在,并在每个线程中拥有独立的Looper对象,没有则为空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
如若还不明白可单独做了解,弄明白这个是必须的,否则后面会云里雾里。不知为何hanlder可以做到跨线程消息切换。我姑且当做读者已熟悉这点。
这里Looper是最为重要的一环,我们先来看这个,其余几个对象源码分析的意义不大,后面小节会在消息流程中分析到。就省略了。如果非要纠结解析可以自己去翻阅一下源码即可。

Looper 关键源码

记住了,Looper主要做两件事。1.创建消息队列。 2.循环取队列中的消息分发。后面一个小节会讲什么时候创建,见流程分析。

构造函数
Looer创建的时候初始化了MessageQueue

1
2
3
4
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//创建消息队列
mThread = Thread.currentThread(); //获取当前线程
}

创建Looper
其中分为在主线程中创建,和子线程创建,但都是借助ThreadLocal来实现跨线程Looper对象的存储。

1
2
3
4
5
6
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));
}

开启循环
可以看到loop方法中是一个死循环在不断的取消息。但注意当无消息时循环并不会做无用功,而是阻塞等待消息。

1
2
3
4
5
6
7
8
9
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息,无消息则阻塞
if (msg == null) { return; }
msg.target.dispatchMessage(msg);//发送消息 其中target就是Handler
}
}

消息流程分解:

主线程中Looper的创建

  • 1.ActivityThread创建在main方法中调用Looper.prepareMainLooper();创建出Looper,而后将创建的Looper存于线程变量中(ThreadLocal),再将主线程中的Looper单独存一份,因为他是主线程的Looper。(实际它调用的是Looper.prepare(),我们也可以在子线程中使用时,用它来创建Looper)。

  • 2.在main方法的最后调用Looper.loop();来开启循环。

1
2
3
4
5
6
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
}

这便是,为什么handler可以做异步的原因了,因为在主UI线程创建的时候,就早已为UI线程创建了一个Looper,并开启了循环。

注:请思考一个问题,能不能在子线程中直接new handler发送消息?如果不可以?有没有办法解决?(切莫去搜一下看到某行代码添加完就可以了便不管为什么这样了,应当分析内部原理。)

handler如何收发消息

  • 1.new Handler()时构造方法从Looper.myLooper();获取当前线程中的Looper对象,然后取出MessageQueue对象,以备后面发消息用。
1
2
3
4
5
6
7

public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
  • 2 handler通过sendMessage(msg)将消息发出,消息最终走向queue.enqueueMessage(msg, uptimeMillis);这里的queque便是我们前面从handler构造方法中Looper里取到消息队列。

    1
    2
    3
    4
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 3.enqueueMessage方法里其中还会将当前发消息的handler存于msgtarget中。当Looper轮训到这条消息时,便会使用到。我们往下再看一眼之前Looper.loop()方法。最终调用了 msg.target.dispatchMessage(msg);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
    Message msg = queue.next(); // 取出消息,无消息则阻塞
    if (msg == null) { return; }
    msg.target.dispatchMessage(msg);//发送消息 其中target就是Handler
    }
    }

4.自此dispatchMessage() 中调用handleMessage(msg);回调。消息就送达了。收工。

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

再来看看这幅图,按照我上面的思路流程在跟着图走一走。看看我们分析得是否正确。

最后附一张以前学习Hanlder手写笔记,其实这份笔记光看的话,可能对读者没什么很大做用。但对我的帮助很大。我要表达的是一个学习思路,像类似这种源码分析最好自己拿笔写写画画,映象会深刻很多。知识过久了会忘记,光靠死记若非常人,很难过目不忘。自己写一遍就完全不一样了,就算过了许久已淡忘这些,打开自己的笔记看一眼就会明白。而不用从头来学一遍。

用我的理解来解释这种现象是学习的过程中可能坑坑洼洼,消磨掉不少时间。这种笔记会成为最后总结出来的结晶,与脑子里的印象流关联在一起。何必再费力气每次都从头温习,不如直接看以前自己的总结岂不快哉?

先生曰:小伙子长得眉目清新秀,这字真是丑得像鸡爪子爬,各位受委屈了。

下一篇我们将分解HandlerThread的工作原理和做用。

本文参考:
AndroidFramework官方源码
Handler 之 源码解析


如何下次找到我?

随缘打赏!