继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Handler消息源码流程分析(含手写笔记)

陪伴而非守候
关注TA
已关注
手记 358
粉丝 62
获赞 285

相关文章链接:

Handler消息源码流程分析(含手写笔记)

HandlerThread线程间通信 源码解析

IntentService源码解析


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

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

目录

  • 简单使用方法

  • 源码流程分析


简单使用方法

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

使用方法:略

5b97cdf00001489d01800180.jpg

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


源码流程分析

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

5b97cdf10001a3b710960928.jpg

关键对象源码分析:

  • 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

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

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

    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方法中是一个死循环在不断的取消息。但注意当无消息时循环并不会做无用功,而是阻塞等待消息。

    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();来开启循环。

    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对象,以备后面发消息用。

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

    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);

     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);回调。消息就送达了。收工。

     public void dispatchMessage(Message msg) {      if (msg.callback != null) {
              handleCallback(msg);
          } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;
                  }
              }
              handleMessage(msg);
          }
      }

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

5b97cdf30001c89e08580689.jpg

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

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

5b97cdf400014c7613580930.jpg


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

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

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


如何下次找到我?

原文链接:http://www.apkbus.com/blog-889706-68413.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP