Android知识Android技术知识

从源码角度看Handler消息机制

2017-12-14  本文已影响36人  721d739b6619

Handler消息机制处理的情景

Handler这个类在Android扮演者的角色,估计不用多说。而平常我们用的最多的就是更新UI,工作线程耗时操作,Handler主线程下更新UI。当然现在RxJava当道,感觉没有什么是RxJava解决不了的。真的膜拜。。。

言归正传,其实大家都知道Handler要做的事情就是线程间通讯。
老实说其实线程间通讯不需要Handler也能做到的。好比下面伪代码

//变量
int i = 0;
new Thead(new Runnable(){
      i = 1;
}).start();
//主线程
for(;;){
  //轮询等待
  if(i == 1){
      //to do something
      break;
  }
}

我们之所以没有这么做,很主要的原因就是这些伪代码的实现Handler这些相关类的机制(我们称为消息机制)已经帮我们做了。废话少说,上源码吧。

Handler类

A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread's {@link MessageQueue}. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on,
it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

上面是官网解释:大致意思
Handler允许你发送和处理 Message 和 Runnable相关联的一个线程消息队列(MessageQueue)。
每个Handler实例关联到单独的线程和这线程的消息队列。当你创建一个Handler,它会绑定到创建它的线程上和消息队列上。从这开始,它会将消息和可运行的消息传递给消息队列并执行他们从消息队列中出来。

从上面可以得到结论:

7个构造函数

image.png

其实可以归类:

image.png

上图是其中一个构造函数,没有Looper的构造函数都会最终走该构造函数。可以看到,代码会获取Looper;如果Looper为空就会报错。通常我们在主线程创建Handler都无需Looper,是因为安卓系统帮我们干了这件事。其实主线程整个消息机制已经存在了。从另外一个侧面来说,说明一个情况,就是一个线程可以有多个Handler
另外Looper为空报错其实就是我们工作线程没有构建Looper。这里先说一个结论每个线程就只有一个Looper后面会从代码中获知。

看看构造函数其余的两个参数

dispatchMessage(Message msg)方法

既然说到mCallback ,就看看这个方法;

image.png
其实就是当Message没有callback参数时即回调方法(同下面两个方法一样用于处理消息)就会看Handler是用Runnable还是handleMessage()方法。
从这个方法可以得出结论:
Handler处理消息可以通过上面三种途径

构造方法和处理消息说完其余剩下就是:

获取消息

image.png

上面就是获取Message的方法。
其实都没有什么特别,都是基于Message的参数不同获取不同类型的Message。

发送消息

image.png
image.png

其实这堆方法最终都是走向同一个方法:


image.png

可以看到mAsynchronous,该值为Message设置了true标记。留到Message模块看。
看到最终是调用MessageQueue的enqueueMessage。(在MessageQueue类详说)

注意两个方法:

image.png
image.png
好吧,其实就是一个方法:sendMessageAtFrontOfQueue()
该方法把uptimeMillis这个参数都设置为0,其余方法不是这样子的。这个要记住,在MessageQueue处理是不一样的。其实通过它的方法名就大概能够猜出什么意思:
image.png
这种方法只适用于非常特殊的情况,导致消息队列饥饿。这个直译是这样。后面会涉及到。

runWithScissors()该方法单独说下

image.png

一般在应用上好少用到这个方法;其实它的应用场景是同一个Looper的情况下,即同一个线程中。执行完Runnable就返回true。如果不同线程下,看BlockingRunnable:
看传入来的timeout时间,再执行Runnable。说白了,在不同线程的情况下就是等runnable执行完另外一个线程就会往下走。

Handler类总结

下面说Looper

Looper

可以看到Handler是这样获取looper,这是基于在主线程上。


image.png

而在工作线程上:


image.png
这是Looper的官方注解,增加两段代码:
Looper.prepare();

Looper.loop();

Looper.prepare();

image.png
这里明显有个地雷就是prepare()方法只能调用一次。
看看它抛出的异常就不用解释吧。每个线程只能一个Looper,这样就保证Looper在每个线程中单例。
这里有个小知识点就是
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
就是它来保证每个线程都是不同的Looper,用法像Map集合。它就是通过记录不同线程设置一个新的Looper,达到各个线程的单例Looper.其实底层就是key存储当前线程,value存储Looper。

Looper.loop();

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     * 在当前线程运行MessageQueue队列
     * 在循环loop最后,一定要调用quit();主线程不允许调用退出方法
     */
    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 {
                //Handler 派发消息,即调用Handler的handleMessage或Runnable
                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);
            }
            //回收Message
            msg.recycleUnchecked();
        }
    }

流程都在注释中说清楚。大概就是通过死循环不停地轮询MessageQueue队列。当发现Message为null就退出这个死循环。Message回收对象

Looper总结

Message

其实Message的核心就两个方法:
obtain()

image.png
recycleUnchecked()
image.png
其实看到这两个方法的实现,就看到Message基本上就没有什么池。只是Message有个变量next又是Message,不断通过给临时变量m,变换着sPool和next。MAX_POOL_SIZE即50

MessageQueue

MessageQueue放到最后说,主要其实整个消息机制最核心就是MessageQueue。但你看源码又会发现所谓队列并没有出现集合来管理Message,就好像上面的Message,变换着Message的next变量。
这里要说明一下,由于MessageQueue涉及到一些调用native地方,由于知识有限所以都不会讲那些内容;不过那些方法主要是用于处理死循环用的,由于死循环涉及到性能方面。所以当没有消息处理的时候会调用native方面代码

next()方法

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

next()方法就是循环Message;
先处理Message没有Handler的情况,将Message属于Asynchronous的消息放到前面其实就是插队。前面handler构造函数的时候已经说过,Asynchronous参数就是在这里起作用了。


image.png

这里的else部分就属于正常情况,处理Message;


image.png
这里Looper调用了quit()或quitSafely()就会将后面的消息清除掉。quitSafely()就是将已经发送了的但还没有到的都会处理掉再退出。而quite()就全部消息都清除掉
image.png
这里说明一下,在MessageQueue,决定这个死循环退出

enqueueMessage(Message msg, long when)

该方法就是Handler发送消息的,刚刚的next()是迭代消息的。

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

这里贴出核心代码:其实就两种情况,else是正常情况。而if是处理Handler的sendMessageAtFrontOfQueue(Message msg) ;上面说Handler时候说过它与其他方法不同就是直接传0过去,不是基于SystemClock.uptimeMillis(),而MessageQueue是基于此时间来处理消息队列的。所以将0的放在当前处理的位置。而需要处理的那个被忽略了。所以官网注释说sendMessageAtFrontOfQueue是会饿死消息队列的。原因在这里。

MessageQueue总结

其实MessageQueue是整个消息机制最核心的。消息的迭代和发送消息都在这里。
有些代码没有提及主要是不是该篇的重点。以后涉及到这方面的内容会再提及。

最后放一张消息机制的图,其实这图和网上的基本一致:


Handler.jpg

整个Android的消息机制算是比较简单的,起码你可以通过代码看见整个机制的全貌。
通过阅读源码可以理解到底层原理的同时亦都是学习与模仿人家代码风格的一种途径。

上一篇 下一篇

猜你喜欢

热点阅读