Handler机制之消息传递过程源码分析

你真的懂Handler是用来干什么的吗

自从 RxJava 大火之后成为大部分 Android 开发者进行调度线程异步处理的首选工具,那么在 RxJava 还没出现的年代 Android 程序员是怎么切换线程完成异步操作的呢?Handler 就是大家常用的工具,根据大神任玉刚的《Android开发艺术探索》中所描述的Handler 机制是 Android 提供的消息机制,通过它可以轻松地将一个任务切换到 Handler 所在的线程中执行。也就是说我们可以在任何一个线程创建一套 Handler 机制,从而可以轻松调度到这个线程中。但是由于大部分开发者都把 Handler 当做切换到主线程更新UI的工具,所以开发者经常在面试中回答 Handler 是干什么的时候回答是用来更新 UI的。

Handler机制结构

  • Handler

处理消息和发送消息的核心,创建Handler的线程就是处理消息的线程,且跟Looper和消息队列相关联Handler机制才能运作

  • Looper

一个挂在消息队列上的钩子,消息队列有消息就立马把消息勾走给丢给Handler处理,处于Handler所在线程中

  • MessageQueue 消息队列

维护一个单链表,主要包含添加消息到队列和从队列读取并删除一条消息

  • Message 消息

消息载体

Handler机制消息传递流程

消息被Handler发送插入消息队列 -> 消息被Looper勾走传给Handler -> Handler处理消息

源码分析消息传递过程

接下来通过查看上面四个关键类的源码了解消息的传递过程。

首先我们先来看看Handler发送消息到消息队列的过程:

// Handler.class
public final boolean sendMessage(Message msg){   
    return sendMessageDelayed(msg, 0);
}

public final boolean post(Runnable r){
    // 把需要 Post 的 runnable 包装为 Message 之后再发送
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    // 从缓存池中获取 Message
    Message m = Message.obtain();
    // 把 runnable 设置为 Message 的 callback 后返回 Message
    m.callback = r;
    return m;
}

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // mQueue : 该 Handler 所在线程的消息队列
    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, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 设置 target (目标Handler) 为自己,因为是自己发送最后自己接受的嘛
    msg.target = this;
    // 设置是否异步
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 调用消息队列的enqueueMessage()并返回返回值
    return queue.enqueueMessage(msg, uptimeMillis);
}

根据Handler的源码有两种发送消息到消息队列的方式:

  • 直接发送一个Message到消息队列中

  • post(runnable) 一个runnable然后用getPostMessage(runnable)包装为Message后发送到消息队列,注意一下runnable被赋值给了Message.callback,原因后面解释。

两种方式都可以设置延迟时间且最终会调用Handler.enqueueMessage()把消息插入到消息队列中。接下来我们可以看看消息队列的源码看看插入消息的过程,关键点已做注释:

// MessageQueue.class
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) { // 没有设置目标Handler的话报错
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) { // 如果该Message被标记为 in-use 就报错
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) { // 如果 mQuitting = true 表示 Handler 机制要被终止了,报错 
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            // 回收消息后返回 false
            msg.recycle();
            return false;
        }
        // 标记该 Message 为 in-use 状态
        msg.markInUse();
        // 设置延迟时间
        msg.when = when;
        // mMessages 是消息队列维护的单链表表头
        Message p = mMessages;
        boolean needWake;
        // 如果插入的消息延迟时间为0或者比表头消息的延迟时间小,就以头部插入的方式插入到消息队列中
        // 否则就按延时时间的大小插入到链表中
        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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        // 唤醒 native
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    // 插入后返回 true
    return true;
}

根据源码消息队列其实维护了一个单链表,且按照延迟时间从小到大排序的。

那么现在我们了解到了消息插入过程,下面我们了解消息被取出的过程:

上面介绍Looper的时候把它形容为一个钩子钩在消息队列上,下面我们先简单了解Looper

Android中每个线程创建的时候是没有Looper的,需要下面两行代码运行Looper

Looper.prepare();
// 如果再创建一个Handler,那么这个线程的 Handler机制就可以完整了
// Handler handler = new Handler();
Looper.loop();

看看Looper代码这两个方法做了什么:

// Looper.class
public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
    // 如果sThreadLocal 已经存储了Looper,证明Looper已存在,报错
    if (sThreadLocal.get() != null) { 
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 否则new一个 Looper 并保存在 sThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
    
private Looper(boolean quitAllowed) {
    // Looper 构造方法创建消息队列
    mQueue = new MessageQueue(quitAllowed);
    // mThread 标记当前线程
    mThread = Thread.currentThread();
}
    
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 (;;) {
        // 调用消息队列 next()获取消息,如果队列中没有可以取的任务时会阻塞
        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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            // 使用消息的目标Handler的dispatchMessage()来分发消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

        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.prepare()看起

  • 内部调用了prepare(boolean quitAllowed),通过sThreadLocal获取该线程的Looper,在这里先解释下mThreadLocalThreadLocal对象,这个类很特别,可以存放一个指定类型的对象,但是不同线程通过get()获取的值是不同的,我们暂时不需要了解ThreadLocal的实现细节,所以在这里用ThreadLocal来保存每个线程的Looper,如果sThreadLocal.get() != null证明该线程已经存在Looper,一个线程只能和一个Looper关联,所以报错,否则创建Looper并保存在sThreadLocal中。
  • 查看Looper的构造函数发现消息队列是在创建Looper的时候创建并关联起来的,所以Looper消息队列都在创建Handler的线程中。

接下来看Looper.loop()

  • 这个方法是Handler机制的启动方法,内部其实是一个无限for循环,只有当调用消息队列的next()方法返回null的时候才会退出循环,并且终止Looper运作。如果大家好奇为什么平时使用Handler的时候这个for循环不会阻塞主线程的话,可以参考下 知乎 的回答
  • 消息队列中得到消息后Looper会用根据Message.target找到目标Handler,然后调用handler.dispatchMessage(msg)把消息传给Handler让其处理消息。
  • 消息被处理后回收消息,进入下一次for循环。

看到这里我们知道了Looper是怎么从消息队列中把消息一个个勾出来传给Handler处理的,那么回头我们看看消息队列next()做了什么:

// MessageQueue.class
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()的代码比较长,我们暂时不需要深入了解其实现细节,只需要了解大概流程即可:

  • 单链表表头消息为null时表示消息队列中没有消息,等待直到有消息
  • 如果表头消息延迟时间还没过证明当前消息还不能被处理,等待直到延迟时间过去
  • 确定表头消息可以被处理的时候把该消息从链表中脱离开,然后标记为 in-use 状态,然后返回该消息给Looper

最后我们了解Handler接收到消息之后是怎么处理的,看 Handler.dispatchMessage():

// Handler.class
public void dispatchMessage(Message msg) {
    if (msg.callback != null) { // post(runnable) 会走这里
        handleCallback(msg);
    } else {
        if (mCallback != null) { // 使用Handler(Callback callback, boolean async)创建Handler的时候会走这里
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 使用 new Hander() 的时候走这里
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
  • 还记得发送消息的时候其中一种方式是 post 一个 runnable 吗,且包装的时候把 runnable 赋值给消息的 callback ,原因现在终于清楚了,原来Handler接收到以 post 方式发送过来的消息时,会直接 run 这个 runnable,就达到了我们前面说的把一个任务传到Handler 所在线程中执行的效果了
  • 如果创建Handler的时候使用的构造方法是Handler(Callback callback, boolean async),就会回调callback.handleMessage(),如果使用的是Handler()的话就回调我们创建时覆写的handleMessage()

到现在我们已经把 Handler机制 中的消息传递过程通过查看源码了解一遍了。

如果想要了解Handler机制的终止流程的话可以到我的另一篇博客看看:Handler机制之Looper.quit()和Looper.quitsafely()

    原文作者:950061da8f79
    原文地址: https://www.jianshu.com/p/6437346d5799
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞