之前写过一篇关于Android的内部消息机制的文章,现在回头在看看发现还是有很多不足之处的,加之在使用过程中对这方面的内容又有了进一步的了解,所以在这里对消息机制在做一次深入的分析。 在分析之前,我们需要弄清楚几个问题: 1. 消息机制在能够干什么 2. 消息机制在Android中的地位 3. 探究其实现原理
一,Android消息机制的作用 大家也都经常使用Handler做UI更新操作,所以大部分都是认为Handler就是用来做UI更新用的,因为子线程是不能更新UI的,需要使用Handler机制也做UI的更新操作,这是大家的共识。没错,Handler的确是ui更新比较好的方式。 其实,我个人觉得android这套消息机制,是线程之间的通信机制,只是用的比较多的是ui更新操作而已。在理说的ui线程,也就是主线程,这个线程是用来循环接受子线程的消息,并交给发送该消息的handler去处理。 也就是说只有”主线程”才能接受handler发送的消息,其实确切的说应该是Looper线程才能接受handler发送的消息,显然ui线程也是Looper线程,至于为什么是Looper线程,这个稍后分析,而Looper线程不仅仅是UI线程,任何一个子线程都可以成为Looper线程。 好了,现在我们已经知道Android这套消息机制可以用来说多线程之间的通信,和数据传递的功能,而且很强大。
二,Android消息机制的地位 地位要从功能说起,上面已经看到消息机制很强大,也很好用。可以说消息机制在Android中的地位是举足轻重的,而且设计思想也很优秀,值得深究。framework源码中用到Handler的地方非常多,比如异步数据库查询类AsyncQueryHandler,AsyncTask等这些都是对handler机制的封装。以及源码中两个Handler之间的通信使用是特别频繁,如果不理解这些内部原理,看上去是眼花缭乱。 对于这么重要的消息机制,要想好好的使用必然是要探究一下其原理的。
三,消息机制原理分析 Handler这一套机制主要设计的类如下: Handler Looper Message MessageQueue ThreadLocal 对于上面设计到的核心类进行一一的分析
ThreadLocal
这是一个线程内部的存储类,可以在执行的线程中存储数据,所以也只能在指定的线程中获取数据。这样说起来比较抽象,比如对于一个boolean类型的变量,多个线程都需要存储和获取这个数据,并且要求这些个线程之间对与该变量的存储和获取是互不干扰的,即在每个线程中都存在这个变量的存储副本(注意这个时候线程之间对这个变量的访问不是互斥的,因为每个线程都是有副本的,所以不需要互斥访问)。在这样的情况下,我们可以使用TheadLocal也实现。这里Looper使用了这个机制,那么Looper为什么需要使用这个机制呢? 也就是说,每个线程中都有一个独立的Looper副本,而每个Looper中都关联一个消息队列,再结合之前说的每个线程都可以成为Looper线程,我们可以就可以理解这么做的原因,那是因为这样可以使线程内的消息队列在线程之间是相互独立的,每个Looper线程值处理其对应的handler发送的消息,这样就很容易的做到消息机制的有条不紊的进行。而且多个线程之间也不会出现消息处理错乱问题。 对与理解ThreadLocal的运行原理,可以执行这样一个例子:
private ThreadLocal<Boolean> mBoolean = new ThreadLocal(); mBoolean.set(true); System.out.println(Thread.currentThread() + " " + mBoolean.get()); new Thread(new Runnable { public void run() { mBoolean.set(false); System.out.println(Thread.currentThread() + " " + mBoolean.get()); } }, "thread1").start() new Thread(new Runnable { public void run() { System.out.println(Thread.currentThread() + " " + mBoolean.get(); } }, "thread2").start()上面的例子中执行结果是main true, thread1 false, thread2 null 上面的log出现不分先后顺序,这里例子可以看出mBoolean在三个线程中都有备份,互不影响。 ThreadLocal就说到这。
2.消息Message和MessageQueue 对于Message就是我们需要发送的消息的载体,即是当我们使用sendMessage发送的具体内容的携带者,arg1, arg2, what,obj等变量来携带需要传递到Looper线程的内容。 对于MeaageQueue从字面意思就可以理解这是一个消息队列,用来存储消息和获取消息的地方。虽然是叫做消息队列,其内部并维护的并不是队列,实际上是通过一个单链表也维护消息列表的。 MessageQueue中有两个主要的方法enqueueMessage()和next(). enqueueMessage()源码如下
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { /*这里msg必须有target,止于handler和msg是何时关联 的,后面分许*/ } ... synchronized(this) { /** 这里就是单链表的插入操作,不做具体分析 */ } }下面看next()方法的源码
Message next() { /** 取出消息,并出链表中移除*/ ... for (;;) { /** 这里的for循环是在等待消息,没有消息就阻塞。消息一旦出现,next就会返回这条消息,并从链表中将其移除*/ } }对于Message和MessageQueue, 前者是线程间传递内容的载体,后者是前者队列的维护。
Looper循环者 Looper是循环着,即是不停的查看MessageQueue中是否有新的消息,如果有就立即处理,否则一直阻塞。首先看其构造方法 private Looper (boolean queitAllowed) { //Looper内部有一个消息队列的引用 mQueue = new MessageQueue(quitAllowed); //关联当前的thread,即looper所在的thread mThread = Thread.currentThread(); }我们知道Handler的工作必须要有Looper参与,想要让一个线程成为looper线程,其实也很简单
public class LooperThread extends Thread() { public void run() { //将当前线程初始化为looper线程 Looper.prepare(); ... //开始循环处理消息队列 Looper.loop(); } }通过上面的两行核心代码,我们就可以将一个普通线程变为looper线程,下面也具体分析实现过程
首先看prepare()方法的实现
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() == null) { throw new RuntimeException("Only one Looper may be created per thread"); } //存储当前线程的looper对象,且值创建一个 sThreadLocal.set(new Looper(quitAllowed)); }当Looper.loop()调用之后,Looper才真正的开始工作,不断的从消息队列中取出消息并执行
public static void loop() { final me = myLooper(); //获取当前线程的Looper对象 if (me == null) { } //当前looper的MessageQueue final MessageQueue queue = me.mQueue; ... //这里开始进入循环,取消息,处理 for (;;) { Message msg = queue.next(); /**交给Message关联的handler去处理,这里将消息处理的过程有切换到Looper线程*/ msg.target.dispathMessage(msg); ... } }实现Looper线程的过程分析完了,从这里我们起码可以看出三个结论 1. 每个线程只能有一个Looper对象,因为这是一个ThreadLocal的变量 2. 调用Looper.prepare()方法可以使一个线程变为looper线程 3. Looper中有一个MessageQueue的实例,loop()方法,进行循环取消息。 分析到这里,MessageQueue,Looper, 当前的线程都关联起来了,下面就是消息的发送和处理,以及Handler和Msg的关联,Handler的消息队列和Looper内部的消息队列相互对应起来
4.异步处理Handler Handler的主要工作是在子线程中发送消息和在Looper线程中接受消息并处理之。首先看Handler的构造方法
public Handler(boolean async) { this(null, async); } public Handler(CallBack callback, boolean async) { ... //Handler持有Looper的引用 mLooper = Looper.myLooper(); /**Handler的queue和Looper.mQueue关联,这样就是Handler消息入队和Looper出队的是同一个队列*/ mQueue = mLooper.mQueue; ... } /** 这个构造方法可以用来为Handler指定Looper线程*/ public Handler(Looper looper, Callback, boolean) { mLooper = looper; mQueue = looper.mQueue; ... }从上面的构造函数可以看出,Handler持有Looper的引用,也持有MessageQueue的引用,而且Handler和Looper持有的m Queue是同一个消息队列的引用,这样才能实现消息的发送和接受处理。接下来看Handler发送消息的过程
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg. 0); } public final boolean sendMessageDelayed(Message, long) { ... return sendMessageAtTime(...); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { ... return enqueueMessage(...); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { /**这里指定msg的执行这为当前的handler对象,handler和msg就此也关联起来了*/ msg.target = this; ... }上面就是Handler发送消息的过程,在发送消息的同时执行该消息的处理着,也就是发送该消息的Handler对象,该Handler对象位于Looper线程中。 注:一个Looper线程只能有一个Looper对象,这个是ThreadLocal决定的,而一个线程可以有多个Handler对象,当然这多个Handler对象是共同使用一个消息队列的,看源码可知。所以在使用时可以根据任务的不同分为多个Handler进行处理。 在Handler中还存在一个Callback接口,这个有什么作用呢?
/** 当你不想继承Handler创建一个类时,可以使用这个接口*/ public interface Callback { public booelan handleMessage(Message msg); }所以Handler中也有相应的构造方法
public Handler(Callback callback) { this(callback, false); }一上就是对于Handler机制的分析,这里没有说如何使用Handler进行UI更新和子线程之间进行数据通信,,仅仅是从源码实现的角度进行分析。 下面看两个特殊的Looper线程
5.HandlerThread 这是Android系统为我们实现的一个Looper线程,继承Thread
public class HandlerThread extends Thread { ... public void run() { ... //循环初始化 Looper.prepare(); ... //进入消息循环 Looper.loop(); } }HandlerThread的实现,和之前分析Looper时说过的让一个子线程成为一个looper线程的步骤是一样的。所以,我们在使用时不需要自己去创建一个Looper线程,系统已经为我们实现好了,只要继承HandlerThread即可。 而在使用时只是需要将Handler的Looper关联对该looper线程即可。
MyHandlerThread ht = new MyHandlerThread(); Looper looper = ht.getLooper(); Handler handler = new Handler(looper);上面可以看出HandlerThread使用起来很方便,这里不在分析。
注: 在使用Handler的过程中,最容易出现的就是内存泄漏的问题。那么为什么会出现内存泄漏呢? 1) 在回头看一下Handler,Looper,Mesage它们的关系就会明了。Handler持有Looper和MessageQueue的引用,Message持有Handler的引用,MessageQueue也持有Message的引用,Looper持有MessageQueue的引用。如果某一个引用一直占用不能释放,就不能被回收,导致内存泄漏。 2) 继承Handler,并作为内部类时,Handler持有Activity的引用,如果子线程中的任务比较耗时,且在子线程完成之前,activity就退出,则Handler持有的activity,activity虽然已经销毁但内存还是得不到释放,可以使用静态内部类加弱引用的方法解决之(静态内部类是不持有外部类引用的)。比如:
static class MyHandler extender Handler { WeakReferene<Activity> mActivityRef; public MyHandler(Activity activity) { mActivityRef = new WeakReference<>(activity); } public void handleMessage(Message msg) { if (mActivityRef.get() != null) { //处理消息 } } }3) 对于Message对象的获取
/** 可以通过这种方式获取*/ Message msg = new Message(); /** 推荐使用这样的方法来新建一个msg*/ Message msg2 = Message.obtain()好了,关于Android的消息机制,其实我们打交道最多的就是上层的Handler接口,对于Looper,MessageQueue怎么实现的关心比较少,但是这样的线程间通信的思想是必须要理解滴。