Handler,Looper,MessageQueue机制

线程与线程间的交互协作

线程与线程之间虽然共享内存空间,也即可以访问进程的堆空间,但是线程有自己的栈,运行在一个线程中的方法调用全部都是在线程自己的调用栈中。通俗来讲西线程就是一个run()方法及其内部所调用的方法。这里面的所有方法调用都是独立于其他线程的,由于方法调用的关系,一个方法调用另外的方法,那么另外的方法也发生在调用者的线程里。所以,线程是时序上的概念,本质上是一列方法调用。

那么线程之间要想协作,或者想改变某个方法所在的线程(为了不阻塞自己线程),就只能是向另外一个线程发送一个消息,然后return;另外线程收到消息后就去执行某些操作。如果是简单的操作可以用一个变量来标识,比如A线程主需要B线程做某些事时,可以把某个对象obj设置值,B则当看到obj != null时就去做事,这种线程交互协作在《Java编程思想》中有大量示例。


Android中的ITC-Inter Thread Communication

注意:当然Handler也可以用做一个线程内部的消息循环,不必非与另外的线程通信,但这里重点讨论的是线程与线程之间的事情。

Android当中做了一个特别的限制就是非主线程不能操作UI元素,而一个应用程序是不可能不创衍生线程的,这样一来主线程与衍生线程之间就必须进行通信。由于这种通信很频繁,所以不可能全用变量来标识,程序将变得十分混乱。这个时候消息队列就变得有十分有必要,也就是在每个线程中建立一个消息队列。当A需要B时,A向B发一个消息,此过程实质为把消息加入到B的消息队列中,A就此return,B并不专门等待某个消息,而是循环的查看其消息队列,看到有消息后就去执行。

整套ITC的基本思想是:定义一个消息对象,把需要的数据放入其中,把消息的处理的方法也定义好作为回调放到消息中,然后把这个消息发送另一个线程上;另外的线程在循环处理其队列里的消息,看到消息时就对消息调用附在其上的回调来处理消息。这样一来可以看出,这仅仅是改变了处理消息的执行时序:正常是当场处理,这种则是封装成一个消息丢给另外的线程,在某个不确定的时间被执行;另外的线程也仅提供CPU时序,对于消息是什么和消息如何处理它完全不干预。简言之就是把一个方法放到另外一个线程里去调用,进而这个方法的调用者的调用栈(call stack)结束,这个方法的调用栈转移到了另外的线程中。

那么这个机制改变的到底是什么呢?从上面看它仅是让一个方法(消息的处理)安排到了另外一个线程里去做(异步处理),不是立刻马上同步的做,它改变的是CPU的执行时序(execution sequence)。

那么消息队列存放在哪里呢?不能放在堆空间里(直接new MessageQueue()),这样的话对象的引用容易丢失,针对线程来讲也不易维护。Java支持线程的本地存储ThreadLocal,通过ThreadLocal对象可以把对象放到线程的空间上,每个线程都有了属于自己的对象。因此,可以为每个需要通信的线程创建一个消息队列并放到其本地存储中。

基于这个模型还可以扩展,比如给消息定义优先级等。


MessageQueue

以队列的方式来存储消息,主要是二个操作一个是入列enqueueMessage,一个是出列next(),需要保证的是线程安全,因为入列通常是另外的线程在调用。

MessageQueue是一个十分接近底层的机制,所以不方便开发者直接使用,要想使用此MessageQueue必须做二个方面工作,一个是目标线程端:创建,与线程关联,运转起来;另一个就是队列线程的客户端:创建消息,定义回调处理,发送消息到队列。Looper和Handler就是对MessageQueue的封装:Looper是给目标线程用的:用途是创建MessageQueue,将MessageQueue与线程关联起来,并让MessageQueue运转起来,且Looper有保护机制,让一个线程仅能创建一个MessageQueue对象;而Handler则是给队列客户端用的:用来创建消息,定义回调和发送消息。

因为Looper对象封装了目标队列线程及其队列,所以对队列线程的客户端来讲,Looper对象就代表着一个拥有MessageQueue的线程,和这个线程的MessageQueue。也即当你构建Handler对象时用的是Looper对象,而当你检验某个线程是否是预期线程时也用Looper对象。


Looper内幕

Looper的任务是创建消息队列MessageQueue,放到线程的ThreadLocal中(与线程关联),并且让MessageQueue运转起来,处于Ready的状态,并要提供供接口以停止消息循环。它主要有四个接口:
  • public static void Looper.prepare()

    这个方法是为线程创建一个Looper对象和MessageQueue对象,并把Looper对象通过ThreadLocal放到线程空间里去。需要注意的是这个方法每个线程只能调用一次,通常的做法是在线程run()方法的第一句,但只要保证在loop()前面即可。

  • public static void Looper.loop()

    这个方法要在prepare()这后调用,是让线程的MessageQueue运转起来,一旦调用此方法,线程便会无限循环下去(while (true){...}),无Message时休眠,有Message入队时唤醒处理,直到quit()调用为止。它的简化实现就是:

    loop() {  
       while (true) {  
          Message msg = mQueue.next();  
          if msg is a quit message, then  
             return;  
          msg.processMessage(msg)  
       }  
    }  

  • public void Looper.quit()

    让线程结束MessageQueue的循环,终止循环,run()方法会结束,线程也会停止,因此它是对象的方法,意即终止某个Looper对象。一定要记得在不需要线程的时候调用此方法,否则线程是不会终止退出的,进程也就会一直运行,占用着资源。如果有大量的线程未退出,进程最终会崩掉。

  • public static Looper Looper.myLooper()

    这个是获得调用者所在线程所拥有的Looper对象的方法。

还有二个接口是与主线程有关的:
  • 一个是专门为主线程准备的

    public static void Looper.prepareMainLooper();

    这个方法只给主线程初始化Looper用的,它仅在ActivityThread.main()方法中调用,其他地方或其他线程不可以调用,如果在主线程中调用会有异常抛出,因为一个线程只能创建一个Looper对象。但是如在其他线程中调用此方法,会改变mainLooper,接下来的getMainLooper就会返回它而非真正的主线程的Looper对象,这不会有异常抛出,也不会有明显的错误,但是程序将不能正常工作,因为原本设计在主线程中运行的方法将转到这个线程里面,会产生很诡异的Bug。这里Looper.prepareMainThread()的方法中应该加上判断:

    public void prepareMainLooper() {  
        if (getMainLooper() != null) {  
             throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks");  
         }  
         //...  
    }  

    以防止其他线程非法调用,光靠文档约束力远不够。

  • 另外一个就是获取主线程Looper的接口:

    public static Looper Looper.getMainLooper()

    这个主要用在检查线程合法性,也即保证某些方法只能在主线程里面调用。但这并不保险,如上面所说,如果一个衍生线程调用了prepareMainLooper()就会把真正的mMainLooper改变,此衍生线程就可以通过上述检测,导致getMainLooper() != myLooper()的检测变得不靠谱了。所以ViewRoot的方法是用Thread来检测:mThread != Thread.currentThread();其mThread是在系统创建ViewRoot时通过Thread.currentThread()获得的,这样的方法来检测是否是主线程更加靠谱一些,因为它没有依赖外部而是相信自己保存的Thread的引用。

Message对象

消息Message是仅是一个数据结构,是信息的载体,它与队列机制是无关的,封装着要执行的动作和执行动作的必要信息,what, arg1, arg2, obj可以用来传送数据;而Message的回调则必须通过Handler来定义,为什么呢?因为Message仅是一个载体,它不能自己跑到目标MessageQueue上面去,它必须由Handler来操作,把Message放到目标队列上去,既然它需要Handler来统一的放到MessageQueue上,也可以让Handler来统一定义处理消息的回调。需要注意的是同一个Message对象只能使用一次,因为在处理完消息后会把消息回收掉,所以Message对象仅能使用一次,尝试再次使用时MessageQueue会抛出异常。


Handler对象

它被设计出来目的就是方便队列线程客户端的操作,隐藏直接操作MessageQueue的复杂性。Handler最主要的作用是把消息发送到与此Handler绑定的线程的MessageQueue上,因此在构建Handler的时候必须指定一个Looper对象,如果不指定则通过Looper获取调用者线程的Looper对象。它有很多重载的send*Message和post方法,可以以多种方式来向目标队列发送消息,廷时发送,或者放到队列的头部等等;

它还有二个作用,一个是创建Message对象通过obtain*系统方法,另一个就是定义处理Message的回调mCallback和handleMessage,由于一个Handler可能不止发送一个消息,而这些消息通常共享此Handler的回调方法,所以在handleMessage或者mCallback中就要区分这些不同的消息,通常是以Message.what来区分,当然也可以用其他字段,只要能区别出不同的Message即可。需要指明的是,消息队列中的消息本身是独立的,互不相干的,消息的命名空间是在Handler对象之中的,因为Message是由Handler发送和处理的,所以只有同一个Handler对象需要区别不同的Message对象。广义上讲,如果一个消息自己定义有处理方法,那么所有的消息都是互不相干的,当从队列取出消息时就调用其上的回调方法,不会有命名上的冲突,但由Handler发出的消息的回调处理方法都是Handler.handleMessage或Handler.mCallback,所以就会有影响了,但影响的范围也令局限在同一个Handler对象。

因为Handler的作用是向目标队列发送消息和定义处理消息的回调(处理消息),它仅是依赖于线程的MessageQueue,所以Handler可以有任意多个,都绑定到某个MessageQueue上,它并没有个数限制。而MessageQueue是有个数限制的,每个线程只能有一个,MessageQueue通过Looper创建,Looper存储在线程的ThreadLocal中,Looper里作了限制,每个线程只能创建一个。但是Handler无此限制,Handler的创建通过其构造函数,只需要提供一个Looper对象即可,所以它没有个数限制。


转自:http://blog.csdn.net/hitlion2008/article/details/8194510

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值