消息机制概述
从开发角度而言,Handler
是Android
消息机制的上层接口,所以,消息机制主要就是Handler
的运行机制,Handler
的作用就是在一个线程中发送消息,然后在另一个线程中处理,通过在消息中存放数据达到线程间通信。如果你想在哪个线程中接收消息,那你就要在那个线程中生成一个Handler
对象(当然实际上是在哪个线程中调用了Looper.loop()方法,那么就会在哪个线程中接收消息)。Handler的运行需要Looper
,MessageQueue
的支撑。所以接下来就分别分析这三个类。
MessageQueue
这个类翻译过来就是消息队列,但是这其实是一个链表,因为对于这个类而言,它的作用就是存取handler
发送的消息,它包含的主要操作就是插入和读取,读取的时候也会涉及删除操作,意思就是一个消息只能被处理一次,所以使用链表的结构会更好,因为链表在插入和删除上有优势。这个类的对象是在Looper
的构造函数中生成的,也就是说一个Looper
对象就对应自己的一个MessageQueue
对象,代码如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
而对于一个线程而言,就只有一个Looper
对象,所以一个线程就只有一个MessageQueue
对象。为什么一个线程就只有一个Looper
对象,我们继续往下看。
Looper
这个类的作用就是管理handler
发送的消息,也就意味着它需要和MessageQueue
一起使用,这可能就是在源码里为什么会将MessageQueue
的构造放在Looper
的构造函数里。在Looper
的构造函数里,会构造一个MessageQueue
对象,同时会得到当前的线程,代码如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这也就意味着Looper
的操作都是基于当前线程的,那我们是如何实现线程切换的呢?其实说起来很简单,我们只需要得到每个线程中的Looper
对象就可以了。那么,如何得到呢?接下来就介绍一个重要的类:ThreadLocal,掌声欢迎。
ThreadLocal
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程则无法获取到数据。这样说太抽象了,直接上代码:
public class MainActivity extends Activity {
private ThreadLocal<String> stringThreadLocal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestThreadLocal();
}
public void TestThreadLocal(){
stringThreadLocal=new ThreadLocal<>();
stringThreadLocal.set("main");
Log.e("log", "main=" + stringThreadLocal.get());
new Thread("thread1"){
@Override
public void run() {
super.run();
stringThreadLocal.set("thread1");
Log.e("log","thread1="+stringThreadLocal.get());
}
}.start();
new Thread("thread2"){
@Override
public void run() {
super.run();
stringThreadLocal.set("thread2");
Log.e("log","thread2="+stringThreadLocal.get());
}
}.start();
}
}
运行结果如下:
从上面的Log可以看出,在不同的线程中访问同一个ThreadLocal
对象,ThreadLocal
获取到的值是不一样的。这就是ThreadLocal
的奇妙之处。ThreadLocal
之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal
的get方法,ThreadLocal
内部会从各自的线程中取出一个数组,然后再从数组中根据当前的ThreadLocal
的索引去查找对应的value值。而不同的线程中的数组是不同的,这就是为什么通过ThreadLocal
可以在不同的线程中维护一套数据的副本并且彼此互不干扰。
这样的话就很简单了,通过这个ThreadLocal
类我们就可以轻松的得到各个线程中的Looper
对象了,从源码中我们也可以看出来:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
这里就贴这一段了,贴多了看着不好看,有兴趣的同学可以下去自己看一下Looper
的源码,其实很简单。既然我们可以得到各个线程中的Looper
对象,那么也就意味着其实我们就已经实现了线程切换。然后我们再看一下Loope
对象中的重要方法:
prepare()
:这个方法的作用是构造一个Looper
对象,调用的顺序是先调用prepare(boolean quitAllowed)
方法,然后调用sThreadLocal.set(new Looper(quitAllowed));
方法,其中quitAllowed
的值的作用就是区分主线程和其他线程,区分的作用是设置主线程不允许退出的。
loop()
:这个方法的作用是循环遍历整个消息队列,将handler发送的消息送给msg.target.dispatchMessage(msg);
处理,这是一个死循环,退出的唯一方式就是MessageQueue
的next()
方法返回null,msg.target
其实就是你定义的handler
对象,这样就实现了将消息又返回给handler
自身来处理。而且,实现了线程的切换。
quie()和quitSafely()
:这两个方法的作用就是退出loop()
循环,两个的区别就是,第一个是立即退出,第二个时等消息队列中的已有消息处理完毕再退出。所以,当我们手动创建Looper
的时候注意在合适的时候把loop
退出。
Handler
这个类的作用就是发送消息和处理消息,当你在创建一个Handler
对象的时候,在Handler
的构造函数中会得到当前的线程的Looper
对象,按照之前的分析,得到了Looper
对象其实就得到了MessageQueue
对象,这里注意是当前线程,当我们调用sendMessage(message)
方法的时候,会将一个消息发送到调用Looper.loop()
方法所在的线程的消息队列中,跟在哪个线程里调用sendMessage(message)
方法没有关系,然后创建Handler
的线程的Looper
对象的Loop()
方法就会将消息返回给handler
对象的dispatchMessage(msg);
来处理。也就是说将sendMessage(message)
在别的线程中调用,最后message
会在调用Looper.loop()
方法的线程中来执行。使用Handler
发送消息有两种方式,一个是post(runnable)
,还有一个是sendMessage(message)
,在调用第一个的时候,会调用getPostMessage(Runnable r)
方法,将Runnable
对象存储到Message
对象中,所以第一个还是调用第二个,下面看一下Handler
的dispatchMessage(Message msg)
,代码如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其中的msg.callback
就是Runnable
对象,而mCallback
就是当你创建Handler
是通过传递一个Handler.Callback
对象的时候的Handler.Callback
对象。
public Handler(Callback callback) {
this(callback, false);
}
最后就是Handler
自身的handleMessage(msg)
。这就是Handler
的dispatchMessage(Message msg)
的执行顺序,也就是先执行Runnable
对象,然后是Handler.Callback
对象,最后是Handler
自身的handleMessage(msg);
。
从使用流程分析
当我们创建一个Handler
的时候,在Handler
的构造函数里会调用Looper.myLooper();
来得到当前的线程的Looper
对象,得到了Looper
对象也就得到了当前线程的MessageQueue
对象,注意这里是当前线程,然后用创建好了的Handler
对象来发送消息,一般来说发送消息都会在别的线程,因为这样才有意义,然后当调用Handler
的sendMessage(message)
后,就会调用MessageQueue
的enqueueMessage(msg, uptimeMillis)
;方法,这样就将消息发送到了调用Looper.loop()
方法所在的线程的消息队列中,然后将消息发送给msg.target.dispatchMessage(msg);
来处理,其中的msg.target
就是你创建的Handler
对象,然后消息就得到了处理。在实际的开发中,我们的发送消息都会在别的线程,并且在这个线程发送消息之前会完成一些耗时任务,比如下载,然后下载完成后发送消息,在handleMessage(Message msg)
或者run()
去执行操作,因为这两个方法的调用都是在创建Handler
的线程中调用的,这样就完成了将下载任务指定到专门的下载线程中,并且能够在原来的线程中操作下载任务的结果。这也就是实现了将一个任务切换到指定的线程中工作。