当执行诸如加载列表、Http请求、Socket数据包解析等任务时,通常都需要我们另外开启线程执行。这时如果任务中出现需要更新应用界面的操作时,我们是不能直接更新的。 与Java不同,Android中只允许在UI/主线程更新应用界面。否则会抛出异常:
E/AndroidRuntime(4507): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its.
所以,这时我们不得不将更新界面的操作发往UI线程并由UI线程实现界面的更新。这是实际开发中用到最多的线程间通信场景,目前使用较为广泛的线程间通信方案有两种,一种是由Android提供的Thread + Handler方案,另一种方案是使用第三方库EventBus。相对于Thread + Handler,EventBus只能在Android组件中(Activity、service、ContentProvider等)间使用,所以局限性较大。有兴趣的朋友请参考EventBus官网:http://www.eventbus.org/。
Thread + Handler式的线程间通信,涉及到Message、MessageQueue、Looper、Handler四个主要类,以及ThreadLocal类、synchronized关键词等关联内容。
一、通信基础类
a.Message类
Message(消息)类是通信内容的载体。线程间的通信包含哪些数据、是什么目的、是异步还是同步、要求什么时候执行其中封装的操作等等信息,都由消息保存。消息类设置有消息缓存池,推荐取消息时从缓存池中取。
Android线程间通信(一):Message
b.MessageQueue类
MessageQueue(消息队列)是Message(消息)的管理者,它负责保存消息的集合,执行消息入队、出队等操作,同时提供SyncBarrier(同步障碍器)与IdleHandler(闲时任务)机制。SyncBarrier机制允许暂停部分Message的出队,而IdleHandler机制允许在没有消息需要出队处理时执行一些简单的任务。
Android线程间通信(二):MessageQueue(上)
Android线程间通信(二):MessageQueue(中)
Android线程间通信(二):MessageQueue(下)(待更)
c.Looper类
Looper类负责循环地从消息队列取消息,并通过消息中的Handler或者Runnable对象完成消息的处理。Looper对象和线程对象是一一对应的,一个线程只能拥有至多(可以没有)一个Looper对象,一个Looper有且只属于一个线程。
Android线程间通信(三):Looper
d.Handler类
在Android整个线程间通信的结构中,Message是通信内容的载体,MessageQueue是Message的管理者,Looper负责从MessageQueue中循环地取消息并分发给对应Handler处理,而Handler是Message的发布者兼处理者。
Handler是实现线程间通信的关键,任何从其他线程发往本线程的消息都要通过Handler发送。Handler在创建时会默认绑定本线程的Looper,这是Handler发出的消息能够被本线程接收的关键。
Android线程间通信(四):Handler
二、通信流程简介
我们撇开细节捋一捋整个通信的流程。在线程a和线程b之间通信:
1)在线程b创建Handler对象并保证在线程a中能够获取整个对象;
2)线程a获取b创建Handler对象,并通过它将需要通信的内容发往线程b;
3)Handler发出的内容以消息的形式进入线程b的消息队列中等待处理;
4)线程b是接收方,所以需要为其申请Looper对象。并在run()中执行对应Looper的loop();
5)loop()从对应消息队列中取消息,取出后交给发出该消息的Handler对象分发;
6)Handler对象根据消息类型分发消息,指定处理消息的处理程序(Handler本身或者Runnable实现类)。
三、通信流程跟踪
就这例子我们来看一下消息是怎么一步步在线程间传递并最终处理的,从其他线程传递信息给主线程是线程间通信的特殊场景不能作为通用的示例讲解。
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
public class MainThreadActivity extends Activity{
public static final int COMMUNICATION_TEST = 12306;
private Handler mHandler = null;//接收线程创建,发送线程使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
threadCommunication();
}
private void threadCommunication(){
//receiver
new Thread(){
@Override
public void run() {
Looper.prepare();//为线程申请并绑定Looper
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("threadCommunication",msg.what+"");
}
};
Looper.loop();//开始消息循环,会造成阻塞
}
};
receiver.start();
//sender
new Thread(){
@Override
public void run() {
//保证mHandler创建后再调用。可能此时Looper.loop()还没开始运行,但不妨碍消息进入消息队列,
//只可能影响消息处理的时间。
while(mHandler == null)
Thread.yield();
//发送空消息
mHandler.sendEmptyMessage(COMMUNICATION_TEST);
try {
//使用完后退出消息循环处理
Thread.sleep(50000);
mHandler.getLooper().quit();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
1.申请并绑定Looper
默认情况下非主线程并不和Looper绑定,当我们需要让一个线程作为接收线程接收通信内容时,需要手动为线程申请并绑定Looper。这是整个通信流程需要调用的第一个相关方法:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//线程局部(变量)
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");
}
sThreadLocal.set(new Looper(quitAllowed));
}
//创建消息队列对象,并保存当前线程(的引用)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
方法为线程申请了一个Looper对象并放入ThreadLocal对象中,ThreadLocal对象的作用是为每个访问它的线程提供专属于该线程的变量值。
2.创建Handler
接下来,我们需要在接收线程定义一个Handler用于发送和处理消息,示例中使用了重写dispatchMessage()的方式来处理消息。我们来看一下创建上述Handler使用到的方法:
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
……(当前Hander子类检查,与流程无关)
mLooper = Looper.myLooper();//得到当前线程的Looper,在本例中即receiver线程的Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//保存消息队列
mCallback = callback;
mAsynchronous = async;//是否是异步Handler,在本例中不是。
}
我们可以看到,在创建Handler时会把当前线程(即receiver线程)的Looper保存在Handler对象中。
3.开启消息队列的循环处理
在第一步创建Looper时,Looper的消息队列也会随之创建。接下来,在其他线程发送消息过来之前我们需要让receive线程开始循环处理消息队列,即调用Looper.loop()。推荐这样做,但在这步之前发送消息也不会妨碍消息进入消息队列,只可能会影响消息的处理时间。
Looper.loop()会调用消息队列的next()方法取消息。因为此时消息队列中并没有消息,消息队列选择阻塞接收线程,等到可以取出消息再唤醒线程并将消息返回给Looper.loop()。
Message next() {
final long ptr = mPtr;
……(代码略。如果消息循环正在退出或者已经废弃,直接返回null。)
int nextPollTimeoutMillis = 0; //阻塞时长
for (;;) {
……(不相关内容)
//nextPollTimeoutMillis为0立即返回,为-1则无限等待(必须主动唤醒)。ptr是指针,涉及本地方法不深究。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//now等于自系统启动以来到此时此刻的非深度睡眠时长
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //队首消息
//如果当前队首的消息时设置的同步障碍器(target为null)。
if (msg != null && msg.target == null) {
// 因为同步障碍器的原因而进入该分支,找到下一个异步消息之后才会结束while。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//此时msg一定是普通消息或者null,一定不是同步障碍器
if (msg != null) {
if (now < msg.when) {
//队首第一个非障碍器的消息执行时间未到,计算阻塞时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {//一切正常,开始取消息
mBlocked = false;//不阻塞线程
if (prevMsg != null) {//如果跳过了队首的同步障碍器取异步消息
prevMsg.next = msg.next;
} else {//如果当前消息就是队首消息
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
return msg;
}
} else { //消息队列为空,或者队首是SyncBarrier且队列中无异步消息
nextPollTimeoutMillis = -1;//-1表示无限等待 ————》本例会永久阻塞线程,直到被唤醒
}
//所有待处理的消息均处理完成, 接下来处理闲时任务
……(因为当前代码块在无限for循环中,此时再次验证一下消息队列是否正在退出。如果是,则废弃消息队列并返回null。)
……(初始化闲时任务)
//闲时任务列表为空,或者不是第一次执行到这里
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
……(初始化闲时任务)
}//synchronized结束
……(如果是本次调用next()过程中第一次到达这里,则执行闲时任务。如果不是第一次,则不会执行到这里)
//将待处理的IdleHandler个数设置为0,使得本次next()的调用再也不会到达这个for循环。
pendingIdleHandlerCount = 0;
//因为执行了闲时任务花费了一段时间(迭代开始处的阻塞方法还未执行到所以还未阻塞),此时再根据之前
//计算出的阻塞时长阻塞线程显然不合适。
nextPollTimeoutMillis = 0;
}//for(;;)结束
}
4.发送消息
到这一步,接收线程已经作好了接收通信内容的准备。可以开始往其中发送消息,在示例中的发送线程中我们使用了众多发送方法中最简单的一种,其他相关方法可参考姊妹篇Handler详解。
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//SystemClock.uptimeMillis()得到系统正常运行的时间,线程通信模块均使用系统正常运行时间来表示时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 将消息msg加入消息队列queue中,uptimeMillis即为消息的执行时间<em> when </em>。
*/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //将发送该消息的Handler,也即当前Handler保存在消息中。分派消息时会使用这个字段
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler类中用于发送消息的方法很多,但是都是这样一步一步包装消息并且最终调用Handler的私有方法enqueueMessage()来完成消息入队的。因为我们发送的是一个空消息,所以在包装该消息的过程中,已经将当前时刻设定成了该消息希望被处理的时间。到这里,应用开发者已经完成了所有线程间通信需要完成的内容。
5.将消息加入消息队列
从这一步开始直到消息处理完毕,都有Android系统完成。当Handler包装好消息后,就需要消息队列来接收这一个消息。特别提醒一点,入队消息这个操作依旧是在发送线程完成。
PS:分析代码时,可滤过主动唤醒的内容。暂时知道在本例中会主动唤醒接收线程即可。
boolean enqueueMessage(Message msg, long when) {
……(msg的合法性判断,不合法会终止入队)
synchronized (this) {
……(判断当前消息队列是否正在退出或者已经废弃)
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果队列首部为null,或者入队消息需要马上执行,或者入队消息执行时间早于队首消息,且线程已阻塞则都需要唤醒。
//如果 p!=null&&when!=0&&when>p.when,则不需要唤醒。
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;//mBlocked记录消息循环是否阻塞
} else {
/*在队列中间插入一个消息。一般情况下不需要唤醒队列(不是加到队首为什么要唤醒呢?),除
* 非队首是一个同步障碍器而且新插入的消息是 1)异步消息 2)执行时间是队列中最早 时。*/
//此处mBlocked值需要根据情况决定。当线程已经阻塞且队首消息是同步障碍器是新加入异步消息,needWake
//才可能(!!)为true。这还要判断消息队列中是否有异步消息,以及异步消息的处理时间早于还是晚于新加入的异步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous(); //如果是true也是暂时的,还有考验在等着呢!
//寻找位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
//能到达这里,说明msg.when > p.when。既然needWake是true,毫无疑问此时消息队列是
//处于阻塞的。这只有一种可能,p这个异步消息的执行时间还没到(情况4)!msg的执行时间还
//更晚(不更晚早break了),那就没有必要唤醒消息队列了。
needWake = false;
}
}//for结束
//插入新消息
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);//唤醒消息队列
}
}
return true;
}
主动唤醒相关内容,详细内容可参考姊妹篇MessageQueue(中)。
我们可以看到,方法是按照消息执行时间(when字段指定)的先后来组合消息的。我们发出的消息找到它应该放置的位置之后就会正式进入消息队列,此时接收线程早已启动循环处理操作,但是因为没有消息处理线程已经被阻塞了,在本示例中会主动唤醒接收线程。
6.接收线程被唤醒
本例中Handler所发送的消息触发了唤醒接收线程的操作,接收线程成功唤醒后开始继续执行消息队列的next()方法。此时我们发出的消息已经在消息队列中并且处于等待处理的情况,所以next()会返回这个消息然后结束。
7.消息循环取出一个消息(此时该消息会从消息队列中剔除)
loop()方法其实很简单,它用一个无限循环不断从消息队列中取消息、分派消息并让处理程序处理、回收消息:
/**
* 在当前线程中执行消息循环。确保在当前线程中保存有对应的Looper对象lI,并在调用looper()之后于其
* 他线程中通过lI.quit()/lI.quitSafely()终止loop()的死循环。
*
*/
public static void loop() {
final Looper me = myLooper();//得到当前线程对应的Looper对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
……
for (; ; ) {
Message msg = queue.next(); // 可能产生线程阻塞
if (msg == null) {
// 如果msg是null,表示消息队列正在退出或者已经被废弃
return;
}
……(打印日志)
//执行msg绑定的Runnable 或者 调用target的handleMessage()
msg.target.dispatchMessage(msg);
……(打印日志)
//不检查状态,直接回收消息
msg.recycleUnchecked();
}
}
8.消息循环分派消息
loop()从消息队列的next()中取出了一个消息,接下来它会让发送该消息的Handler(保存在消息的target字段中)开始分派消息。Handler分派消息的方法如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//callback是Runnable对象
handleCallback(msg);//调用msg.callback.run()
} else {
if (mCallback != null) {//mCallback是Handler的成员变量,含有handleMessage()方法
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//调用Handler的handleMessage(),默认情况下是空方法,需要重写
}
}
我们发送消息时并没有为其设置Runnable实现类对象,在创建Handler时也没有设置其mCallback属性,所以在本例中消息最后会交给handleMessage()方法处理,例子中我们重写了这个方法。
9.处理消息
最终我们发出的消息交给了handleMessage()方法处理,而我们重写了这个方法让它在日志里打印“12306”。在IDE的logcat中,我们可以找到和下列内容类似的日志:
11-19 17:47:58.530 7219-7238/? D/threadCommunication﹕ 12306
至此,消息得到处理。
10.回收消息
在loop()分派Handler处理完消息后,会调用消息的回收方法将清空消息内容并将其加入消息缓存池中。
11.退出消息循环
当确定线程间的通信已经全部结束后,我们需要退出接收线程的消息循环。这个时候因为接收线程还在执行loop()方法,所以我们需要从其他线程退出或者往其中发送一个消息通知其退出(需要自定义)。
退出消息循环需要调用Looper类的成员方法quit()/quitSafely():
/**
* 终止Looper。如想安全终止Looper,请参考lI.quitSafely()
* <p>调用该方法将会使得loop()在下一次循环时立刻终止,无论终止时MessageQueue中是否还有尚未处
* 理的消息。这之后无论以何种方式发布(post)消息都将会失败,譬如Handler#sendMessage(Message)会
* 返回false。</p>
* <p>
* 调用这个方法时,可能有一些消息在Looper终止前都不会被交付(delivery) ,因而这个方法并不安全。
* 考虑使用{@link #quitSafely}方法替代,从而保证所有本应执行完的工作能够有条不紊地执行完再结束Looper。
* </p>
*/
public void quit() {
mQueue.quit(false);
}
/**
* 安全地终止Looper。
* 调用该方法后,截止调用时刻的所有消息都能够如常被交付(delivery),而晚于该时刻的消息尽数被丢弃。
* 一旦处理完符合时刻的所有消息,loop()便会在下一次循环时终止。这之后无论以何种方式发布(post)消息都将
* 会失败,譬如Handler#sendMessage(Message)会返回false。
*/
public void quitSafely() {
mQueue.quit(true);
}
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
quit()/quitSafely()会调用消息队列的quit(),该方法将会使得next()方法返回null。loop()在next()返回null时,会立即退出无线循环。至此,接收线程的Looper使命完结。接收线程执行完Looper.loop()后的代码之后,接收线程正式关闭。