控件中国网现已改版,您看到的是老版本网站的镜像,系统正在为您跳转到新网站首页,请稍候.......
中国最专业的商业控件资讯网产品咨询电话:023-67870900 023-67871946
产品咨询EMAIL:SALES@COMPONENTCN.COM

Android消息相关诸类

作者:未知 出处:cnblog 2013年09月22日 阅读:

上次写如何等待线程的消息处理完成,分析了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唤醒。

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

IdleHandler。这个在前一篇稳重详细介绍过,它会在消息队列为空或者虽有消息,但执行时间when都没到的时候被执行。详细参考前一篇文章,不赘述。
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);
        }
    }

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

Message.callback不为空。Message有一个Runnable类型的callback成员变量,可以具体指定针对该消息的处理方法。可以在构造Message中指定该callback方法。
Message.callback为空但Handler.mCallback不为空。mCallback的类型是Handler.Callback接口,需要被实现的函数是handleMessage。通过它,可以使用委托(delegate)而非继承(inherit)的方式来指定处理消息的方法。
以上二者皆空。此时调用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的设计原则。我更倾向于将两个功能分为两个类(至少是两个接口),一个专门负责处理消息,一个专门是插入消息。

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

创建一个HandlerThread的实例。它会提供给我们带消息处理功能的线程。
实现一个Handler的子类,覆盖实现handleMessage。我们可以定义一系列该handler可以使用的消息类型枚举,针对不同的类型执行具体的方法。
创建该Handler的实例。如果是想向该线程插入消息,则不指定looper参数;如果是其他线程的消息队列,则looper指定为HandlerThread.getLooper。
通过Message.obtain创建Message,在参数中指定消息类型,之前创建的handler实例(作为target)和其他一些变量(例如when,arg1等)。
然后调用Message.sendToTarget来发送消息。之后消息会进消息队列。
在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自己创建。

 

热推产品

  • ActiveReport... 强大的.NET报表设计、浏览、打印、转换控件,可以同时用于WindowsForms谀坔攀戀Forms平台下......
  • AnyChart AnyChart使你可以创建出绚丽的交互式的Flash和HTML5的图表和仪表控件。可以用于仪表盘的创......
首页 | 新闻中心 | 产品中心 | 技术文档 | 友情连接 | 关于磐岩 | 技术支持中心 | 联系我们 | 帮助中心 Copyright-2006 ComponentCN.com all rights reserved.重庆磐岩科技有限公司(控件中国网) 版权所有 电话:023 - 67870900 传真:023 - 67870270 产品咨询:sales@componentcn.com 渝ICP备12000264号 法律顾问:元炳律师事务所 重庆市江北区塔坪36号维丰创意绿苑A座28-5 邮编:400020
在线客服
在线客服系统
在线客服
在线客服系统