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

    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 之 源码解析


如何下次找到我?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容