第10章 Android的消息机制

本章主要讲的内容是Android的消息机制。

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue就是我们常说的消息队列,它的内部存储了一组消息,虽然叫做消息队列,但是它的内部却是采用单链表的数据结构来存储消息列表的。Looper为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有就处理,如果没有就会等待。我们都知道Handler的创建是需要Looper的,也就是说Handler创建的时候会采用当前线程的Looper来构造消息循环系统。那么Handler内部是如何获取当前线程的Looper的呢?这就要使用到ThreadLocal了,它可以在不同的线程中互不干扰地存储和读取数据,通过ThreadLocal可以轻松获取每个线程的Looper。
下面的几个小节我们会围绕ThreadLocal、MessageQueue、Looper和Handler来阐述Android的消息机制。

10.1 Android的消息机制概述

Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三者实际上是一个整体,只不过我们在开发的过程中,更多的是和Handler打交道。Handler的主要作用是将一个任务切换到某个指定的线程中去执行。
首先,Android为什么要提供这么一个功能呢?
这是因为Android规定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.");
}
}

因此,系统之所以提出Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾。

然后就会有人问,系统为什么不允许在子线程中访问UI呢?
这是因为Android中的UI控件是不安全的,如果在多线程中并发访问的话就会导致UI空间处于不可预期的状态。那么又有人会说为什么不给UI控件加锁保证线程安全呢?
缺点有两个:
(1)加锁会让UI访问的逻辑变的复杂;
(2)加锁会降低UI访问的效率,因为锁机制会阻塞某些线程的执行;
所以最简单的方式就是采用单线程模型来处理UI操作,就是通过Handler切换一下UI访问的执行线程就可以了。

10.2 Android的消息机制详细分析

10.2.1 ThreadLocal的工作原理

TheadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存取数据而与其他线程中互不干扰。

TheadLocal的使用场景主要是:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用TheadLocal。在不同的线程中,我们虽然访问的是同一个TheadLocal对象,但是它们通过TheadLocal获取到的值却是不一样的,这就是TheadLocal的奇妙之处。那主要是因为虽然不同线程访问同一个TheadLocal的get或者set方法,但是TheadLocal会从各自的线程中取出一个数组,然后再从数组中根据当前TheadLocal的索引去查找出对应的value值,很显然不同的线程所获得的数组是不同的。这样就可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

对于TheadLocal的使用方法和工作过程,这里就不做解释了。我们主要来看一下它的内部实现:
TheadLocal是一个泛型类,它的主要方法是get和set方法。所以我们只要搞清楚这两个方法就可以明白TheadLocal的工作原理了。
首先来看TheadLocal的set方法,如下所示:

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

可以看到,以当前线程为参数传入到vaules方法中就可以拿到当前线程的ThreadLocal数据。其实在Thread类的内部有一个成员是专门用于存储线程的ThreadLocal的数据: ThreadLocal.Values localValues,那么获得当前线程的ThreadLocal数据就变得很简单了。 ThreadLocal内部是如何存储在localValues中的?其实在localValues的内部有一个数组:private Objext[] table,ThreadLocal的值就存储在这个数组中。
下面看一下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数组中的位置就是table[index+1]。最终,ThreadLocal的值就会被存储在tbale数组中:table[index+1] = value.
下面再来看一下ThreadLocal的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);
    }

这个过程很清楚,就是取出当前线程的localValues对象,如果这个对象为null那么就返回初始值。

从get和set方法可以看出,他们所操作的对象都是当前线程的localValues对象的table数组,因此在不同的线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的操作仅限于在各自的线程中,这也就解释了为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据。

10.2.2 MessageQueue的工作原理

MessageQueue就是我们常说的消息队列,前面已经说过尽管也叫队列,但是内部的实现还是单链表【插入和删除方面比较有优势】。MessageQueue包含两个基本操作:插入和读取,分别对应的方法是:enqueueMessage和next,next方法从消息队列中取出一条消息的同时往往也伴随着删除这条消息的操作。在enqueueMessage和next方法中主要就是单链表的插入和删除的操作,这个只要大家有过基本的数据结构的知识看懂都不难,就不再解释了。值得注意的一点是:next方法是一个死循环,如果消息队列中没有消息的话就会被阻塞,当有消息到来时,next方法会返回这条消息并将其从单链表中删除。

10.2.3 Looper的工作原理

Looper在消息机制中扮演的角色是消息循环,具体的来说它会不停歇的在Message Queue中查看是否有新消息,如果有新消息就会立刻处理,如果没有的话就会阻塞在那里。我们先看一下它的构造方法,如下所示:

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

Handler的工作创建工作是需要Looper支撑的。如何为一个线程创建一个Looper呢,其实很简单,通过Looper.prepare()为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。
大家可能注意到了,Looper和MessageQueue是关联的,MessageQueue的next方法是死循环,一旦没有新消息就会阻塞,Looper的loop方法也是死循环。那么可能大家就会问那既然是死循环难道就不能停止或者退出吗?答案肯定是有的,首先可以告诉大家的是这两者的退出是相关的。下面就是详细的解释:

loop方法就是一个死循环,退出循环的唯一方式就是MessageQueue的next方法返回了null。但是刚才我们注意到了,next方法即使没有新的消息也没有返回null而是被阻塞。其实当只要Looper的quit方法被调用,MessageQueue就会被标记为退出状态,这个时候不管有没有新消息next方法都会返回null的。因此,想要Looper的loop跳出循环的话,必须调用quit方法使得loop跳出循环的条件成立,即MessageQueue的next方法返回了null,这样loop就停止了。总而言之,Looper用完必须调用quit方法退出,这样的话循环才会停止。
loop方法会不断的调用MessageQueue的next方法获取新消息,而next方法是一个阻塞操作,当没有消息时就会阻塞,这时loop方法也会阻塞的。如果next返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage,而这个msg.target就是这条消息的Handler对象,这样Handler发送的消息最终最终是自己的dispatchMessage去处理的。但是这里有不同:Handler的dispatchMessage方法是在创建Handler所使用的Looper【每一个Looper与线程是关联的】中执行的,这样就成功的将代码逻辑切换到指定的线程中去了。

10.2.4 Handler的工作原理

Handler的工作主要是消息的发送和接收过程。
Handler发送消息的过程仅仅是向信息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了。最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用。这时Handler就进入到消息处理阶段了。

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

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

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

其次,检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息,其实mCallback是一个接口,是这么定义的:

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

最后调用了handleMessage方法来处理消息。

10.3 主线程的消息循环

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

    public static void main(String[] args) { 
        // 代码省略
        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"));
        } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop(); 
        throw new RuntimeException("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; 
        ……
        ……
}

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值