Handler,Looper以及HandlerThread的解析

来源:互联网 发布:搜狐网络大厦 邮编 编辑:程序博客网 时间:2024/06/05 20:34

Handler,Looper以及HandlerThread的解析                                                        

本文为作者原创,转载请注明出处。公众号为 毛铜飞 欢迎关注


1.前言



之前在工作过程中,都是以写笔记的形式,记录一些开发中遇到的问题,以及平时学习的心德和成果。后来发现,写给自己看的笔记大多过于简洁,或者排版不雅观,导致再次去复习巩固的时候,可阅读性较差。也没法和大家一起共享和探讨到我写的内容,所以决定用订阅号的形式去改进一下自己的学习记录的方式,博客以及GitHub也会相应同步。希望大家多多支持。今天要分析的是在开发中或是在面试中非常常用也非常重要的Handler.以及和它相关的Looper,它俩的关系,以及HandlerThread的分析。


2.概述



就应用程序而言,Android系统中的Java的应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下:

  1. 有一个消息队列,可以往这个消息队列中投递消息。

  2. 有一个消息循环,不断从消息队列中取出消息,然后处理。

我们可以用下面这个图来展示这个工作过程:




从图中可以看出:

1.事件源把待处理的消息加入到消息队列中,一般是加至队列尾部,一些优先级高的消息也可以加至队列头,事件源提交的消息可以是按键,触摸屏等物理事件产生的消息,也可以是系统或应用程序本身发出的请求消息。

2.处理线程不断从消息队列头中取出消息并处理,事件源可以把优先级高的消息放到队列头,这样,优先级高的消息就会首先被处理。

在Android系统中,这些工作主要就是由Looper和Handler来实现的。


3.Looper分析



首先我们先看一下Looper的源码:


上图是Looper的所有成员变量,我们可以看出一共有四个比较重要的成员变量.

  1. 它封装了一个消息队列mQueue,

  2. 里面有一个mThread即Looper当前所在的Thread。

  3. ThreadLocal,是 java中的线程局部变量类,全名是Thread Local Variable ,它的实现应该是和操作系统提供的线程本地存储(TLS)有关系,这里就不做深究,总之,该类有两个关键函数set:设置调用线程的局部变量。get:获取调用线程的局部变量.

  4. sMainLooper即当前初始化时暂存在ThreadLocal的looper.


再看一下Looper里面最重要的两个函数,prepare()和loop()。下面是prepare的源码:




从上图我们可以看到prepare其实就是构造了一个Looper对象。设置到调用线程的局部变量中去,而Looper对象内部封装了一个消息队列。也就是说,prepare函数通过ThreadLocal机制,巧妙地把Looper和调用线程关联在了一起,那它为什么要这样做的,我们再看一下Looper的另外一个重要的函数loop(),下面是源码:






通过上面的源码我们其实就很清楚Looper到底是干什么的,它封装了一个消息队列,接收事件源传递过来的消息,Looper的prepare函数把这个Looper和调用prepare的线程(也就是最终处理的线程绑定在一起了)。该处理的线程调用loop函数,处理来自该消息队列的消息。好,那么问题来了,事件源是怎么向Looper消息队列添加消息的呢?我们接下来看看Handler。


4.Handler分析



首先我们先看一下Handler的成员变量和构造方法:





从上图其实我们可以看出,Handler构造方法里面是需要Looper的,当我们没有传入Looper的时候是从Looper.myLooper(),里面获取的,而Looper.myLooper()我们从之前的源码里看到,是在looper.prepare()的时候初始化的,所以Handler的实例化必须是在looper.prepare()之后的。那么问题来了,一些同学肯定会疑问,为啥我在Activity里面new Handler()的时候从来没有prepare()过呢。我们回看一下Looper里面的一个方法:


通过注释我们可以看出,在我们的主线程中android会自动帮我们建立一个looper。那么问题又来了,我们之前看到Looper.loop()方法是一个死循环,Android的主线程里面竟然存在一个死循环,为什么不报ANR?这里其实就是Handler的一个精华问题所在。源码太长我就不贴了,大家可以看一下ActivityThread,和ApplicationThread的源码,其中ApplicationThread是负责发送消息的可以详细看一下,可以发现其实我们Activity中所有的生命周期方法全部都是由Handler和Looper和消息队列构造的,之后就没有其他代码了,所以不存在ANR的问题,所以你每次点击一个view其实都是一次Handler传输。怪不得每次面试都逃不开Handler,看来还是有原因的。


好了,言归正传,继续看Handler的构造方法,从上面的构造方法可以现,Handler中的消息队列变量最终会指向Looper的消息队列,Handler为何要如此做呢?在回答这个问题之前我先来问一个问题,怎么往Looper的消息队列里面插入消息呢?如果没有不知道Handler,还有另外一个比较原始的方法可以插入消息:

1.调用Looper的myQueue,它将返回消息队列的对象MessageQueue,

2.构造一个Message,填充它的成员,尤其是target变量。

3.调用MessageQueue的enqueueMessage,将消息插入消息队列。

这种原始的方法特别麻烦,而且容易出错,但有了Handler后,我们的工作就变得很简单了,它更像一个辅助类,指向消息队列的辅助类,帮我们简化了工作。Handler提供了一些函数,帮助我们完成创建消息和插入消息队列,大家可以看一下API就能一目了然。接下来我们看看Handler是如何处理消息的。



5.Handler的消息处理



刚才我们往Looper的消息队列中加入了一个消息,按照Looper的处理规则,它在获取消息后调用了target的dispatchMessage函数,先看源码



这里的处理消息的优先级一目了然。

  1. Message如果自带了callback处理,则交给callback;

  2. Handler如果设置了全局的mCallback则交给mCallback处理。

  3. 如果上述都没有,则交给handler子类实现的handlerMessage来处理。

通常情况下都是用第三种方式来处理。好了Handler的消息处理基本就是这样。接下来我们再看一个比较重要的线程HandlerThread.


6.HandlerThread介绍



为什么要介绍这个呢,我先给大家举一个例子,请看代码:




这是我写的一个例子,代码很简单:

  1. 在主线程中创建了一个子线程,并且子线程通过Looper处理消息。

  2. 主线程中得到子线程的Looper,并且根据这个Looper创建一个Handler,这样发送给该Handler的消息由子线程处理。


很可惜上面的代码是有问题的。在onCreate生命周期的lpThread.myLooper;这里是有很大的问题的,因为myLooper的创建是在主线程中的而赋值却是在子线程中进行的,很有可能子线程还没有给他赋值,而主线程就取到myLooper的初值了,也就是null 。这是一个很常见的Handler和Looper的同步问题。有些同学可能会说那就直接 Handler handler=new Handler(Looper.myLooper());但是这样调用返回的是主线程的Looper而不是我们想要的子线程的,对于这个问题,HandlerThread就能完美解决。我们来看一下HandlerThread是怎么解决的,




主线程调用getLooper()来获取新的Looper,HandlerThread运行run函数,looper就在这里创建,其实写法很简单,就是用了一个wait()在还没有的时候等一下,在有的时候notifyAll一下唤醒,就解决了我们的难题,为了避免重复发明轮子,大家还是多用HandlerThread类吧~


好了。今天就说到这了,大家有什么问题欢迎留言,非常感谢~~

阅读全文
0 0