Android——消息机制

主要介绍内容:

  • ThreadLocal 的工作原理
  • 消息队列的工作原理
  • Looper 的工作原理
  • Handler 的工作原理
  • 主线程的消息循环

提到消息机制大家应该都不陌生,在日常开发中不可避免地要涉及这方面的内容。从开发角度来说,Handler 是 Android 消息机制的上层接口,这使得在开发过程中只需要和 Handler 交互即可。Handler 的使用过程很简单,通过它可以很轻松地将一个任务切换到 Handler 所在的线程中去执行。很多人认为 Handler 的作用是更新 UI,这的确没错,但是更新 UI 仅仅是 Handler 的一个特殊的使用场景。具体来说是这样的:有时候需要在子线程中进行耗时的 I/O 操作,可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在 UI 上做一些改变,由于 Android 开发规范的限制,我们并不能在子线程中访问 UI 控件,否则就会触发程序异常,这个时候通过 Handler 就可以将更新 UI 的操作切换到主线程中执行。因此,本质上来说,Handler 并不是专门用于更新 UI 的,它只是常被开发者用来更新 UI。

Android 的消息机制主要是指 Handler 的运行机制,Handler 的运行需要底层 MessageQueue 和 Looper 的支撑。 MessageQueue 的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。Looper 的中文翻译为循环,在这里可以理解为消息循环。由于 MessageQueue 只是一个消息的存储单元,它不能去处理消息,而 Looper 就填补了这个功能, Looper 会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。Looper中还有一个特殊的概念,那就是 ThreadLocal,ThreadLocal 并不是线程,它的作用是可以在每个线程中存储数据,我们知道,Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,那么 Handler 内部如何获取到当前线程的 Looper 呢?这就要使用 ThreadLocal 了,ThreadLocal 可以在不同的线程中互不干扰地存储并提供数据,通过 ThreadLocal 可以轻松获取每个线程的 Looper。当然需要注意的是,线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建 Looper,我们经常提到的主线程,也叫 UI 线程,它就是ActivityThread,ActivityThread 被创建时就会初始化 Looper,这也是在主线程中默认可以使用 Handler的原因。

前面提到,Android 的消息机制主要是指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作过程,这三者实际上是一个整体,只不过我们在开发过程中比较多地接触到 Handler 而已。Handler 的主要作用是将一个任务切换到某个指定的线程中去执行,那么 Android 为什么要提供这个功能呢?或者说 Android 为什么需要提供在某个具体的线程中执行任务的这种功能呢?这是因为 Android 规定访问 UI 只能在主线程中进行,如果在子线程中访问 UI,那么程序就会抛出异常。ViewRootImpl 对 UI 的操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread 方法来完成的,如下所示:

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

针对 checkThread 方法中抛出的异常信息,相信读者在开发中都曾经遇到过。由于这一点的限制,导致必须在主线程中访问 UI,但是 Android 又建议不要再主线程中进行耗时操作,否则会导致程序无法响应即 ANR。考虑一种情况,假如我们需要从服务端拉取一些信息并将其显示在 UI 上,这个时候必须在子线程中进行拉取工作,拉取完毕后又不能在子线程中直接访问 UI,如果没有 Handler,那么我们的确没有办法将访问 UI 的工作切换到主线程中去执行。因此,系统之所以提供 Handler,主要原因就是为了解决在子线程中无法访问 UI 的矛盾。

这里再延伸一点,系统为什么不允许在子线程中访问 UI 呢? 这是因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态,那为什么系统不对 UI 控件的访问加上锁机制呢?缺点有两个:首先加上锁机制会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理 UI 操作,对于开发者来说也不是很麻烦,只是需要通过 Handler 切换一下 UI 访问的执行线程即可。

Handler 的使用方法这里就不做介绍了,这里描述一下 Handler 的工作原理。Handler 创建时会采用当前线程的 Looper 来构建内部的消息循环系统,如果当前线程没有 Looper,那么就会保存,如下所示:

E/AndroidRuntime(27568) : FATAL EXECPTION: Thread-43484
E/AndroidRuntime(27568) : java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
E/AndroidRuntime(27568) : at android.os.Handler.<init>(Handler.java:121)
E/AndroidRuntime(27568) : at con.ryg.chapter_10.TestActivity$3.run(TestActivity.java:57)

如何解决上述的问题呢?其实很简单,只需要为当前线程创建 Looper 即可,或者在一个有 Looper 的线程中创建 Handler 也行,具体解决方案我们会在 Looper 的工作原理 中进行介绍。

Handler 创建完毕后,这个时候其内部的 Looper 以及 MessageQueue 就可以 和 Handler 一起协同工作了,然后通过 Handler 的 post 方法将一个 Runnable 投递到 Handler 内部的 Looper 中去处理,也可以通过 Handler 的 send 方法发送一个消息,这个消息同样会在 Looper 中去处理。其实 post 方法最终也是通过 send 方法来完成的,接下来主要来看一下 send 方法的工作过程。当 Handler 的 send 方法被调用时,它会调用 MessageQueue 的 enqueueMessage 方法将这个消息放入消息队列中,然后 Looper 发现有新消息到来时,就会处理这个消息,最终消息中的 Runnable 或者 Handler 的 handleMessage 方法会被调用。注意 Looper 是运行在创建 Handler 所在的线程中的,这样以来 Handler 中的额业务逻辑就被切换到创建 Handler 所在的线程中去执行了,这个过程可以一张图来表示,图如下:

这里写图片描述

这只是一张 Handler 基本工作原理图,更详细的工作原理图会在介绍完 Handler 的工作原理后给出

ThreadLocal 的工作原理

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。在日常开发中用到 ThreadLocal 的地方较少,但是在某些特殊场景下,通过 ThreadLocal 可以轻松地实现一些看起来很复杂的功能,这一点在 Android 的源码中也有所体现,比如 Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。具体到 ThreadLocal 的使用场景,这个不好统一来描述,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。比如对于 Handler 来说,它需要获取当前线程的 Looper,很显然 Looper 的作用域就是线程并且不同线程具有不同的 Looper,这个时候通过 ThreadLocal 就可以轻松实现 Looper 在线程中的存取。如果不采用 ThreadLocal,那么系统就必须提供一个全局的哈希表共 Handler 查找指定线程的 Looper,这样以来就必须提供一个类似于 LooperManager 的类了,但是系统并没有这么做而是选择了 ThreadLocal,这就是 ThreadLocal 的好处。

ThreadLocal 另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实这时就可以采用 ThreadLocal,采用 ThreadLocal 可以让监听器作为线程内的全局对象而存在,在线程内部只要通过 get 方法就可以获取到监听器。如果不采用 ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法就是将监听器通过参数的形式在函数调用栈中进行传递;第二种方法就是将监听器作为静态变量供线程访问。上述这两种方法都是有局限性的。第一种方法的问题是当函数调用栈很深的时候,通过函数参数来传递监听器这几乎是不可接受的,这会让程序的设计看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有 10 个线程在并发执行呢? 提供 10 个静态的监听器对象?这显然是不可思议的,而采用 ThreadLocal,每个监听器对象都在自己的线程内部存储,根本就不会有方法 2 的这种问题。

介绍了那么多 ThreadLocal 的知识,可能还是有点抽象,下面通过实际的例子来演示 ThreadLocal 的真正含义。首先定义一个 ThreadLocal 对象,这里选择 Boolean 类型的,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        show();
    }

    private void show() {
        Log.e("MainActivity", "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
        mBooleanThreadLocal.set(true);
        Log.e("MainActivity", "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
        new Thread(){
            @Override
            public void run() {
                Log.e("MainActivity", "[Thread#Thread 1 ]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
                mBooleanThreadLocal.set(false);
                Log.e("MainActivity", "[Thread#Thread 1 ]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                Log.e("MainActivity", "[Thread#Thread 2 ]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();
    }
}

在上面的代码中,在主线程中设置 mBooleanThreadLocal 的值为 true, 在子线程 1 中设置 mBooleanThreadLocal 的置为 false,在子线程 2 中不设置 mBooleanThreadLocal 的值。然后分别在 3 个线程中通过 get 方法获取 mBooleanThreadLocal 的值,根据前面对 ThreadLocal的描述,这个时候,主线程中应该是 true,子线程 1 中 未设置之前应该为 null,设置之后应该为 false,而子线程 2 中由于没有设置值,所以应该是 null。运行程序,日志如下所示:

10-13 13:44:48.079 8121-8121/? E/MainActivity: [Thread#main]mBooleanThreadLocal=null
10-13 13:44:48.080 8121-8121/? E/MainActivity: [Thread#main]mBooleanThreadLocal=true
10-13 13:44:48.082 8121-8198/? E/MainActivity: [Thread#Thread 1 ]mBooleanThreadLocal=null
10-13 13:44:48.082 8121-8198/? E/MainActivity: [Thread#Thread 1 ]mBooleanThreadLocal=false
10-13 13:44:48.082 8121-8199/? E/MainActivity: [Thread#Thread 2 ]mBooleanThreadLocal=null

从上面日志可以看出,虽然在不同线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 获取到的值却是不一样的,这就是 ThreadLocal 的奇妙之处。结合这个例子然后在看一遍前面对 ThreadLocal 的两个使用场景的理论分析,我们应该就能比较好地理解 ThreadLocal 的使用方法了。ThreadLocal 之所以有这么奇妙的效果,是因为不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后在从数组中根据当前 ThreadLocal 的索引去查出对应的 Value 值。很显然,不同线程中的数组是不同的,这就是为什么通过 ThreadLocal 可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

对 ThreadLocal 的使用方法和工作过程介绍后,下面分析 ThreadLocal 的内部实现,ThreadLocal 是一个泛型类,它的定义为 public class ThreadLocal ,只要弄清楚 ThreadLocal 的 get 和 set 方法就可以明白它的工作原理。

首先看 ThreadLocal 的 set 方法,如下所示:

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }


    Values values(Thread current) {
        return current.localValues;
    }


    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

从上面的 set 方法中,首先会通过 values 方法来获取当前线程中的 ThreadLocal 数据,如何获取呢?其实获取的方式也很简单,在 Thread 类的内部有一个成员专门用于存储线程的 ThreadLocal 的数据:ThreadLocal.Values localValues,因此获取当前线程的 ThreadLocal 数据就变得异常简单了。如果 localValues 的值为null,那么就需要对其进行初始化,初始化后再将 ThreadLocal 的值进行存储。下面开一下 ThreadLocal 的值到底是如何在 localValues 中进行存储的。在 localValues 内部有一个数组:private Object[] table,ThreadLocal 的值就存在这个 table 数组中。下面看一下 localValues 是如何使用 put 方法将 ThreadLocal 的值存储到 table 数组中的,代码如下所示:

void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

上面的代码实现了数据的存储过程,这里不去分析它的具体算法,但是我们可以得出一个存储规则:那就是 ThreadLocal 的值在 table 数组中的存储位置总是为 ThreadLocal 的 reference 字段所标识的对象的下一个位置,比如 ThreadLocal 的 reference 对象在 table 数组中的索引为 index,那么 ThreadLocal 的值在 table 数组中的索引就是 index + 1。最终 ThreadLocal 的值将被存储在 table 数组中:table[index + 1] = value。

上面分析了 ThreadLocal 的 set 方法,这里分析它的 get 方法,如下所示:

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

    Values values(Thread current) {
        return current.localValues;
    }

    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

可以发现,ThreadLocal 的 get 方法的逻辑也比较清晰,它同样是取出当前线程的 localValues 对象,如果这个对象为 null,那就返回初始值,如果 localValues 对象不为null,那就取出它的 table 数组并找出 ThreadLocal 的 reference 对象所在 table 数组中的位置,然后 table 数组总的下一个位置所存储的数据就是 ThreadLocal 的值。

从 ThreadLocal 的 set 和 get 方法可以看出,它们所操作的都是当前线程的 localValues 对象的 table 数组,因此在不同的线程中访问同一个 ThreadLocal 的 set 和 get 方法,它们对 ThreadLocal 所做的读/写 操作仅限于各自线程的内部,这就是为什么 ThreadLocal 可以在多个线程中互不干扰地存储和修改数据,理解了 ThreadLocal 的实现方式有助于理解 Looper 的工作原理。

消息队列的工作原理(MessageQueue)

消息队列在 Android 中指的是 MessageQueue,MessageQueue 主要包含两个操作:插入 和 读取。读取操作本身会伴随着删除操作,插入 和 读取 对应的方法分别为 enqueueMessage 和 next ,其中 enqueueMessage 的作用是往消息队列中插入一条消息,而 next 的作用是从消息队列中取出一条消息并将其从消息队列中移除。尽管 MessageQueue 叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。下面主要看一下它的 enqueueMessage 和 next 方法的实现,enqueueMessage 的源码如下所示:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            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 方法的实现,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 方法是一个无限循环的方法,如果消息队列中没有消息,那么 next 方法会一直阻塞在这里。当有新消息到来时,next 方法会返回这条消息并将其从单链表中移除。

Looper 的工作原理

Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在那里。首先看一下它的构造方法,在构造方法中它会创建一个 MessageQueue 即消息队列,然后将当前线程的对象保存起来,代码如下所示:

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

我们知道,Handler 的工作需要 Looper,没有 Looper 的线程就会报错,那么如何为一个线程创建 Looper 呢? 其实很简单,通过 Looper.prepare() 即可为当前线程创建一个 Looper,接着通过 Looper.loop() 来开启消息循环,如下所示:

        new Thread("Thread#2") {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();

Looper 的主要方法有如下几个:prepare()、prepare(boolean quitAllowed)、prepareMainLooper()、getMainLooper()、myLooper()、loop()、quit()、和 quitSafely() 这几个方法,下面我们分别介绍吧:

先看 prepare() 方法:

    public static void prepare() {
        prepare(true);
    }

其实就是调用了 prepare(boolean quitAllowed) 方法,prepare(boolean quitAllowed) 方法我们等会再看,先来看一下 prepareMainLooper() 方法的实现:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

可以看到 prepareMainLooper() 其实也是通过调用 prepare(boolean quitAllowed) 来完成的。那么 prepare(boolean quitAllowed) 具体做了什么操作呢?

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

可以看到其实就是调用了 sThreadLocal 的 set 方法并创建了一个 Looper 对象,就这么两行代码,还是接着来看看我们比较关心的 sThreadLocal 吧,看完这个我想大家马上就会明白了 ….

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

咦!这不是我们前面刚刚介绍过的 ThreadLocal 么? 到这里为止我想应该可以解开大家的疑惑了,咱们前面提到过“Looper 是运行在创建 Handler 所在的线程中的”而咱们的主线程的 Looper 其实是在 ActivityThread 中通过 prepare 方法来实现的,这样不就保证了 Handler、Looper、MessageQueue在同一个线程中了么? 为什么这么说呢?我们开看一下 Looper 的构造方法中具体做了什么操作:

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

代码很简单,就是创建了一个 MessageQueue 对象,也就是说当前线程的 Looper 保存了 一个 MessageQueue 消息队列,也就是说不同线程中的 Handler 有自己的 Looper 以及 MessageQueue 消息队列。

Looper 也是可以退出的,Looper 提供了 quit() 和 quitSafely() 来退出一个 Looper,二者的区别是:quit 会直接退出 Looper 而 quitSafely 只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。Looper 退出后,通过 Handler 发送的消息会失败,这个时候 Handler 的 send 方法会返回 false。在子线程中,如果手动为其创建了 Looper,那么在所有的事情完成以后应该调用 quit 方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出 Looper 以后,这个线程就会立即终止,因此建议在不需要的时候终止 Looper.

接下来我们再来看看 Looper 中最重要的一个方法 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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

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


    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

首先调用了 myLooper() 方法,接着从 返回的 Looper 对象中获取消息队列(MessageQueue),再接着就是循环消息了,loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回了 null。当 Looper 的 quit 方法被调用时,Looper 就会调用 MessageQueue 的 quit 或者 quitSafely 方法来通知消息队列退出,当消息队列被标记为退出状态时,它的 next 方法就会返回null。也就是说,Looper 必须退出,否则 loop 方法就会无限循环下去。loop 方法会调用 MessageQueue 的 next 方法来获取新消息,而 next 方法是一个阻塞操作,当没有消息时,next 方法会一直阻塞在那里,这也导致 loop 方法一直阻塞在那里。如果 MessageQueue 的 next 方法返回了新消息,Looper 就会处理这条消息: msg.target.dispatchMessage(msg),这里的 msg.target 是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage 方法来处理了。但是这里不同的是,Handler 的 dispatchMessage 方法是在创建 Handler 时所使用的的 Looper 中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。

Handler 的工作原理

Handler 的工作主要包含消息的发送和接受过程。消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现,post 的一系列方法最终是通过 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;   //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);
    }

//mQueue的获取
    public Handler() {
        this(null, false);
    }

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

可以发现,Handler 发送消息的过程仅仅是通过调用 MessageQueue 的 enqueueMessage 方法向消息队列中插入了一条消息,MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 收到消息后就开始处理了,最终消息由 Looper 交由 Handler 处理,即 Handler 的 dispatchMessage 方法会被调用,这时 Handler 就进入了处理消息的阶段。dispatchMessage 的实现如下所示:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Handler 处理消息的过程如下:

首先,检查 Message 的 callback 是否为null,不为 null 就通过 handleCallback 来处理消息。Message 的 callback 是一个 Runnable 对象,实际上就是 Handler 的 post 方法所传递的 Runnable 参数。handleCallback 的逻辑也是很简单,如下所示:

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

其次,检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handleMessage 方法来处理消息。 Callback 是个接口,它的定义如下:

    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     *
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

通过 Callback 可以采用如下方法来创建 Handler 对象: Handler handler = new Handler(callback)。那么 Callback 的意义是什么呢?源码里面的注释已经做了说明:可以用来创建一个 Handler 的实例但不需要派生 Handler 的子类。在日常开发中,创建 Handler 最常见的方式就是派生一个 Handler 的子类并重写其 handleMessage 方法来处理具体的消息,而 Callback 给我们提供了另外一种使用 Handler 的方式,当我们不想派生子类时,就可以通过 Callback 来实现。

最后,调用 Handler 的 handleMessage 方法来处理消息。

Handler 还有一个特殊的构造方法,那就是通过一个特定的 Looper 来构造 Handler,它的实现如下所示:

    public Handler(Looper looper) {
        this(looper, null, false);
    }

下面看一下 Handler 的一个默认构造方法,public Handler(),这个构造方法会调用下面的构造方法。很明显,如果当前线程没有 Looper 的话,就会抛出 “Can’t create handler inside thread that has not called Looper.prepare()”这个异常,这也解释了在没有 Looper 的子线程中创建 Handler 会引发程序异常的原因。

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {

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

主线程的消息循环

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

public static void main(String[] args){
    ...
    Process.setArgVO("<pre-initialized>");

    Looper.prepareMainLooper();

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

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

    AsyncTask.init();

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

    Looper.loop();

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

主线程的消息循环开始了以后,ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程,如下所示:

private class H extends Handler{
    public static final int LAUNCH_ACTIVITY = 100;
    public static final int PAUSE_ACTIVITY = 101;
    public static final int PAUSE_ACTIVITY_FINISHING = 102;
    public static final int STOP_ACTIVITY_SHOW = 103;
    public static final int STOP_ACTIVITY_HIDE = 104;
    public static final int SHOW_WINDOW = 105;
    public static final int HIDE_WINDOW = 106;
    public static final int RESUME_ACTIVITY = 107;
    public static final int SEND_RESULT = 108;
    public static final int DESTROY_ACTIVITY = 109;
    public static final int BIND_APPLICATION = 110;
    public static final int EXIT_APPLICATION = 111;
    public static final int NEW_INTENT = 112;
    public static final int REVEIVER = 113;
    public static final int CREATE_SERVICE = 114;
    public static final int SERVICE_ARGS = 115;
    public static final int STOP_SERVICE = 116;
}

ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

好了,没想到会写这么多,累尿了。下面附上一张完整的 消息机制工作图完事:

这里写图片描述

图中蓝色数字标记为执行流程……..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值