美文网首页Android面试相关Android知识Android开发经验谈
Handler消息源码流程分析(含手写笔记)

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

作者: 香脆的大鸡排 | 来源:发表于2017-07-15 20:50 被阅读1424次

    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

        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;
        }
    
    • 2 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);
            }
        }
    

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

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

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


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

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

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


    如何下次找到我?

    相关文章

      网友评论

      • woLfcRazy:文中的问题,是可以在异步线程去发送消息的,只是处理的时候也是在异步线程的runnable中处理,所以要考虑异步线程的生命周期等因素,不能处理UI操作。
      • 24f485d31ef5:是我见过讲的最清晰的,特意登陆评论一下,感谢分享
        香脆的大鸡排:@胡图图_85ae 谢谢,祝你好运噢
      • 0a105caa3c54:鸡排,我发现自己理解东西好慢,虽然看了懂了一点,但是我感觉还不够。
        香脆的大鸡排:@差不多先生ly 慢慢来,滴水穿石
      • 公子清笳:看第二遍加深印象
        香脆的大鸡排:@公子清笳 :blush: 可以尝试自己对着源码翻,然后写一下笔记,映像会更好点哦。
      • 恋猫月亮:高产的老铁😉
        码字农民工:我发现在哪里都能遇到恋猫的小郭- -!
        香脆的大鸡排:@恋猫月亮 早 喵喵
      • Deps:怎么办,我很少用这个,Rxjava解决线程切换
        香脆的大鸡排:@Deps 但整个安卓应用层通信都是基于此的,往深了研究这个问题无法逃避。还是耐心学习为好。
        香脆的大鸡排:@Deps rxjava要依赖于rxAndroid才可以切换主线程,而rxAndroid 底层还是使用了handler消息机制。见rxAndroid源码
        Deps:@Deps 因为我觉得这个用起来太麻烦

      本文标题:Handler消息源码流程分析(含手写笔记)

      本文链接:https://www.haomeiwen.com/subject/xarwhxtx.html