zoukankan      html  css  js  c++  java
  • Android消息相关诸类

    上次写如何等待线程的消息处理完成,分析了MessageQueue的部分代码。Android系统为了方便消息处理,提供了多个消息处理相关的类,包括Message,Handler,Looper,MessageQueue,HandlerThread等。之前没有好好理清他们之间的关系和内部实现,今天就通过代码分析学习下。

    Message

    Message表示一条消息。其成员变量中,what表示消息类型,用户可以自定义针对一个Handler的消息类型列表(Handler下面再说)。Message有arg1,arg2和obj为消息提供更加丰富的信息。Message提供了一系列的static的obtain方法来帮助创建Message对象。这种通过obtain来构造对象的方法在android的code中四处可见,我想作者应该读过effective java那本书,其第一条就是使用静态工厂方法替代构造器。

    因为Message可能会在跨进程的远程调用中作为参数传递,所以它实现了Parcelable接口,提供了writeToParcle和readFromParcle以供序列化。

    基本上,我们可以把Message看做一个实体类。绝大多数方法都是setter,getter和序列化相关的。只有一个是操作类的,就是sendToTarget。sendToTarget会调用target.sendMessage(this)。target是Message中一个成员变量,类型是Handler。在Handler.sendMessage函数的中,其最后会调用到Handler.enqueueMessage,并进而进入MessageQueue.enqueueMessage。从类名和方法名基本可以猜出,这是把消息放到消息队列中。我们先不关心中间的Handler,先看下MessageQueue。

    MessageQueue

    MessageQueue表示一个消息队列。如同大多数的窗口系统,Android为每个线程维护了一个消息队列。在这些窗口系统中,线程会不断的从消息队列中读取消息,处理消息,然后再读取下一条消息。从这个角度看,这些窗口系统都可以看做是消息驱动的系统。如果来了消息,就不断处理;如果没有消息,就暂时休息。

    消息队列提供了一个比较方便的跨线程/进程交互机制。一个线程可以向另外一个线程的消息队列中添加消息,从而驱动另外一个线程做需要的工作。线程也可以向自己的消息队列中添加消息,这样子当前消息被处理完之后,就会读取刚加入的消息进行下一轮的操作。

    在实现上,成员变量mMessages用来维护消息队列链表。它实际是个Message类型,因为每个Message都有个next的成员变量,于是他们就通过next相互连接成链表。

    enqueueMessage用于向消息队列中加入一条新消息。在Message中有一个when成员变量,表示该消息需要被处理的最早时间。(因为android并非实时系统,所以不保证该时间一定能被处理,例如前一个消息的处理花费了太长时间没有退出)。在插入消息时,会按照when进行排序。

    next用于从队列中读取下一个要处理的消息。因为消息已经按照处理时间when进行排序,所以只需要读取第一个就可以了。如果没有消息,为节省CPU,会阻塞于nativePollOnce。它会被底层的硬件消息唤醒,也可以被enqueueMessage的nativeWake唤醒。

    以上是主要逻辑。除此之外,又有两个个小概念:

    1. IdleHandler。这个在前一篇稳重详细介绍过,它会在消息队列为空或者虽有消息,但执行时间when都没到的时候被执行。详细参考前一篇文章,不赘述。
    2. Barrier。这是特殊的消息,特点其target为空。当消息队列头是这种消息,那其后的消息都不能被执行,相当于在消息队列中加了个栅栏,阻挡后续消息的执行。加Barrier是通过enqueueSyncBarrier来执行。要去掉这个“栅栏”,必须通过removeSyncBarrier。这里写“其后的消息都不能被执行”其实不完全对,更确切的说法是同步的消息都不能执行,但异步的可以。在message中有setAsynchronous/isAsynchronous来设置和判断是否异步。

    另外,消息队列中的消息一般都是应用层面的消息。而硬件消息,则在nativePollOnce时会直接被另外一个分支处理掉。这样做应该是为了加速用户响应。

    现在通过enqueueMessage把消息插入到消息队列了,那谁会通过next来读取消息呢?

    Looper

    Looper用来循环的读取MessageQueue的消息,并触发其处理函数。(什么是处理函数呢?后面会谈到)

    java自己的Thread类并不能提供消息队列循环的功能,那如何为其增加消息循环功能呢?

    我们都知道java Thread类的主要方法是run,当启动Thread后,run方法就会被调用。很容易想到,我们可以在run中不断的读取MessageQueue中的消息,然后处理消息。Looper的实现者也是这么想的,其最主要的函数Loop,只是在一个死循环中不断的读取消息,并调用处理函数。在Looper中有个成员变量MessageQueue mQueue,用来表示其读取的消息队列。

    Looper中限制每个线程最多只有一个消息队列(当然如果不用Looper类,我们可以让一个线程有多个消息队列,虽然没什么用)。这是通过static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();来实现的。这里sThreadLocal是每个线程唯一的变量,于是每个线程就只有一个Looper实例。这个sThreadLocal是通过prepare来初始化的,它只是简单的构造一个Looper实例,然后赋给这个线程本地变量。

    有了Looper,我们可以自己写一个带消息循环处理功能的Thead。Android实现者很体贴,为减少我们的工作量,他为我们实现了HandlerThread。

    HandlerThread

    HandlerThread很简单,它只是在run中先调用Looper.prepare创建线程唯一的Looper实例,然后调用Looper.loop来不断地读取消息,处理消息。

    Handler

    到这里,我们已经可以通过Message.sendToTarget向消息队列插入消息;之后在HandlerThread中通过Looper从消息队列中读取消息。读取后又该如何处理呢?

    事实上,Handler就表示了如何处理一个消息。它是Message中target成员变量的类型。在Looper的loop方法中,在得到消息后,会调用message.target.dispatchMessage。dispatchMessage的实现如下:

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

    它会分三种情况进行处理:

    1. Message.callback不为空。Message有一个Runnable类型的callback成员变量,可以具体指定针对该消息的处理方法。可以在构造Message中指定该callback方法。
    2. Message.callback为空但Handler.mCallback不为空。mCallback的类型是Handler.Callback接口,需要被实现的函数是handleMessage。通过它,可以使用委托(delegate)而非继承(inherit)的方式来指定处理消息的方法。
    3. 以上二者皆空。此时调用Handler自己的handleMessage。这时候需要使用继承的方式覆盖掉hander默认的handleMessage(默认是个空函数)。

    第二种和第三种都是在Handler级别指定一个统一的处理函数,在函数里面针对具体的消息类型采用不同的操作,区别只是一个委托,一个继承,前者灵活,后者简单;第一种则是针对每个消息都指定一个独立的处理函数。现实中,最常见的还是第三种方式,会创建一个Handler的子类,然后在子类中覆盖handleMessage,在其中switch...case...不同的消息进行不同的处理。

    以上是handler的第一个功能,提供了消息的处理方法,我想这也是其名字的由来。

    前面Message部分我们提到,message.sendToTarget会调用Handler.sendMessage,进而是Handler.enqueueMessage->Handler.sendMessageAtTime->MessageQueue.enqueueMessage。它有一个成员变量mQueue表示该handler对应的MessageQueue。如果使用没有参数的构造函数,则mQueue是当前线程的MessageQueue(通过Looper.myLooper().mQueue得到);如果指定了looper参数,则使用该looper(以及其对应的线程)对应的MessageQueue(通过looper.mQueue得到)。

    这是Handler的第二个功能,提供了向构造函数中looper参数指定的队列插入消息的功能。但在我看来,这违背了SRP的设计原则。我更倾向于将两个功能分为两个类(至少是两个接口),一个专门负责处理消息,一个专门是插入消息。

    至此,整个流程已经完整了。如果我们想要一个带消息处理功能的线程,指定消息处理的方式,并能够向其插入消息,只需要下面的工作:

    1. 创建一个HandlerThread的实例。它会提供给我们带消息处理功能的线程。
    2. 实现一个Handler的子类,覆盖实现handleMessage。我们可以定义一系列该handler可以使用的消息类型枚举,针对不同的类型执行具体的方法。
    3. 创建该Handler的实例。如果是想向该线程插入消息,则不指定looper参数;如果是其他线程的消息队列,则looper指定为HandlerThread.getLooper。
    4. 通过Message.obtain创建Message,在参数中指定消息类型,之前创建的handler实例(作为target)和其他一些变量(例如when,arg1等)。
    5. 然后调用Message.sendToTarget来发送消息。之后消息会进消息队列。
    6. 在HandlerThread的run中会调用Looper.loop来读取到消息,然后message.target.handleMessage会被调用(如果message了定义callback,则是callback被调用)。

    对于第四步创建Message,Handler也对其进行了封装。其Handler.obtainMessage可以调用Message.obtain来创建消息。对于第五步,实际上一般也直接调用Handler.sendMessage来发送。于是在写代码的时候,除了初始化,之后的创建消息,发送消息,处理消息,都是由Handler来完成。虽然在我看来功能上有点多,但使用上却是方便了很多。

    还值得一提的函数是Handler.post,它也是发送消息,实现上也是通过sendMessage做的。特别之处是它接收Runnable的参数,然后会以该参数作为callback创建message。这样可以比较方便的创建具有特定处理函数的message,不管如果没有它,我们也可以自己通过Message.obtain自己创建。

    最后,一个Thread只能有一个MessageQueue,也只能有一个Looper.但是可以有多个Handler。我们在sendMessage时使用哪个Handler调用的(message.target就是该Handler),那就在哪个Handler中处理该message。

  • 相关阅读:
    秋意浓浓回成都2月杂记
    验证表单的js代码段
    C#算法小程序(1)
    C#数据结构之单向链表
    Excel VBA编程的常用代码
    我的漫漫系分路
    2007年下半年系统分析师上午试卷及参考答案
    「2014年間休日カレンダー」のご案内
    Eclipse的几点使用技巧
    沧海一粟小组(第一次作业)
  • 原文地址:https://www.cnblogs.com/xichengtie/p/3331250.html
Copyright © 2011-2022 走看看