Handler消息机制详解,只此一篇,以后不会忘了

Handler消息机制是Android中很重要的一个知识点,之前网上有很多同学分享了大量的博客介绍handler机制,那么为什么我要再写一篇呢?因为我看过的博客发现分析的干巴巴的,不容易记忆。所以呢,这次我花时间做了一张消息机制的时序图,为的就是看图给右脑记忆,从此不在忘记!!!

好了,废话不多说,先看时序图,下面我会根据图讲解串通:

<img src="http://pcayc3ynm.bkt.clouddn.com/handler%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg"/>

从哪看起呢?大家分析Handler消息机制的时候要么从sendMessage()分析起来,要么从Looper.prepare()开始,我喜欢从后者开始,因为sendMessage()执行的前提就是Looper调用了loop()方法,不然也收不到消息。我们就先从Looper说起,因为Looper执行loop方法之后才能正常接收到消息并执行后续操作。

主线程Looper初始化

我们先看图中的Looper对象,在Looper对象的第一个控制焦点,即上图中 “ActivityThread创建,初始化Looper,新建MessageQueue,即Queue” 焦点,由这个焦点可看出我们谈论的是主线程中的Looper,所以上图中的Looper对象就是主线程的。子线程的其实类似,就是Looper的初始化需要我们自己调用,我们先看主线程的。

主线程中的Looper什么时候创建的呢?因为我们代码中并没有为UI线程创建Looper,实际上创建应用程序的时候系统就已经为我们创建好了UI线程的Looper,看代码:

public static void main(String[] args) {
    // ...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    // ...
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

这是ActivityThread的main()方法,可以认为就是应用程序创建的入口,main()方法里我们先调用了Looper.prepareMainLooper()方法,准备好Looper之后,最后调用了Looper.loop(),这时候主线程(即UI线程,后续不在解释)的Looper就开始了循环,也就意味着我们可以往主线程发消息了。

Looper.prepareMainLooper()内部还是调用了prepare()方法,往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));
}

我们看下Looper的构造方法:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

构造方法里会创建一个MessageQueue对象,顾名思义,就是用来放Message的队列,Looper调用loop()方法之后开始循环,就会从这个MessageQueue里面取消息。有取必然有放,我们看完了Looper的初始化,Looper循环也运行起来了。所以接下来看发消息。

Handler发送消息

需要发送消息,那么就必须要创建Handler,所以我在图中加了一个Activity对象,并调用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());
        }
    }
    //标注1
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //标注2
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

创建Handler最终会调用到上面的构造方法,当然创建子线程接收消息的Handler除外。标注1处通过Looper.myLooper()获取到Looper对象,myLooper方法内部还是从ThreadLocal里面取的,所以拿到的当然是主线程的Looper了。标注2处拿到Looper的MessageQueue,只要拿到这个MessageQueue,我们在Handler里面就可以往主线程的MessageQueue里面放Message了。



好了,我们终于可以开始发消息了

看图中Thread对象的第一个焦点,这个Thread对象就是模拟工作线程,第一个焦点调用Message.obtain()方法获得一个msg,然后调用Handler的sendMessage()方法(这里不重要的点我就不在贴源码,因为图中生命线和焦点就是源码方法调用的时序,大家在看的时候可以对照源码),sendMessage()内部调用Handler的sendMessageDelayed()方法,到达下一个焦点,sendMessageDelayed()方法又调用了Handler内部的sendMessageAtTime()方法,sendMessageAtTime()方法接着调用了Handler本身的enqueueMessage()方法,enqueueMessage()我要贴下代码,因为这里有个精彩的地方:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

这里我们发送的msg把它的target属性指向了Handler自身,为什么要这样做呢,就是为了Looper接收到消息之后回调Handler的callback,后面我们把消息发到Looper的时候会细说,继续看,enqueueMessage()方法调用了queue.enqueueMessage(msg, uptimeMillis)方法,这个queue就是我们创建Handler时候的MessageQueue,我们看MessageQueue的enqueueMessage()方法:

boolean enqueueMessage(Message msg, long when) {
    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) {
        //...
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //标注1
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //标注2
            for (;;) {
                prev = p;
                p = p.next;
                //标注3
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

代码挺长,实际上就是入队操作,这里有个mMessages,并且p指向了mMessage,我们可以把它理解为是待执行的message队列,该队列是按照when的时间排序的且第一个消息是最先执行。

我们看标注1处,有三个条件,如果mMessages对象为空,或者when为0也就是立刻执行,或者新消息的when时间比mMessages队列的when时间还要早,符合以上一个条件就把新的msg插到mMessages的前面 并把next指向它,也就是msg会插进上图中队列的最前面,等待loop的轮询。

如果上面的条件都不符合就进入else代码中,我们可以看到标注2处有个for的死循环遍历已有的message对象,其中标注3中有个if语句when < p.when when是新消息的执行时间,p.when是队列中message消息的执行时间,如果找到比新的message还要晚执行的消息,就执行 msg.next = p; prev.next = msg;也就是把插到该消息的前面,优先执行新的消息。好了,到这里我们发送的消息就到了队列里面,发送消息已经完结了,下面就等Looper循环到我们的消息就行了。

Looper循环

Looper调用loop()方法之后就是一个死循环,如下是loop()方法源码:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    //...

    for (;;) {
        //标注1
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        //...
        
        try {
            //标注2
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        //...
    }
}

代码很长,我只贴出有用的,可以看到标注1处,会从queue里面取出msg,然后看标注2,会调用msg的target属性的dispatchMessage(msg)方法,首先这个target我们上面提了一下,就是在Handler的enqueueMessage()方法里我们把Handler自身赋值给了msg的target属性,那么loop循环里调用到标注2处就又回到了Handler的dispatchMessage(msg)方法。

分发消息

dispatchMessage(msg)就是分发消息的处理,我们看源码:

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

如果msg本身的callback不为空,就调用msg自身的回调方法,否则进入else里面

else里面先判断mCallback是否为空,这个mCallback在Handler构造方法里可以传入,如果mCallback不为空就调用mCallback回调,否则调用handleMessage(msg)方法。到这个回调,消息已经接收到了,下面就是我们自己的业务逻辑处理了。

我写这篇博客的目的其实还是上面的流程图,如果大家把流程图弄懂了,以后自然就记住了。

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

推荐阅读更多精彩内容