Android Handler机制

如果把App比作人体,那么Handler机制就是人体的整个神经系统。它维系着从脑部到全身的任何一个部位的命令信息的传递。

Handler 机制的运作方式

Handler机制正常运作离不开基本的三要素:Handler、Looper、MessageQueue。(如果把Message算作第四要素也没问题)。

Looper 是整个机制的核心,可以看做是消息引擎,当Looper在运转的过程中,其自身机制会保证从MessageQueue(消息队列)中不断地获取消息(Message)并执行。

MessageQueue 是承载Message的容器,其特性属于队列性质,但存在一些特殊操作,这一点我们后面再说。

最后就是Handler,它提供了管理Looper,调用Looper,和外部组件交互的能力。

Handler的源码并不多,功能也并不复杂,下面重点讲解Handler的重要方法。

  1. 初始化。

Handler提供了多个构造函数,但最终都会汇总到下面两个构造函数:

public Handler(Looper looper, Callback callback, boolean async) 

public Handler(Callback callback, boolean async) 

构造方法参数不多,looper是消息引擎,在与外界交互的时候毕传;

callback是外界自定义回调,若callback不为空,则不再走Handler原生的handlerMessage方法而是调用callback,代码逻辑如下。
执行的优先级排序从大到小依次为:message.callback,handler.callback,handler.handlerMessage()。所以当调用场景命中了所有以上三个回调时,他们的优先级要仔细分辨。

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

第三个参数:async,是否异步。一般情况下,我们在Handler做手动初始化时,都不需要传这个参数,是因为在调用Handler(looper,callback)的时候,已经默认讲async赋值了false。那么这个值是什么意思呢?看下一段讲解。

三种消息类型和同步屏障

在handler 入队消息时,会检查mAsynchronous,也就是async的值,如果是true,则设置该msg为异步执行。

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

设置msg为异步执行有合用途呢?对MessageQueue中的消息执行会产生什么样的影响呢?我们接下来看MessageQueue中的方法:next().

Message next() {
        ...
        
        for (;;) {
           ...

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            
            ...
         }
    }

这里需要理解两个重要概念:1. Message的分类;2. AsyncBarrier机制。

跟聚用途和调用机制的不同,Message大体可分为三类:1. 普通Message;2. 异步Message;3. 同步屏障Message。

首先是Message的执行机制:也就是从Handler.post()开始到最终handler.dispatchMessage(),是如何实现的。
以下代码描述了Handler.post()和Handler.postDelay()的机制。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

经过层层调用,post/postDelayed -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage,最终追到queue.enqueueMessage(msg, uptimeMillis)方法中。

boolean enqueueMessage(Message msg, long when) {
      。。。
        synchronized (this) {
                。。。
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                。。。
            } 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;
                for (;;) {
                    prev = p;
                    p = p.next;
                    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;
    }

else中的语句说的其实是链表的按序插入。插入标准就是Message中的when变量。从之前的调用链一路走来会发现,when的值就是该Message所应该执行的精确的时间点。根据这个时间点,在message入队的时候进行插入,确保队列顶部的message先于后续message执行。如果调用的是postDelayed方法,则 “when = 当前时间 + delay时间”。这也就是post和postDelay的实现原理。到目前为止,我们只讲了一半,就是Message是如何入队的。那么通过MessageQueue.next()执行的过程中,轮到该Message出队并执行时,又是如何?下面再次引用MessageQueue的next()方法源码:

Message next() {
     。。。
        for (;;) {
            。。。
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                //获取一个队列头部的msg
                。。。
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //如果该Message的when的时刻还没有到,则设置一个唤醒时间,到时候再执行。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        //否则出队,返回该Message对象。同时对其进行标记,“正在使用”。
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            。。。
            }
        。。。
        }
    }

出队的时候,首先找到一个队列头部的Message,判断其when字段的执行时刻是否已经到来,如果没有,则设置一个“唤醒时间”,到时候再执行。否则,对队列执行出队操作,重设链表指针,并返回该Message。

通常情况下我们对MessageQueue的使用也就到此了。但Handler构造中对async 的设置,使得按顺序执行的规则并不是一成不变的,Handler机制为“异步Message”提供了“超车道”。这使得对应的Handler在初始化时,其消息就是异步执行的。

结合上面这段MessageQueue.next()代码,就会产生一个问题:既然Handler在初始化的时候就决定了其消息是异步还是同步执行,为什么还要在next中区分消息是否是异步还是同步呢?

这里存在一个误区,同一个线程,是否可以有多个Handler运行?答案是肯定的,可以有多个。但是,同一个线程能否有多个Looper呢?答案是否定的,一个线程最多只能有一个Looper,也就是最多只能有一个引擎,这意味着只能有一个MessageQueue运转(MessageQueue是Looper的成员变量,在Looper的prepare阶段准备好)。

我们分别看一下Handler的某一个构造函数和Looper的prepare()方法就知道了。

public Handler(Callback callback, boolean async) {
       ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler的这个构造方法要求我们必须在构造它之前在当前线程存在一个Looper,mLooper = Looper.myLooper(),若没有,则运行时异常,报错为“当前线程没有Looper存在”。上述逻辑解答了为什么MessageQueue.next()中会区分对待Message的异步和同步类型:因为他们是可能存在于同一个MessageQueueu中的,而这个取决于当前线程是否存在不同async值的Handler()对象。

下面就自然产生了如下两个问题:1. 异步Message的用途是什么,为什么MessageQueue要给这个另类提供超车道?2. 既然AsyncMessage只是相对于普通Message设置了async标记,除此外在入队的过程中没有其他的任何不同,那么MessageQueue是通过什么方式或者机制支持异步消息执行的呢?

先回答第二个问题:MessageQueue对于“异步消息”是如何支持的。这里就要引入我们的第三类Message了————同步屏障(SyncBarrier)。
同步屏障机制能够有效保证异步消息的正常执行。由于三种消息均处于同一个MessageQueue中,因此当同步屏障被按规则插入之后,Looper在执行next方法时若执行到一个同步屏障,则直接寻找后续队列中遇到的第一个异步Message,并拿出来执行(详见MessageQueue.next())。
一下是上文对next代码中省略的一部分,专门用来处理同步屏障的逻辑:

Message next() {
        。。。
                
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // 遇到了同步屏障,则开始遍历后续Message,寻找异步消息类型并取出。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                //如果取出的消息不为空,则统一按照后续正常流程执行。否则,挂起,直到有新消息进入。
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

现在解答第一个问题:为什么Handler机制要提供异步消息的类型?用途是什么。
一个最典型的应用,发生在Android View的绘制过程中(此处应该有独立知识点),为了保证在渲染信号到来的时候,刷新机制能够立刻执行,使用到了该机制。在ViewRootImpl中存在如下方法:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

同时当view绘制完毕的时候,必须调用如下方法消除同步屏障,否则该屏障消息会始终存在于MessageQueue中:

void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

这也刚好印证了,MessageQueue中为何会存在removeSyncBarrier()方法。因为next方法中不涉及到对同步屏障的清楚处理。需要手动调用。

总结

  1. Handler机制三要素:Handler、Looper、MessageQueue(包含Message)
  2. 一个线程最多只能有一个Looper和一个消息队列,但可以有多个Handler。
  3. Handler的post、postDelay等方法的实现原理,when字段在整个机制中的重要意义。
  4. MessageQueue.next方法的运行方式。
  5. 同步屏障的概念和实现原理,用途。

关联问题:

  1. 如果SyncBarrier的插入晚于某个异步消息,怎么办。有没有机制能够保证SyncBarrier一定在该异步消息之前插入?
    postSyncBarrier(long when)这个方法就是最好的保证。无论是屏障消息还是异步消息,都需要遵循由when决定的插入顺序,因此postSyncBarrier的时机只要发生在目标异步的消息的when之前即可。

  2. . 消息机制是如何切换线程的?比如从子线程切换到主线程。
    其实就是拿到了对应主线程的Handler。最常用的方式就是Activity.runOnUiThread()。首先判断当前线程是不是ui线程,如果是,则直接调用action.run()。否则,获取主线程Handler进行post。

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

通过这个例子可以明白,无论我们处于哪个线程,只要有办法获取目标线程所绑定的Handler,即可实现消息的线程切换。

  1. ANR监听的是什么?为什么Looper在执行死循环的 时候不会触发ANR。
    ANR的监听是一个比较大的主题,简单来说,ANR监听的并不是Looper的循环情况,而是每个Message在执行过程中的执行时间,因此题目其实是具备误导性的。四大组件中对消息的执行时间的要求也是不一样的。

  2. ThreadLocal是什么?有什么作用?

    ThreadLocal并不是Thread,它其实是是一个容器,专门存放跟Thread相对应的内容。在Handler机制中,ThreadLocal存放 的是跟线程一一对应的Looper对象。若在实例化Handler的时候Looper还没有绑定到该线程,则抛出异常。Looper通过prepare进行初始化,存入threadLocal。

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