美文网首页
Android Handler源码解析

Android Handler源码解析

作者: Android开发技术总结 | 来源:发表于2019-12-13 22:28 被阅读0次

    前言:

    对于一个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是不是就清楚了。

    相关文章

      网友评论

          本文标题:Android Handler源码解析

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