Android Handler源码解析

前言:

对于一个Android研发而言,亲身体会就是不管在平时开发或者面试的时候,Handler消息机制毋庸置疑都是一个必备的知识点,所以这边留一份个人笔记,如有分析不对的地方,还望指出!

目录:

1、如何分析Handler源码

2、源码大致流程:消息的入队与出队

3、从大致流程进入细化分析

3.1、Handler、Looper、MessageQueue三者之间的关系

3.2、Handler、Looper、MessageQueue之间的协作

总结图1:Handler在子线程中发送消息,消息会被添加到MessageQueue消息队列中,再来由Handler所处的当前线程的Looper来不断的轮询MessageQueue以获取出队消息,最后调用dispatchMessage进行消息传递给handleMessage进行处理:


image.png

1、如何分析源码

众所皆知的Android源码的有很多,涉及到一个类或者多个类,一个类中又有很多代码,所以这边最简单的分析方式就是回归到Handler的使用中来,也就是如何使用Handler
1.1、实例一个Handler对象(主线程)

1.2、在子线程中使用Handler发送一个消息,如:handler.sendEmptyMessage(1)

1.3、消息发送出之后(执行1.2步骤),消息最终会被转发到我们new出来的Handler中的handleMessage方法进行处理(子线程消息发送到主线程中处理)

以上3个步骤即为我们对Handler的基本使用方式,所以,我们可以以发送消息的时机,作为源码分析的切入点,并留下一个疑问:

问题1:子线程发送的消息为什么是在主线程中接收的呢?

2、源码大致流程:消息的入队与出队

2.1、消息发送:sendMessage(Message msg) \ sendEmptyMessage(int what) \ postDelayed(Runnable r, long delayMillis) 等等

2.2、消息及发送时间处理:sendMessageAtTime(Message msg, long uptimeMillis)

2.3、消息队列添加:enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

2.4、消息出队:到这里,既然有消息添加到队列中的流程,而且我们最终都会获得相应的消息返回,那么消息是如何出队的呢?带着这个疑问,我们最终在MessageQueue 消息队列中找到一个函数名称为 next() 的函数。

问题2:这个next()函数 是在什么时候调用的呢?

在Handler源码上,以消息发送作为分析切入点来查看,如2.1罗列的几种消息发送方式,我们都可以很清楚的发现,消息都是调用了sendMessageDelayed(Message msg, long delayMillis),最终调用到sendMessageAtTime(Message msg, long uptimeMillis),然后在该方法里面调用了enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis),到这里,不管从方法名称还是局部变量的名称来看,这边都出现了一个队列的信息,所以可以知道Handler的消息发送最终是在sendMessageAtTime里面调用了MessageQueue.enqueueMessage()对消息进行队列添加,然后调用了MessageQueue.next()进行消息轮询并返回Message结果。

3、从大致流程进入细化分析

3.1、Handler、Looper、MessageQueue三者之间的 关系图2 如下:


image.png

在分析到第2步的sendMessageAtTime结束时,我们这边引出了一个消息队列的内容:MessageQueue queue = mQueue

/**
     * Enqueue a message at the front of the message queue, to be processed on
     * the next iteration of the message loop.  You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     * <b>This method is only for use in very special circumstances -- it
     * can easily starve the message queue, cause ordering problems, or have
     * other unexpected side-effects.</b>
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, 0);
    }

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

问题:mQueue是什么东西?这个mQueue是怎么来的?所以我们在Handler的构造方法中找到了它的初始化位置

/**
     * Use the {@link Looper} for the current thread with the specified callback interface
     * and set whether the handler should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
     *
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    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());
            }
        }

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

到此,从上面的两段源码,且带着第2点中,next()被调用的时机问题,我们引出了os/Handler中的两个成员变量

  final Looper mLooper;
  final MessageQueue mQueue;

MessageQueue 对象是从Looper中获得的,也就是说mQueue是在Looper中实例化的,所以很明显,Handler中的消息队列MessageQueue 是从轮询器Looper中获得的。
那么问题来了:为什么消息队列要在轮询器中进行实例化,请看以下源码

/**
     * Return the {@link MessageQueue} object associated with the current
     * thread.  This must be called from a thread running a Looper, or a
     * NullPointerException will be thrown.
     */
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

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

MessageQueue在Looper中进行实例化,也就是说一个Looper就有一个MessageQueue,属于绑定关系,从而得出一个Looper只能轮询一个消息队列
 所以可得出如关系图2中Handler、Looper、MessageQueue三者的关系:Handler中持有Looper和MessageQueue,Looper中持有MessageQueue,而且Handler中的MessageQueue来自于Looper中的MessageQueue。
  Handler是使用时通过New实例化出来的,MessageQueue是在Looper中进行实例的,那么这个Looper是如何实例化的?所以这边我们将引出 ActivityThread。而ActivityThread是什么东西呢?这边就稍微介绍一下:
  安卓应用程序作为一个控制类程序,跟Java程序类似,都是有一个入口的,而这个入口就是ActivityThread的main函数:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

以上的main函数代码中,我们还需要意识到两个问题:

1.我们之所以可以在Activity用Handler handler=new Handler()直接创建出来就默认绑定到主线程了,是因为上面的代码为我们做了绑定主线程的Looper的事情,
2.主线程的Looper是不能在程序中调用退出的,最后一句代码看到没,如果调用的话,就会抛出异常,退出主线程的循环是框架层在调用退出应用程序的时候才调用的

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


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

在ActivityThread的main中调用了 Looper.prepareMainLooper() -> prepare(false) -> sThreadLocal.set(new Looper(quitAllowed)), 这么一来,是不是执行到了上面的Looper构造函数中了?到这里,细心的人会发现这么一个问题:

问题3:Looper被实例化出来之后并没有直接返回,而是被set到了ThreadLocal中?

Handler与Looper是成对出现的,一个子线程发送消息,一个主线程接收消息,那么这边就涉及到了多线程,线程之间的通讯,是需要保证数据的安全,即数据隔离,所以使用到了ThreadLocal进行线程管理:如A线程在获取数据时只能获取A线程所控制的数据,而不能去获取到B线程中对应的数据,否则就会引起数据不同步,比如A线程数据被B线程数据所覆盖之类的问题,同时也验证了一个线程只能关联一个Looper对象。

所以问题3解决了。最后这个main的结尾,调用了 Looper.loop(); 进行轮询消息队列! 是不是很完美了?

3.2、Handler、Looper、MessageQueue之间的协作

通过前面的源码分析,我们已经知道了消息是如果添加到消息队列了。我们再来看消息的出队分析。

以下在Looper轮询器中的loop()中我们看到这样一句代码:Message msg = queue.next(); // might block

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

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

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

所以通过以上代码,我们可以知道消息的出队,是在Looper这个轮询器中的loop()函数通过死循环的方式:for (;;),不断的通过队列的next()方法中拿到消息:queue.next(),并且如果队列消息为null了,就跳出该轮询。所以问题2是不是已经解决了?

在出队过程中,也就是MessageQueue消息队列中的next()函数中,我们可以知道next()返回的是一个Message消息对象,从函数注释上来看:当轮询器 loop 轮询的时候会返回一条消息,且从代码for (;;)循环的代码中可以看出,是在这里不断的拿到消息队列并返回下一条消息,到这里,我们需要注意的是因为这个消息是可以循环使用的,而且我们可以看到这样一个native函数调用:nativePollOnce(ptr, nextPollTimeoutMillis);所以我们可以得出消息的循环使用内存是通过C++来维护完成的(这边因为对native没有深入研究,所以pass这块!)

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        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.
                    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消息机制中,是如何从一个消息的发送,进行了怎么样的世界环游,最终如何回到了Handler的handleMessage中的!

分析到这里为止,如果还没蒙圈的人会发现,我们在前面提出过的几个问题都解决了,那么问题1呢?

子线程发送的消息为什么是在主线程中接收的呢?

其实我们在前面也已经有提及到了该问题,就是为什么在ActivityThread的main中实例化的Looper对象是被set到了ThreadLocal中。

在java中,main是不是主线程呢?不需要解释了吧。看代码:当前线程中Looper的获取方式

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

程序一开始在ActivityThread在执行main函数时实例化Looper,然后保存到了ThreadLocal中。而我们在主线程中new了一个Handler,那么Handler默认对应的Looper就是主线程的Looper:通过以上代码,从ThreadLocal管理中获取出当前线程(主线程)对应的Looper对象,所以对应的Looper自然也是主线程的Looper,明白了吗?

所以主线程的Looper在轮询出消息队列MessageQueue中的消息时,就是出于主线程中,这样问题1是不是就清楚了。

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

推荐阅读更多精彩内容