学习Android时,在请求网络数据或者进行其他耗时操作时,大家都懂得使用
结合
去进行耗时操作,并将获得的结果呈现到UI上。而其中的原理就涉及
,
,
,
的关系,其实对于这个知识点,基本从学android开始,每个人就看到来自四面八方的资料,我自己之前也写过博客记录其中的原理,但是依然不够详细,连自己回头看都觉得没法完全看懂。今天决定再来一发,努力把这个知识点讲得简单,详细。
Looper是什么
首先我们得先了解
是什么,普通的线程
一般在执行完当前任务之后就停止执行其他操作了。如果有这样的场景:有断断续续的任务来临(可能每隔10秒,20秒来一个新任务),这些任务都是交付给某个线程去执行,那么普通的线程就无法满足这样的需求。而
的作用此时就体现了,它可以使你的线程满足这样的场景,
可以使普通的线程获得轮询的功能,它执行完一个任务后,将轮询是否有新任务来临,如果有新任务,则去处理新的任务,任务执行完后,继续去轮询新的任务。以上就是Looper存在的作用。
这里先大致列出一个简单的例子,也是在网上到处可见的例子,这个例子其实就来自于
的源码注释里。如下:
如上,调用
和
之后,当前线程才能变成轮询线程了,我们可以通过
的引用,调用其
方法就可以不断往该线程发送任务。另外,如果你是在其他线程中持有上面的
,在其他线程中往当前线程发送任务,那么你就能实现跨线程通信了。有没有觉得这个场景很熟悉,没错,就是平时我们从执行网络操作的工作线程中,往UI线程中发送消息的场景。
这里只是举个最简单的例子,不过已经涵盖了最重要的思想。但是看到这里还是可能看不懂的,放心,后面会讲解Looper的源码。
Looper解析
,
,
,
这四个类代码层面的联系,其实有点复杂,因为相互之间有彼此的引用。
先来打开
的源码看看,可以看到它有如下成员变量
这里需要注意的变量有三个
,
,
。
-
sThreadLocal:借助
类使得每个线程都只对应一个Looper对象(除了UI线程的Looper)。ThreadLocal类的原理这里不展开,大家只需要知道它使得多个线程可以持有同个对象相互独立的引用即可。
-
mQueue:任务队列,
从这个任务队列中轮询取出任务
-
sMainLooper:UI线程的
。
接下来看看两个比较重要的方法
和
,首先是
方法,如下:
这个方法比较简单,主要是新建一个
对象,存放在
变量中,并初始化消息队列
.
接下来再看看
方法
这个方法主要是获取保存在
中的
,然后获取
对象中的消息队列
,并在一个
循环中轮询消息队列,拿出消息
并进行处理,处理消息的关键代码是这一句1 | msg.target.dispatchMessage(msg);
|
,消息对象
中持有一个
引用,而
的1 | dispatchMessage(Message m)
|
方法将消息最终传递到我们平时经常重写的1 | handleMessage(Message m)
|
方法。接下来讲解
,
的源码。
Handler原理
首先来看看
关键的成员变量
一个
只能对应当前线程,也只能对应一个
,
。稍微做个总结,一个线程只能有一个
,一个
,但是却可以有多个
。如下图
我们接下来进一步探索
的源码了,看看究竟是怎么发送和处理消息的。
首先来看是在哪里处理消息的。从上面将
的源码时,我们发现,处理消息是在
的
方法中进行,该方法的源码如下:
从上面可以看出,处理消息的地方可以有三种可能
- Message对象自带的回调接口
- Handler自带的回调借口
- Handler的handlerMessage()方法
看完处理消息的地方,再来看看
的几个发送消息的API
- post(Runnable)
- postAtTime(Runnable, long)
- postDelayed(Runnable, long)
- sendEmptyMessage(int), sendMessage(Message)
- sendMessageAtTime(Message, long)
- sendMessageDelayed(Message, long)
其实上面6个方法,分为
和
两种,每种都有三个发送消息的方式,不难发现一一对应。那么,为什么
发送的是一个
对象,而
发送的是一个
对象。
不难发现,以上六个方法,其实最终都是调用
。
而其中
方式的三个方法中都调用了
构造一个
,该方法的源码如下:
该方法构造一个
并赋值一个处理消息的回调接口,所以不难发现,
方式发送的消息,都是在方法传递进来的
中进行的。此处
仅仅是接口的概念,无关线程。
理解了上面6个发送消息的方法后,看
的其他发送消息的重载方法就很容易懂了。
Message原理
首先来看看Message主要的成员变量
类还提供了一系列
的重载方法,这一系列方法都是通过在一个
内存池中获取一个
对象,减少内存开销,所以推荐使用该方法,而非每次都适用
的方式。
UI线程其实就是一个轮询线程
大家应该熟悉
结合
的用法,并且都是在UI线程按照大致如下的方式建立
对象,
通过上面新建的
,然后在某个执行耗时操作的线程中,借助
这个引用发送消息给
方法处理。不难发现,UI线程中的
可以接收并处理随时到来的任务,所以其实UI线程就是一个轮询线程了,但是我们前面讲过,调用
和
之后,当前线程才能变成轮询线程,但是我们这里并没有看到在UI线程中调用这两个方法啊。不急,我们打开
的源码看看,如下:
我们调用
之后,最终会进入
方法,打开其源码:
从这段代码可以看出,这里并没有去新建并初始化一个
,紧接着,在
的源码中又看到一个段代码:
原来真相是这样的,子啊新建
之前,UI线程已经为你准备好了一个
了,但是还有一个疑问,上面的代码可以看出,UI线程貌似只调用了
方法,还没调用
方法啊,那么后面这个方法,UI线程是在哪里调用的呢?这个只能上网查查资料了,最终发现是在一个叫
中调用到,包括上面的
,如下:
到这里基本就理清了为什么UI线程是轮询线程,也是因为UI线程已经默认调用了
和
,我们平时才能那么方便的使用
结合
去请求网络数据并呈现到UI上。
小技巧
某个方法被调用时,我们不知道它是被UI线程调用还是其他子线程调用,我们可以这样
再来看看Volley中如何将网络请求结果传回主线程。最重要的一步代码如下,在建立请求队列时,传入了一个ExcutorDelivery对象,该对象中又包含一个Handler对象,这个Handler绑定于主线程,没错,最终就是靠这个传入的handler将网络请求结果回调给主线程。