Android Handler-带你从源码理解消息机制

前言

在我们的Android日常开发中,我们经常会遇到一些比较耗时的I/O操作或者访问网络的操作,而我们都知道Android系统规定不能在主线程中执行以上等操作,所以我们只能开启一个子线程来执行这些操作;然而为了确保UI操作的安全性,Android系统又规定不得在非主线程中操作UI。那么当我们在一个子线程中操作完一个事件后想要通知主线程去更新UI元素的话该怎么办呢?Handler的出现就完美的解决了这个问题。

关于Handler

1.定义:

一个Handler允许你发送、处理和运行与线程相关的对象。它的主要作用有两个,第一,排入一个消息并且在未来的某个时间点上运行处理这个消息;第二,在不同于你所处的线程上排入一个将要被执行的操作。

2.基本用法:

关于Handler的用法,这里我列出一个最常见的使用方式,那就是在主线程中创建一个Handler,然后在子线程中发送消息给Handler,让Handler去处理消息。代码如下:

public class HandlerTest {
    private static final String TAG = "Handler";
    // 创建Handler对象处理Message消息
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d(TAG, msg.obj + "");
                    break;
                default:
                    break;
            }
        }
    };
    /**
     * 此方法暴露给外部Activity调用,内部开启一个子线程用于模拟执行耗时操作并通过handler发送一个Message消息
     */
    public void create() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 假设这里开始执行一段耗时操作
                // ==========
                // 耗时操作执行完毕,需要通知主线程更新UI
                Message message = Message.obtain();
                message.what = 1;
                message.obj = 100;
                handler.sendMessage(message);
            }
        }).start();
    }
}

打开某一个Activity创建HandlerTest对象,并调用其create() 方法,就会在控制台打印出一条100的日志。以上就是一个很常见的Handler的使用方式。

3.重要角色:

其实Handler之所以能完成发送消息,处理消息等一系列的操作,这和几个重要的“帮手”是分不开的。他们分别是Message,MessageQueue,Looper以及Handler,它们之间整体的协同工作流程可以比作成一个邮寄信件的过程。

3.1.Message:

定义:一个可以被发送给Handler的消息,它可以包含一段描述和具体的对象数据。
Message可以看成是每一封信件。

3.2.MessageQueue:

定义:一个持有将要被Looper分发出去的消息(Message)列表的底层阶级的类。
MessageQueue就像是一个邮箱,里面存放着信件(Message)。

3.3. Looper:

定义:一个用于为一个线程执行消息循环的类。
我们假设投递的邮箱是一个非常高级的邮箱,它的内部有一个可以自动将投递进来的信件取出并放置到邮递员取信区域的机器。当有信件投递进邮箱时,它就工作;当邮箱中没有信件时,它就暂停工作,直到有新的信件被投递进来后它再继续工作。而Looper就是这么一个非常“智能”的角色。

3.4.Handler:

定义这里就不再赘述,而其本身就是充当信使这么一个角色,它要发送和处理信件。用一张图概括他们之间协同工作的关系就如下图所示:


handler.png

4.走进源码:

这里我打算先把每一个“帮手”的实现原理(源码)先简单介绍下,最后再解读一个消息从创建到发送再到处理的整个过程。这样在理解整个过程中,就不会对某一个类的变量或者方法有疑问了。

4.1.Message:

关于Message,它是一个消息实体类。它的内部有几个比较重要的变量:

1.what:用来区分Message的一个标志。当handler处理消息的时候,知道是哪一个Message。
2.arg1和arg2:两个int类型的成员变量,当一个Message只需要携带少量整型值的时候可以用他们存储。
3.obj:可以被Message携带的任意类型的对象。(当用于跨进程通信时,如果它是一个framework层的类那么它一定不能为空)
4.data:存储数据的Bundle对象。
5.target:就是当前处理它的handler,根据字面意思就是目标的意思,创建一个Message的目的就是让handler处理它,这样更好理解些。
6.when:当前Message交付到目标handler的具体时间节点(后面会更详细的介绍)。
7.next:一个Message对象,可以理解为当前Message的下一个Message,和链表有些相似。
8.sPool:本身也是一个Message对象,根据命名我们也可以理解为一个消息池(后面在讲解Message的创建方式时会再次提到)。

在Message类的内部,提供了很多创建它的方法,这里我们看一下其中的一个obtain()方法的源码:

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 * 从一个全局池中返回一个Message实例,避免让我们在多种情况下创建新的对象实例。
 */
public static Message obtain() {
    synchronized (sPoolSync) { // 1.通过同步代码块的方式来保证线程操作消息池时的安全性。
        if (sPool != null) { // 2.判断sPool是否为空,如果不为空,直接复用sPool并返回。
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message(); // 3.如果sPool为空,那么就返回一个新的消息实例。
}

还有几个obtain()方法的重载,内部也是调用了obtain()方法;另外,Message还有一个无参的构造函数,但是源码中也在此方法的注释部分声明建议使用obtain()方法来获取一个Message对象,至于原因就是和obtain()方法的注释一样,避免创建多个对象实例浪费内存空间。另外,其内部还有些get、set方法,这些方法闻其名知其意,这里不过多描述。

4.2.MessageQueue:

关于MessageQueue,我们在调用handler发送和处理消息的时候并不会直接调用到它,而是在handler的内部才能看到它的身影。它的源码相对于Message来说还是较复杂的,但这里我们挑重点的说。其实MessageQueue在整个过程中的作用就是插入消息和取出消息,插入消息对应的方法是enqueueMessage方法,而取出消息对应的方法是next方法。在阅读这两个方法之前,我们先了解几个比较重要的成员变量。

4.2.1.mQuitAllowed

首先说到的是mQuitAllowed变量,这个变量的含义是当前的MessageQueue是否允许被退出。它在MessageQueue的构造方法中被赋值,其构造方法如下:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

而MessageQueue的构造方法是在Looper中被调用的,在Looper被创建的时候,它会同时创建一个MessageQueue。如果是主线程创建的Looper那么传入这个MessageQueue构造方法的参数就为false,如果是其他线程的话就为true(具体的源码实现后面在讲解Looper的源码时会看到)。

4.2.2.mQuitting

接下来是mQuitting变量,这个变量的含义是当前的MessageQueue是否可以退出了。它默认为false,只有当MessageQueue的quit方法被调用后才会置为true,quit方法如下所示:

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true; // 这是唯一可以将我置为true的地方
        if (safe) {
            removeAllFutureMessagesLocked(); // 清空所有的延迟消息
        } else {
            removeAllMessagesLocked(); // 清空所有的消息
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

这里要注意的是,千万不要把这个变量和刚刚说的mQuitAllowed变量弄混,它们两个从命名上很相似。但仔细看quit方法的第一行我们就可以知道,只有当mQuitAllowed变量为true的前提下,quit方法才得以正常的执行下去,mQuitting变量也才可以有机会被置为true;如果mQuitAllowed变量为false的话,那么会抛出非法状态异常。而这个quit方法是在Looper的quit方法或quiteSafely方法中被调用的。

4.2.3.mBlocked

接下来是mBlocked变量,它也是布尔类型的,它的含义是作为MessageQueue中next方法是否以一个非零的超时时长被阻塞在pollOnce()方法中的一个标志,简单的可以理解为当前消息队列是否被阻塞的标志。它的使用和赋值的地方就在enqueueMessage和next方法中,后面在讲解两个方法的时候会具体提到。

4.2.4.mMessages

最后说一下比较重要的mMessages变量,它是一个Message类型的变量。还记得刚刚我们介绍Message时说它里面有几个比较重要的变量,其中有一个叫next的变量,它也是一个Message类型的变量。而MessageQueue虽然被称作消息队列,但它的实质并不是一个队列,而是一个依靠Message自身的链表特性而连接起来的一个链表结构。在接下来要讲到的enqueueMessage方法中会更清晰的验证这一点。

4.2.5.enqueueMessage方法

现在,一起来看一下向队列中插入消息的方法。方法的源码如下(方法比较长,这里我在每一个操作的前面添加相应的解释):

/**
 * 方法的调用是在Handler中
 */
boolean enqueueMessage(Message msg, long when) {
    // 这里首先判断msg的target是否为空,如果为空,直接抛出异常(没有目标,就不知道最后要把你发送给谁)。
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 这里判断msg是否已经被使用,如果已经被使用,直接抛出异常(isInUse方法在Message源码中可以看到) 
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        /**
         * 这里用到了刚刚说到的mQuitting变量,如果发现mQuitting为true,就说明quit方法已经被顺利调用;
         * 当前的消息队列就不会再接收任何消息了,也就意味着这时再调用handler发送消息已经没有用了;
         * 同时抛出一个异常,并将msg对象回收置空。
         */
        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标记为已经被用过
        msg.when = when; // 将方法中的第二个参数设置成msg的when变量
        Message p = mMessages; // 创建临时变量p并指向mMessages变量
        boolean needWake; // 创建一个布尔变量用来标记是否需要唤醒当前队列
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 当满足此条件时将msg添加到消息队列的头部,并且根据mBlocked的值来决定是否唤醒队列
            msg.next = p; // 将msg的next指向刚才的mMessages
            mMessages = msg; // 再将mMessages指向msg
            /**
             * 进入此条件有两种可能,第一种是消息队列为空,这时队列处于阻塞态,所以mBlocked的值肯定为true,这时需要唤醒;
             * 第二种就是消息队列不为空,但是本次插入的消息是调用了handler的sendMessageAtFrontOfQueue发送的msg;
             * 此时队列本身就就未处于阻塞态,mBlocked为false,所以无需再次唤醒
             */
            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();
            // 下列操作就是根据msg的when变量的值的排序来将msg插入到队列
            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;
}

以上就是向队列中插入消息的过程,其实简单的可以概括为三步。第一步,判断消息是否满足入队的条件并且检测队列是否能够插入消息;第二步,根据队列和消息的状态来决定消息插入的方式以及确定是否需要唤醒队列;第三步就是根据值来决定是否需要唤醒队列。经过剖析这个方法,是不是也可以确定了消息队列的实质其实就是一个链表,而非队列。

4.2.6.next方法

看完了消息入队的方法,我们再来研究一下消息出队的方法。next方法源码如下:

/**
 * 此方法的重点是看它返回Message的逻辑,有些和Message不相关的地方,就不贴出来了。
 */
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.
    // 如果消息循环已经退出并且释放,那么直接在这里返回;这种情况发生在程序退出后重新启动looper(这种操作是不支持的)
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration(仅在第一次迭代的时候为-1)
    // 这里创建一个int类型的变量,个人理解它的含义为到当前消息被返回的时长
    // 这里也提前指出,在即将进入的循环中,这个变量会根据不同的条件被赋值;
    // 当它的值为-1的时候,代表已经没有消息了,方法就会阻塞在nativePollOnce方法那里
    // 直到有新的消息入队,重新唤醒队列
    int nextPollTimeoutMillis = 0;
    for (;;) { // 进入一个没有任何判断条件的死循环
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 这是一个native层方法,当nextPollTimeoutMillis为-1时,将阻塞在这里
        nativePollOnce(ptr, nextPollTimeoutMillis); 
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis(); // 获取当前时间
            Message prevMsg = null; // 创建变量prevMsg
            Message msg = mMessages; // 创建msg变量并指向mMessages
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // msg的target为空,不合法,就丢掉它寻找下一个异步消息
                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,接下来的操作就是从队列中返回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; // 消息为空时将nextPollTimeoutMillis置为-1
            }
            // Process the quit message now that all pending messages have been handled.
            // 当其他的所有消息都处理完,处理退出的消息
            if (mQuitting) {
                dispose();
                return null;
            }
            // 后面还有一些代码,但是和主要逻辑无关,这里不贴出来了,感兴趣的可以自己阅读下
            ............
        } 
        ............
    }
}

以上就是从消息队列中取出消息的方法,这个方法的源码虽然比较多,但是真正和取消息相关的代码其实并不多。那么取出消息的过程也可以简单分成三步,第一步,判断队列是否已经退出以及消息是否合法;第二步,根据队列中消息的顺序确定要返回的消息;第三步,将返回的消息从队列中移除。
另外,这里还要强调一个知识点,就是上面我贴出的代码的最后,当发现mQuitting变量为true的时候,返回一个null,next方法就此结束。而这里要说的就是,当我们在一个子线程中创建了一个Looper,并且调用了它的loop方法开启了消息循环,那么当线程中消息队列里面的消息处理完成之后,线程就会一直阻塞在next方法中,以至于线程无法终止。因此如果我们想要线程在执行完全部的消息之后可以正常的终止的话,那么就应该在执行完全部的消息之后调用Looper的quit或者quitSafely方法来退出Looper,这样MessageQueue中的mQuitting变量就会变为true,从而解除线程阻塞态。

4.3.Looper:

这个Looper也是在Handler内部被调用的,就是它把消息发送到了Handler的手里。其实关于Looper,之所以我们平时在使用Handler的时候不会直接用到它,是因为通常情况下我们都是在主线程中使用的Handler,而主线程默认为我们做了关于Looper的一些初始化操作。在文章的开头,我们在主线程中直接new了一个Hander并没有出任何的问题,而如果采用同样的方式在一个子线程中直接new一个Handler的话,程序会立即崩溃,并且在控制台我们可以看到崩溃日志为:

"Can't create handler inside thread " xxx线程 " that has not called Looper.prepare()"

就是说,一个线程中如果没有调用Looper.prepare()方法,那么是无法创建一个Handler的。至于具体原因后面讲解Handler的源码时候就能明白了。现在我们先从Looper的prepare方法说起,首先看下这个方法:

/**
 *方法内部调用重载的prepare方法
 */
public static void prepare() {
    prepare(true);
}
/**
 * 方法中的sThreadLocal是一个ThreadLocal类型的成员变量
 * 这里看下方法中的参数,应该有印象吧,这个参数其实就是最终要传递到MessageQueue中的那个队列是否允许被退出的标志
 */
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { // 如果get方法返回不为空,说明当前线程已经创建了Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed)); // 如果没有创建,那么就创建一个Looper并存储到sThreadLocal中
}
/** 
 * Looper的构造方法,内部创建一个MessageQueue并指定当前的线程(mQueue和mThread为两个成员变量)
 */
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

刚刚我们提到了一个ThreadLocal类,这个类的原理这里就不深入讲解了,它是一个在线程内部存储数据的类,当在指定的线程中存储了数据以后,只有在指定的线程中才能获取到存储了的数据,而在其他的线程是无法获取到的。prepare方法的逻辑还是很清晰的,主要用于在一个线程中创建一个Looper,要注意不能在一个线程中多次调用,否则会导致程序崩溃的。
现在我们回过头来说主线程,在主线程ActivityThread的main方法中,系统默认为我们调用了Looper的prepareMainLooper方法,这个方法的源码如下:

/**
 * 方法的内部也是调用了prepare方法,并且传入的参数为false。同时,这个方法也不能被多次调用;
 * 方法的源码注释也说到,一个程序的主线程的Looper是系统为其创建的,我们不能自己手动去创建。
 */
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 @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

这就是我们在主线程直接创建一个Handler的时候不会崩溃的原因,因为系统默认为我们调用了Looper的prepareMainLooper方法。其实真正创建一个Looper对象的地方是在prepare的带有一个参数的重载方法中,它接收的那个布尔类型的参数是最终赋值给MessageQueue中的mQuitAllowed变量的。通过刚才了解子线程和主线程创建Looper对象的方式后,我们知道子线程是调用prepare方法,其内部调用prepare的重载方法并传入的参数值为true;而主线程是调用prepareMainLooper方法,其内部也是调用prepare的重载方法并传入的参数值为false。这里也和MessageQueue中的quit方法中的逻辑相吻合,当mQuitAllowed为false的时候,程序会崩溃并输出异常信息如下:

Main thread not allowed to quit. 
重点--loop方法

在简单介绍下Looper在使用时的一些注意事项后,我们来看一下它重要的一个方法loop,方法的源码如下:

/**
 * 此方法中,这里只贴出关键代码,我们只需关心Looper是如何把消息发给Handler的就可以了
 */
public static void loop() {
    // 首先检测Looper是否为空,如果Looper为空,会导致程序崩溃
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 创建queue变量指向当前的消息队列
    final MessageQueue queue = me.mQueue;
    ........
    for (;;) { // 进入死循环遍历消息队列中的消息
        Message msg = queue.next(); // might block 这里的next方法已经讲过
        if (msg == null) { // 如果msg为空,就终止循环退出当前loop方法
            // No message indicates that the message queue is quitting.
            return;
        }
        ........
        try {
            // 走到这里说明msg你不为空,那么就调用msg的target变量(绑定的Handler)的dispatchMessage方法
            msg.target.dispatchMessage(msg);  // 这样消息现在就已经发给了Handler了
            ........
        } catch (Exception exception) {
            ........
        } finally {
            ........
        }
        ........
        msg.recycleUnchecked(); // 消息发送后,重置消息对象,清空之前的数据
    }
}

这里贴出来的代码其实也就是源码中的差不多十分之一吧,不过关键的步骤就这些。loop方法的流程也可以概括为三步,第一步,检测Looper对象是否已经创建;第二步,在循环中通过调用MessageQueue的next方法从消息队列中遍历出消息;第三步,调用Handler的dispatchMessage方法将消息发送到Handler的手中然后将消息重置。
在方法的内部有一处注释(msg==null条件语句里),大致意思为“没有消息代表消息队列正在退出”。前面我们通过阅读MessageQueue的next方法,我们可以知道要想next方法返回null,只有一个条件,那就是MessageQueue中的mQuitting变量为true时(还有一种情况,就是MessageQueue中的mPtr变量值为0时,而mPtr为0的情况有两种,一种就是mQuitting变为true以后会执行dispose方法将它置为0,另一种就是dispose方法在程序终止时被调用)。所以这里要强调的还是如果在一个线程中不再使用Looper了,记得调用它的quit或者quitSafely方法,这样loop方法才能结束的。
另外一定要记得,如果在子线程中创建Handler,除了要手动调用Looper的prepare方法之外,还要调用loop方法来开启消息循环,否则消息是无法发送到Handler的(主线程中也默认调用了这个方法)。

4.4.Handler

终于到了关键角色Handler了,前面也简单描述过它的职责,在阅读完前面的内容后,现在我们应该更进一步的了解它的职责了。它就是负责把消息发送到队列,然后处理从looper中发送给它的消息。为了更全面的理解它的职责,下面就来看一下它的源码。

4.4.1.构造方法

Handler有7个构造方法,这里我们只列出两个,其中一个是它的无参构造(我们平时最常用的),另一个就是这个无参构造内部调用的接收两个参数的构造方法,代码如下:

/**
 * 默认的无参构造,内部调用下面的两个参数的构造方法
 */
public Handler() {
    this(null, false);
}
/**
 * 第一个参数是一个定义在Handler内部的接口;
 * 当我们在实例话一个Handler的时候,为了避免去实现一个Handler的子类,我们可以选择传入一个Callback接口;
 * 日常开发中我想大多数人都是直接采用匿名内部类的方法初始化一个Handler,因此这个也可以不传。
 * 第二个参数会赋值给Handler中的mAsynchronous变量,如果为true的话那么Handler中所有的消息都会被设置成异步消息
 */
public Handler(@Nullable Callback callback, boolean async) {
    // 这里警告如果一个继承Handler的类在使用时未被定义成静态变量,会有造成内存泄漏的危险
    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(); // 将mLooper指向当前线程的Looper实例
    if (mLooper == null) { // 这里就是为什么创建Handler之前必须创建Looper的原因了,因为你不创建会抛异常
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // 将mQueue指向当前线程的消息队列
    mCallback = callback; 
    mAsynchronous = async;
}

在构造方法中,就是检验当前线程是否具备开启Handler的条件,如果具备的话,就将Handler和当前线程的Looper以及MessageQueue进行关联绑定。

4.4.2.enqueueMessage方法

这个方法的名字是不是很熟悉?没错,前面讲过的MessageQueue中也有这么一个同名的方法,而其实Handler中的这个方法最终内部就是调用了MessageQueue中的enqueueMessage方法来将消息插入队列的。我们先来看一下这个方法的源码:

/**
 * 第一个参数就是当前线程的消息队列
 * 第二个参数就是即将安排入队的消息
 * 第三个参数最终会设置成第二个参数msg的when变量的值(前面在讲MessageQueue中的enqueueMessage方法时说过)
 */
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
    msg.target = this; // 将当前的Handler设置成msg的target变量
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) { // 这个变量在构造方法中被赋值
        msg.setAsynchronous(true); // 如果为true,那么入队的消息将全部变成异步消息
    }
    return queue.enqueueMessage(msg, uptimeMillis); // 调用真正的入队方法
}

之所以说到这个方法,是因为其实我们在使用Handler发送消息到队列时,无论我们调用的是Handler的sendMessage方法、sendMessageDelayed方法或者是post方法,最终都会调用到这个enqueueMessage方法。这里我打算贴出几个我们经常会用到的发送消息的方法的源码,但是不做讲解,因为一看就明白:

/**
 * 以下这三个方法,我们可以在外部直接调用,从上而下方法依次被调用,最终会调用enqueueMessage方法;
 * 它们的区别就是最终消息被处理的时间可能会有所不同
 */
// 消息发出就处理
public final boolean sendMessage(@NonNull Message msg) { 
    return sendMessageDelayed(msg, 0);
}
// 设置一段延时再处理消息
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { 
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 设置指定时间去处理消息
public boolean sendMessageAtTime(@NonNull 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); 
}

以上三个方法的区别就是最终设置给msg的when变量的值可能会不同。

4.4.3. dispatchMessage方法

这个方法是Handler分发消息去处理的方法,在前面讲Looper的loop方法中提到过。当消息被传入到此方法后,方法内部会根据消息对象的属性以及Handler的属性来决定如何分发这个消息。方法的源码如下:

public void dispatchMessage(@NonNull Message msg) {
    // 如果msg的callback不为空,那么就调用handleCallback方法
    if (msg.callback != null) {
        handleCallback(msg);
    } else { // 否则判断mCallback是否为空
        if (mCallback != null) { // 如果不为空就回调这个接口的handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // 执行handleMessage方法
    }
}
/**
 * 就是调用Message的callback变量的run方法;
 * 这个allback是Message中的一个Runnable类型的成员变量。
 */
private static void handleCallback(Message message) {
    message.callback.run();
}
/**
 * 此方法为一个空方法,源码中注释部分告诉我们Handler的子类必须实现此方法;
 * 而我们通常采用匿名内部类的方式直接重写此方法,在方法中是如何处理消息的逻辑。
 */
public void handleMessage(@NonNull Message msg) {
}

其实到这里,关于Handler的整个消息机制就差不多讲完了。不过,关于Handler还有一个方法这里要重点说一下。在阅读它的源码之前,我一直以为这个方法是新开了一个线程去执行事件,而阅读后才发现根本不是的。这个方法就是post方法。

4.4.4.post方法

这里我先修改一下文章开头写的demo中的create方法中的代码(别的代码不变),在子线程中调用handler的post方法,然后在Runnable的run方法中操作UI。代码如下:

public void create() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 操作UI
                }
            });
        }
    }).start();
}

如果方法一旦被执行,那么会导致程序崩溃吗?答案是不会的。而又是为什么呢?难道是在post方法中new出了一个新的主线程?脑洞确实挺大,其实post方法并没有创建任何的新线程,千万不要被Runnable迷惑了双眼。我们来看一下post方法的源码:

/**
 * 方法内部调用了sendMessageDelayed方法,sendMessageDelayed方法不再赘述;
 * 主要看一下该方法的第一个参数的getPostMessage方法(下面)
 */
public final boolean post(@NonNull Runnable r) {
   return sendMessageDelayed(getPostMessage(r), 0);
}
/**
 * 方法内部创建一个新的Message对象,并将post方法传进来的Runnable对象设置成Message的callback变量
 */
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

看完了这个方法的源码是不是恍然大悟了,原来其本质也是发送一条消息给消息队列,最终再由Looper发送给Handler,只不过这个方法发送的消息的callback属性不为空。而在前面讲dispatchMessage方法时说过,如果当前分发的消息对象的callback不为空,那么会执行到handleCallback方法中。而handleCallback方法内部其实就是调用了消息对象的callback的run方法,所以run方法是运行在Handler所创建的线程中的,本demo中Handler是在主线程中创建的。

5.重点回顾

1.任何一个线程若想启动一个Handler,必须先创建Looper对象(主线程中系统默认创建)。
2.只有调用了Looper的loop方法后,线程的消息循环才能开启。(主线程中系统默认调用)。
3.当一个线程中不再处理任何消息时,记得调用Looper的quit或quteSafely方法退出Looper。
4.一个Message对象一旦被Handler发送给消息队列,那么这个消息对象就已经在Handler所处的线程中。

总结

终终终终终终终终终终于写完了这篇文章,也是完成了一次挑战。Android的Handler机制还是挺巧妙的,仔细阅读相信一定可以理解其中的原理的。如果文章对您有帮助,还希望点个赞;有写的不对或者不好的地方,还望提出指正,定虚心接受。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容