第10章 Android的消息机制

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

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue就是我们常说的消息队列,它的内部存储了一组消息,虽然叫做消息队列,但是它的内部却是采用单链表的数据结构才存储消息列表的。Looper为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper填补了这个功能,Looper会以无限循环的形式去查找是否有心得消息,如果有就处理,如果没有就会等待。我们都知道Hnadler的创建是必须需要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必须退出,这样的话循环才会停止。

loop方法会调用MessageQueue的next方法获取新消息,而next方法是一个阻塞操作,当没有消息时就会阻塞,这时loop方法也会阻塞的。如果next返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage,而这个msg.target就是这条消息的Handler对象,这样Handler发送的消息最终也是自己的dispatchMessage处理的。但是这里有不同:Handler的dispatchMessage方法是在创建Handler所使用的Looper中执行的,这样就成功的将代码逻辑切换到指定的线程中去了。

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;
        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 RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int SERVICE_ARGS            = 115;
        public static final int STOP_SERVICE            = 116;

        public static final int CONFIGURATION_CHANGED   = 118;
        public static final int CLEAN_UP_CONTEXT        = 119;
        public static final int GC_WHEN_IDLE            = 120;
        public static final int BIND_SERVICE            = 121;
        public static final int UNBIND_SERVICE          = 122;
        public static final int DUMP_SERVICE            = 123;
        public static final int LOW_MEMORY              = 124;
        public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
        public static final int RELAUNCH_ACTIVITY       = 126;
        public static final int PROFILER_CONTROL        = 127;
        public static final int CREATE_BACKUP_AGENT     = 128;
        public static final int DESTROY_BACKUP_AGENT    = 129;
        public static final int SUICIDE                 = 130;
        public static final int REMOVE_PROVIDER         = 131;
        public static final int ENABLE_JIT              = 132;
        public static final int DISPATCH_PACKAGE_BROADCAST = 133;
        public static final int SCHEDULE_CRASH          = 134;
        public static final int DUMP_HEAP               = 135;
        public static final int DUMP_ACTIVITY           = 136;
        public static final int SLEEPING                = 137;
        public static final int SET_CORE_SETTINGS       = 138;
        public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
        public static final int TRIM_MEMORY             = 140;
        public static final int DUMP_PROVIDER           = 141;
        public static final int UNSTABLE_PROVIDER_DIED  = 142;
        public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
        public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
        public static final int INSTALL_PROVIDER        = 145;
        public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
        public static final int CANCEL_VISIBLE_BEHIND = 147;
        public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148;
        public static final int ENTER_ANIMATION_COMPLETE = 149;
        ……
}

ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后回调

ApplicationThread的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中执行,这个过程就是主线程的消息循环模型。

时间: 2024-08-29 23:36:58

第10章 Android的消息机制的相关文章

Android 开发艺术探索——第十章 Android的消息机制

Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑. MessageQueue即为消息队列,顾名思义,它的内部存储了一组消息,以队列的的形式对外提供插入和删除的工作.虽然叫队列,但内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表. Looper意思为循

Android的消息机制之ThreadLocal的工作原理

提到消息机制大家应该都不陌生,在日常开发中不可避免地要涉及到这方面的内容.从开发的角度来说,Handler是Android消息机制的上层接口,这使得开发过程中只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行.很多人认为Handler的作用是更新UI,这说的的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景,具体来说是这样的:有时候需要在子线程中进行耗时的IO操作,这可能是读取文件或者访问网络等,当耗时操作

Android Handler消息机制深入浅出

作为Android开发人员,Handler这个类应该是再熟悉不过了,因为几乎任何App的开发,都会使用到Handler这个类,有些同学可能就要说了,我完全可以使用AsyncTask代替它,这个确实是可以的,但是其实AsyncTask也是通过Handler实现的,具体的大家可以去看看源码就行了,Handler的主要功能就是实现子线程和主线程的通信,例如在子线程中执行一些耗时操作,操作完成之后通知主线程跟新UI(因为Android是不允许在子线程中跟新UI的). 下面就使用一个简单的例子开始这篇文章

Android Handler消息机制

在上一篇文章<Android AsyncTask异步任务>中我们介绍了如何使用AsyncTask异步处理网络通信和UI更新.在本文中将使用Handler消息机制来异步处理网络通信和UI更新. Google参考了Windows的消息机制,在Android系统中实现了一套类似的消息机制.学习Android的消息机制,有几个概念(类)必须了解: 1.Message 消息,理解为线程间通讯的数据单元.例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程. 2.M

Android进阶-消息机制

Handler的主要作用是将一个任务切换到某个指定的线程去执行. Android的消息机制主要涉及三个类:Handler, Looper, MessageQueue: 现在假设一个情景: 有两个线程,线程1和线程2,在线程1中调用Looper.prepare(), 创建一个Handler对象handler,调用Looper.loop(). 在线程2中调用handler.sendMessage()发送消息,那最终这个消息会在线程1中被handler.handleMassage()处理.我们画出内存

【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其他线程来说是无法获取到数据的,也就是说ThreadLocal可以在多个线程中互不干扰地存储和修改数据.基于ThreadLocal的这一特点,那么当我们在开发中,需要将某些数据以线程作为作用域,并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal了. Android的消息机制中也

Android的消息机制

1.背景                                                                Handler是Android消息机制的上层接口,通过handler可以轻松地将一个任务切换到Handler所在的线程中去执行. Handler的作用之一是更新UI,有时候需要在子线程中进行耗时的I/O操作,可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在UI上做一些改变,这时用Handler. Android的消息机制主要是指Handler的运行机制

聊一聊Android的消息机制

侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比较简单的.从大的方面讲,不光是Android平台,各种平台的消息机制的原理基本上都是相近的,其中用到的主要概念大概有:1)消息发送者:2)消息队列:3)消息处理循环.示意图如下: 图中表达的基本意思是,消息发送者通过某种方式,将消息发送到某个消息队列里,同时还有一个消息处理循环,不断从消息队列里摘取消息,并进一步解析处理. 在An

Android的消息机制以及Message/MessageQueue/Handler/Looper

概览 * Message:消息.消息里面可包含简单数据.Object和Bundle,还可以包含一个Runnable(实际上可看做回调). * MessageQueue:消息队列,供Looper线程消费消息. * Looper:用于循环处理Message,一个Thread结合一个Looper来实现消息循环处理.Android App的主线程包含了Looper. * Handler:负责向当前Looper线程发送Message,并实现如何处理消息的回调,回调可放到Callback接口的实现中,也可以