Android消息机制解析——Handler

日常开发的过程中,我们经常会使用到hander,handler是Android中消息机制的上层接口,使用过程很简单方便,通过handler我们可以轻松的将一个任务切换到另外一个线程中去执行,通常我们会在子线程中使用handler,将UI操作切换到主线程去执行。

  • handler概述

    Handler的运行,还需要MessageQueue和Looper,还有ThreadLoacl。MessageQueue用来存储所有待处理的消息的队列,其底层实现是一种单链表的数据结构。Looper 则是以无限循环的方式来处理MessageQueue中所存储的消息,没有消息需要处理的话,就会阻塞等待。ThreadLocal并不是一个线程,他的作用是你可以声明一个ThreadLocal对象,分别在不同的线程去访问它,在该线程操作ThreadLocal对象只会影响在该线程中的访问结果,而其他线程中并不受影响,也就是说,一个ThreadLocal对象,在每一个线程中都有一个独立的、与该线程相关的数据副本。在handler中,我们可以使用ThreadLocal轻松地获取不同线程的Lopper对象。

    我们直到,使用handler必须要先初始化Looper对象,如果使用handler的时候没有初始化Looper对象,会抛出异常。通常我们使用Looper.perpare()初始化Looper,然后创建Handler对象,并且调用Looper.loop()方法,来启动Looper对象的消息循环。而在主线程中,我们可以直接使用handler对象,并不需要去初始化looper对象和启动looper对象的消息循环,是因为在ActivityThread中,当ActivityThread被创建的时候,系统已经帮我们做好了相关工作。

    当我们使用handler的send或者是post方法来发送一个runnable对象的时候,最终handler都会调用到send方法,send方法会调用MessageQueue的enqueueMessage方法将该消息放置到MessageQueue中,然后Looper对象发现有消息到来,便会从MessageQueue中获取并移除这个消息,然后handler的handleMessage方法就被Looper调用,由于我们初始化handler的时候,使用的是handler所在线程的Looper对象来创建的,所以Looper对象运行在handler所在的线程,那么此时,我们就成功的将消息切换到了handler所在的线程来处理了。

  • ThreadLocal相关概念

    前边的讲述中我们也提到,ThreadLocal是一个线程内部的数据存储类,通过他可以在指定的线程内部存储数据,数据存储之后,只有指定线程可以访问,其他线程无法获取。

    ThreadLocal的概念我们日常中使用的比较少,只有当某些数据的作用域需要是某个线程的时候,我们才会用到,Handler这里就用的非常的巧妙。

    下面我们来看一个ThreadLocal的最简单的使用例子,来理解一下他的特性,如果读者对ThreadLocal有了解的话,建议直接跳过:

    private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();
    
    mThreadLocal.set(true);
    Log.d(TAG,"Thread main-->"+mThreadLocal.get());
    
    new Thread(){
        @override
        public void run(){
            mThreadLocal.set(false);
            Log.d(TAG,"Thread 1-->"+mThreadLocal.get());
        }
    }.start();
    
    new Thread(){
        @override
        public void run(){
            Log.d(TAG,"Thread 2-->"+mThreadLocal.get());
        }
    }.start();

    运行程序之后,日志打印出来的为主线程true,线程1位false,线程2为null。日志我就不贴了。

    这里我们可以看出来,虽然在不同线程中访问的是同一个ThreadLocal对象,但是他们获取到的却是不一样的结果。因为ThreadLocal的内部有一个成员变量,专门用来存储各个线程的索引,当某个线程去操作ThreadLocal的时候,如果是首次访问,没有该索引,get方法回去初始化创建该线程的索引,并返回初始值,set方法则会创建索引之后,再根据索引,去修改对应的数据的值。

    这里具体的ThreadLocal的get和set方法的源码就不贴了,有兴趣的可以自行查看,里边的内容涉及一定的算法。

  • Looper

    在handler的机制中,Looper的作用就是不断的从MessageQueue中查看是否有新的消息,如果有新的消息就去处理,没有的话则一直阻塞。

    looper的初始化和调用方法前边已经提到,就是Looper.prepare()和Looper.loop()方法。这两个方法用于初始化非主线程中的Looper对象,主线程的Looper对象的初始化则由Loop.prepareMainLooper()方法来完成,在ActivityThread创建的时候去调用,当然,其本质也是通过调用Looper.prepare()来实现的。同时,Loop还提供了getMainLooper的方法,供我们在任意位置获取主线程的Looper对象。

    Looper也提供了用于退出循环的方法,quit和quitSafety,二者的区别在于quit是直接退出,而quitSafety则是不再接收新的消息,同时处理完当前消息队列中所有的消息之后才会退出。当Looper退出之后,handler发送消息会失败,send方法会返回false。当我们手动在子线程中创建了Looper对象之后,消息处理完毕,我们应该调用quit方法来退出Looper,减少资源的开销。

    Looper.loop()方法执行之后,整个handler的消息系统才算是正常的运作起来,loop方法的源码也比较简单,我们查看后可以发现,loop方法其实是一个死循环,唯一跳出该循环的方法就是MessageQueue的next方法返回null,而只有当我们调用了quit或者quitSafety的时候,消息队列会被标注为退出状态,此时,MessageQueue的next方法才会返回null,Looper才会退出,否则,当没有消息的时候,Looper会一直阻塞在那里。

  • handler发送消息的过程

    前边提到,我们可以调用handler的send和post方法来发送消息,但最终会调用到send方法,而send方法其实是一系列的方法,这些方法最终又会调用到enqueueMessage方法,向MessageQueue中插入一条消息,当MessageQueue中有新消息的到来,MessageQueue的next方法就会将该消息返回给Looper的loop方法来处理,最终消息通过Looper交给handler来处理,即handler的dispatchMessage方法被调用。

    在Handler的dispatchMessage方法中,会依次检测Message的callback,handler的callback是否为null,如果不为null则交给他们来处理,当二者都是null的时候,则会调用handler自身的handlerMessage方法来处理该消息。

至此,我们就将Handler的整个消息机制分析完毕了,主线程中的handler我们提到了一些,并没有详细的去描述,但其大体机制并没有什么差异。

不知道各位有没有一个疑问,Looper的loop方法没有消息的时候,就会一直阻塞,那么如果主线程中没有消息呢,不会阻塞主线程吗?

多虑了,主线程怎么可能没有消息,不说别的,光是UI刷新的消息就够了,何况还有很多其他的消息需要去处理,对吧?

参考资料:Android开发艺术探索

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值