异步消息处理线程是指,线程启动后会进入一个无限循环体之中,每循环一次,从其内部的消息队列(MessageQueue)中取出一个消息,并且回调相应的消息处理函数,执行完一个消息后则继续循环。 如果消息队列为空,则线程会暂停,一直到消息队列中有了新的消息。
实现异步线程的一般思路
如图1:
图1
实现异步线程要解决的问题具体包括:
1.每个异步线程内部包含一个消息队列(MessageQueue),队列中的消息一般采用排队机制,即先到达的消息会先得到处理。
2.线程的执行体中使用while(true)进行无限循环,循环体中从消息队列中取出消息,并根据消息的来源,回调相应的消息处理函数。
3.其他外部线程可以向本线程的消息队列中发送消息,消息队列内部的读/写操作必须进行加锁, 即消息队列不能同时进行读/写操作。
Android中异步线程的实现方法
在异步消息线程内部可以有一个和多个Handler对象,外部程序(外部线程)通过该Handler对象向线程发送异步消息, 消息经由Handler传递到MessageQueue对象中。异步消息线程内部只会有一个MessageQueue对象,在异步消息线程主执行函数中从MessageQueue中读取消息,并回调Handler对象中的回调函数handleMessage()。如图2
图2
下面分析的是android异步消息线程内部的Handler,MessageQueue,Looper类的调用过程
线程局部存储(Thread Local Storage)
通过调用Looper类的静态方法prepare()为线程创建MessageQueue对象,该函数的代码如下:
public static final void prepare(){
if(sThreadLocal.get()!=null){
throw new RuntimeException("only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
在这段代码中,变量sThreadLocal的类型是ThreadLocal,该类的作用是提供“线程局部存储”,那么什么是线程局部存储呢(TLS)?
ThreadLocal类的对象的作用域是当前线程。 当同一个线程引用该变量时,其值总是相同的,而从不同的线程中引用该变量时,其值应该不同。
ThreadLocal就是能够提供这种功能的类,Looper内部的sThreadLocal变量是当该进程第一次调用Looper.prepare()是被复制的,之后该进程中的其他线程调用prepare()函数时,sThreadLocal变量就已经被赋值了。sThreadLocal对象内部会根据调用prepare()线程的id保存一个数据对象,这个数据对象就是所谓的“线程局部存储"对象,该对象是通过sThreadLocal的set()方法设置进去的,Looper类中保存的这个对象是一个Looper对象。
prepare()函数中首先调用sThreadLcoal.get()函数获取该线程对应的Looper对象,如果该线程已经存在Looper对象,则提示出错,否则,为该线程创建一个新的Looper对象。为什么一个线程只能又一个Looper对象呢?这仅仅是异步线程所需要的,因为每个Looper对象都会定义一个MessageQueue对象,一个异步线程中只能有一个消息队列,所以只能有一个Looper对象,这与“线程局部存储"本身没有什么关系。
注意:线程局部存储(TLS)变量 在本线程内的任何对象内保持一致
静态变量 在本进程内的任何对象保持一致
Looper类
Looper的作用有两点,第一是为调用该类中静态方法prepare()的线程创建一个消息队列;第二是提供静态函数loop() , 使用该函数的线程进行无线循环,并从消息队列中读取消息。下面先看第一点,如何创建消息队列。
在Looper的静态函数prepare()中,会给线程局部存储变量中添加一个新的Looper对象,Looper的构造函数中则会创建一个MessageQueue对象,如以下代码所示:
private Looper(){
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
对于一个程序员来说,当需要把一个线程变为异步消息处理线程时;应该在Thread类的run()函数中先调用Looper.prepare()为该线程创建一个MessageQueue对象,然后在调用Looper.loop()函数,使当前线程进入消息处理循环。loop()函数的代码如下:
public static final void loop(){
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while(true){
Message msg = queue.next();//might block
if(msg != null){
if(msg.target == null){
return ;
}
if(me.mLogging != null)
me.mLogging.println(">>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);
msg.target.dispatchMessage(msg);
if(me.mLogging != null)
me.mLogging.println("<<<< Finished to " + msg.target + " " + msg.callback);
msg.recycle();
}
}
}
这段代码的执行流程如下:
1.调用myLooper()函数返回当前线程的Looper对象,该函数内部仅仅通过调用sThreadLocal.get()方法返回当前线程id对应的Looper对象。
2.进入while(true)无限循环。
(1)调用MessageQueue对象的next()函数取出队列中的消息。注意如果当前队列为空,则当前线程会挂起,也就是说,next()函数内部会暂停当前线程。
(2)回调msg.target.dispatchMessage()函数,完成对该消息的处理,也就是说,消息的具体处理实际上是由程序指定的。msg变量的类型是Message,msg.target
的类型是Handler。
(3)每处理完消息后,需要调用msg.recycle()回收该Message对象占用的系统资源。因为Message类内部使用了一个数据池保存Message对象,从而避免不停的创建和删除Message类对象,因此,每次处理完该消息后,需要将该Message对象表明为空闲状态,以便使该Message对象可以被重用。
MessageQueue
消息队列采用排队的方式对消息进行处理,即先到的消息会先得到处理,但如果消息本身指定了被处理的时刻,则必须等到该时刻才能处理该消息。消息在MessageQueue中使用Message类表示,队列中的消息以链表的结构进行保存,Message对象内部包含一个next变量,该变量指向下一个消息。
MessageQueue中的两个主要函数是“取出消息"和”添加消息",分别为函数next()和enquenceMessage()。
Handler
尽管MessageQueue提供了直接 读/写 的函数接口,但对于应用程序员而言,一般不直接 读/写 消息队列。 前面讲过,在Looper.loop()函数中,当取出消息后,会回馈msg.target对象的handlerMessage()函数,而msg.target的类型正事Handler.
程序员一般使用Handler类向消息队列中发送消息,并重载Handler类的handleMessage()函数添加消息处理代码。
Handler对象只能添加到有消息队列的线程中,否则会发生异常。下面代码是Handler类的构造函数:
mLooper = Looper.myLooper();
if(mLooper == null){
throw new RuntimeException("can't create handler inside thread that has not call"
}
因此,在构造Handler对象前,必须已经执行过Looper.prepare(),但prepare()不能执行两次。
创建Handler对象可以在执行Looper.loop()函数之前,也可以在执行之后。在以往的应用程序开发中,程序员一般在Activity对象的初始化代码中添加Handler对象,事实上,在Activity对象被构造前,Activity所在的线程已经执行了Looper.prepare()函数。
一个线程中可以有多个Handler对象。在Looper.loop()函数中,不同的Message对应不同的Handler对象,从而回调不同的handleMessage()函数。