Android 开发艺术探索读书笔记 10 -- Android 的消息机制

本篇文章主要介绍以下几个知识点:

  • Android 的消息机制概述
  • Android 的消息机制分析
  • 主线程的消息循环
hello,夏天 (图片来源于网络)

Android 的消息机制主要是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueueLooper 的支撑。

MessageQueue是消息队列,其内部存储了一组消息,以队列的形式对外提供插入和删除的工作。(采用单链表的数据结构来存储消息列表

Looper是消息循环,以无限循环的形式去查找是否有消息,若有消息就处理,否则一直等待。

10.1 Android 的消息机制概述

Handler 的主要作用是将一个任务切换到某个指定的线程中去执行,Android 提供这个功能主要是为了解决在子线程中无法访问 UI 的矛盾。


问题 1:Android 为什么要提供 Handler 这个功能?

答:Android 规定访问 UI 只能在主线程中进行,若在子线程中会抛出异常:

void checkThread(){
    if(mThread != Thread.currentThread()){
        throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
    }
}

但 Android 又不建议在主线程中进行耗时操作(会导致ANR),从而提供 Handler 解决上面的矛盾。

问题 2:不允许在子线程中访问 UI ?

答:Android 的 UI 控件不是线程安全的。


Handler 的工作原理:

1. Handler 创建时会采用当前线程的 Looper 来构建内部的消息循环系统。

2. 通过Handlerpost 方法将一个 Runnable 投递到 Handler 内部的 Looper 中去处理,或通过 Handlersend 方法发消息到 Looper 中去处理(post 方法最终也是通过 send 方法来完成)。

3. Handlersend 方法被调用时,它会调用 MessageQueueenqueueMessage 方法将消息放入消息队列中,Looper 发现新消息时会处理,最终消息中的 RunnableHandlerhandleMessage 就会被调用。

Handler 的工作过程

10.2 Android 的消息机制分析

10.2.1 ThreadLocal 的工作原理

ThreadLocal 是一个线程内部的数据存储类,通过它可在指定的线程中存储数据,数据存储后,只能在指定线程中可获取存储的数据。

ThreadLocal 的使用场景:当某些数据是以线程为作用域并且不同线程具有不同的数据副本时;复杂逻辑下的对象传递。


下面举个例子来演示 ThreadLocal 的含义。

首先定义一个 Boolean 类型的 ThreadLocal 对象如下:

private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

然后分别在主线程、子线程1、子线程2 中设置和访问它的值:

       // 在主线程中设为 true
        mBooleanThreadLocal.set(true);
        LogUtils.e(TAG, "[Thread#main]mBooleanThreadLocal = " + mBooleanThreadLocal.get());

        new Thread("Thread#1"){
            @Override
            public void run() {
                // 在子线程1中设为 false
                mBooleanThreadLocal.set(false);
                LogUtils.e(TAG, "[Thread#1]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                // 在子线程2中不设置
                LogUtils.e(TAG, "[Thread#2]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
            }
        }.start();

运行效果如下:

运行打印的日志

从上面的日志可看出,虽然在不同线程访问的是同一个 ThreadLocal 对象,但获取到的值却是不一样的,这就是它的奇妙之处。

上面效果,是因为不同线程访问同一个 ThreadLocalget 方法,ThreadLocal 内部会从各自的线程中取出一个数组,再从数组中去查找出对应的 value 值。


下面分析 ThreadLocal 的内部实现。

ThreadLocal 是一个泛型类,其定义为public class ThreadLocal<T>,只要清楚它的 getset 方法就可明白其工作原理。

有兴趣的可以去看看其源码。。。

结论:在不同线程中访问同一个 ThreadLocalgetset 方法,其所做的读写操作仅限于各自线程的内部,从而使 ThreadLocal 可在多个线程中互不干扰的存储和修改数据。

10.2.2 消息队列的工作原理

MessageQueue 含两个操作:插入 enqueueMessage 和读取 next (读取操作本身伴随着删除操作)。

首先看其 enqueueMessage 实现:

    boolean enqueueMessage(Message msg, long when) {
        . . .
        synchronized (this) {
            . . .
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

enqueueMessage 实现来看,其主要操作是单链表的插入操作,下面看 next 的实现:

    Message next() {
        . . .
        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;
                }
               . . .
            }
            . . .
        }
    }

可以发现 next 是一个无限循环的方法,若消息队列中无消息, next 方法会一直阻塞在这里。当有新消息时,next 方法会返回这条消息并将其从单链表中移除。

10.2.3 Looper 的工作原理

Looper 扮演着消息循环的角色,会不停地从 MessageQueue 中查看是否有新消息,若有会立刻处理,否则一直阻塞在那里。

首先看一下其构造方法:

    private Looper(boolean quitAllowed) {
        // 创建一个消息队列 MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        // 将当前线程的对象保存起来
        mThread = Thread.currentThread();
    }

接着看一下其最重要的一个方法 loop,只有调用了 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();
        }
    }

loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueuenext 方法返回了 null。

MessageQueue 返回了新消息,Looper 就会处理这条消息:

msg.target.dispatchMessage(msg)

这样 Handler 发送的消息最终又交给它的 dispatchMessage 方法来处理了。


Handler 的工作需要 Looper,没有 Looper 的线程就会报错,那如何为一个线程创建 Looper 呢?

其实很简单,如下:

new Thread(){
    @Ovrttide
    public void run(){
        // 为当前线程创建一个 Looper
        Looper.prepare();
        Handler handler = new Handler();
        // 开启消息循环
        Looper.loop();
    }
}.start();

Looper 除了 prepare 外,还提供了 prepareMainLooper 方法给主线程创建 Looper,其本质也是通过 prepare 实现的。

Looper 提供了 getMainLooper,可以在任何地方获取到主线程的 Looper

Looper 提供两种方式退出:quit(直接退出)、quitSafely(设定一个退出标记,消息处理完毕后才安全退出)。

建议:不需要的时候终止 Looper


10.2.4 Handler 的工作原理

Handler 的工作主要包含消息的发送和接收过程。

消息发送可通过 post(最终也是通过 send 完成) 及 send 方法来实现。其过程如下:

    public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    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) {
        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) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以发现,Handler 发消息时向消息队列中插入一条消息,MessageQueuenext 方法返回这条消息给 LooperLooper 收到消息并处理再交由 HandlerdispatchMessage 方法进入处理消息阶段:

    public void dispatchMessage(Message msg) {
        // 1. 检查 Message 的 callback 是否为 null
        // 不为 null 就通过 handleCallback 来处理消息
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 2. 检查 mCallback 是否为 null
            // 不为 null 就调用其 handleMessage 来处理消息
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 3. 调用 Handler 的 handleMessage 来处理消息
            handleMessage(msg);
        }
    }

其中 handleCallback 逻辑如下:

private static void handleCallback(Message message){
    message.callback.run();
}

其中 mCallback 是个接口,定义如下:

/**
 *  可以用来创建一个 Handler 实例但不需要派生其子类
 */
public interface Callback{
    public boolean handleMessage(Message msg);
}

综上,Handler 处理消息过程可归纳成如下:

Handler 消息处理流程图

Handler 的构造方法很多,它的一个默认构造方法 public Handler() 会调用下面的构造方法:

    public Handler(Callback callback, boolean async) {
        . . .
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            // 若当前线程无 Looper 的话就会抛出异常
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

上面解释了在没有 Looper 的子线程中创建 Handler 会报错的原因。

10.3 主线程的消息循环

Android 的主线程就是 ActivityThread,其入口是在 main 方法中通过 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 来开启主线程的消息循环,如下:

public static void main(String[] args) {
        . . .
        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");
    }

主线程的消息循环开始后,ActivityThread 还需要一个 ActivityThread.H (内部定义了一组消息类型) 来和消息队列进行交互。

ActivityThread 通过 ApplicationThreadAMS 进行线程间通信的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,然后ApplicationThread 会向 H 发送消息,H 收到消息后将 ApplicationThread 中的逻辑切换到 ActivityThread (主线程)中去执行。

以上便是主线程的消息循环模型。

本篇文章就介绍到这。

推荐阅读更多精彩内容