对于Android的异步消息处理机制,大家一定都不陌生,异步消息处理机制一个常见的应用场景就是在子线程中更新UI,我们都知道,Android的UI是线程不安全的,如果在子线程中直接更新UI,便会导致程序崩溃。对于该问题,常见的解决方法是,在UI线程新建一个Handler并覆写其handleMessage方法,在子线程中获取Message对象,通过Message对象的arg,obj字段以及setData()方法携带一些数据,之后利用UI线程的Handler将消息发送出去,最后便可以在handleMessage方法中获取到刚刚发送的消息并进行相应的处理了,示例代码如下:
public class MainActivity extends ActionBarActivity {
private Handler handler1=new Handler(){
@Override
public void handleMessage(Message msg){
//to do sth
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable(){
@Override
public void run() {
Message msg=Message.obtain();
msg.what=1;
Bundle bundle = new Bundle();
bundle.putString("info", "info");
msg.setData(bundle);
handler1.sendMessage(msg);
}
}).start();
}
}
这种方法相信大家都已经用得很熟了,注意,此时我们是在UI线程创建Handler的,那么现在我们尝试在子线程创建Handler,看看与之前的UI线程创建Handler有何区别,示例代码如下:
public class MainActivity extends ActionBarActivity {
private Handler handler2=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable(){
@Override
public void run() {
handler2=new Handler();
}
}).start();
}
}
结果,系统竟然报错了,错误提示信息为Can't create handler inside thread that has not called Looper.prepare() 。意思是,不能在一个没有调用Looper.prepare()的线程内创建Handler。那么我们依据系统的意思,在创建Handler之前调用Looper.prepare()试试看:
new Thread(new Runnable(){
@Override
public void run() {
Looper.prepare();
handler2=new Handler();
}
}).start();
果不其然,这回终于不报错了。但是,仅仅解决问题是不够的,我们更应该去探究问题背后所隐藏的原理,只有这样我们的能力才会有一个质的提升,所以下面,我将带领大家从源代码级别深入地探究Android的异步消息处理机制。
OK,话不多说,让我们赶快进入这美妙的探索之旅吧~~~
前面已经讲到,在子线程中创建Handler时,如果事先不调用一下Looper.prepare(),系统便会报错。那么我们为什么一定要先去调一下Looper.prepare()呢?看来,只有Handler的构造函数以及Looper.prepare()的源代码能够告诉我们答案了,我们先从Handler的构造函数看起,Handler的构造函数源代码如下:
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
我们从mLooper = Looper.myLooper()这行代码看起,如果拿到的mLooper对象为空的话,便会抛出一个运行时异常,提示信息正是刚刚的“Can't create handler inside thread that has not called Looper.prepare()”,那么mLooper对象何时为空呢,这就要去看Looper.myLooper()中的代码了,Looper.myLooper()的代码如下:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
显然,这个方法是从sThreadLocal对象中拿出当前的Looper对象,如果拿不到的话则返回null。现在我们可以大胆猜测下在什么地方给sThreadLocal对象设置Looper的了,没错,就是在Looper.prepare()方法中,我们赶紧去看一下Looper.prepare()方法的源码:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
在Looper.prepare()中,会先去尝试获取sThreadLocal中的Looper对象,如果当前能够获取到Looper对象,则抛出运行时异常“Only one Looper may be created per thread”,这也说明了每个线程最多只能有一个Looper对象。如果获取不到Looper对象,则给sThreadLocal设置Looper对象。
说到这里,很多朋友可能都会有疑惑:为什么主线程中没有调用Looper.prepare(),却依然能够正常地创建Handler呢?
这是因为程序在启动时,系统已经自动帮我们调用了Looper.prepare()了,具体可以参照ActivityThread中的main()方法,这里就不去详述了,感兴趣的朋友可以去自行查阅。
我们继续去分析Handler中的源代码:
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
可以看到,在利用Looper.myLooper()获取到Looper对象并赋值给Handler的成员变量mLooper之后,又将 mLooper的mQueue赋值给Handler的成员变量mQueue,由此可见,Handler中拥有着Looper和MessageQueue两个成员变量,Handler的构造函数的主要目的就是初始化这两个成员变量,同时,一个Looper对应着一个MessageQueue。
分析完Handler的构造函数以及Looper.prepare()的源代码,我们再来研究一下消息的发送流程,先温习一下消息发送的代码:
Message msg=Message.obtain();
msg.what=1;
Bundle bundle = new Bundle();
bundle.putString("info", "info");
msg.setData(bundle);
handler1.sendMessage(msg);
看到这里,我们不禁要问:Handler到底将消息发到哪里?为什么之后在handleMessage中又可以收到之前发送的消息?
我们知道,Handler有很多发送Message的方法,其中,除了sendMessageAtFrontOfQueue方法,其他方法都会辗转调用到sendMessageAtTime方法,sendMessageAtTime方法的源代码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
可以看到,sendMessageAtTime方法有两个参数,第一个参数为msg,代表着我们发送的消息,第二个参数为uptimeMillis,代表着发送消息的时间,其值为自系统开机到当前时间的毫秒数加上延迟时间,如果不是调用的sendMessageDelayed方法,则延迟时间为0.之后,将当前Handler的MessageQueue对象(即mQueue)取出,判断MessageQueue对象是否为空,若不为空,则将当前消息的target属性指向当前发送消息的Handler对象(即this),最后,调用我们MessageQueue对象的enqueueMessage方法让消息进入消息队列中。
这里要稍微解释下MessageQueue,顾名思义,MessageQueue就是一个消息队列,其内部会以一个队列的形式存储我们发送的消息,并提供了消息的入队和出队方法。
接下来就要分析较为关键的enqueueMessage方法了,enqueueMessage方法会将消息放入消息队列中,并按照消息发送的时间对消息进行排序,enqueueMessage方法的源代码如下:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
enqueueMessage方法有两个参数,第一个是入队的消息,第二个是该消息发送的时间。前面提到的sendMessageAtFrontOfQueue方法也会调用到enqueueMessage方法,但传入的消息发送时间为0。在enqueueMessage方法的内部,会将当前入队消息的when字段设置为传入的消息发送时间,取出当前的队头消息并赋给变量p,之后判断如果当前队头消息为空或消息发送时间为0或消息发送时间小于队头消息的时间,则将当前入队消息的next指针指向队头消息,之后将队头消息重新赋值为入队消息,从而完成了将入队消息插入到消息队列头部的操作。如果不满足上述的三种情况,则按照消息发送时间的先后顺序寻找入队消息在队列中的插入位置,之后将入队消息插入即可。
上面是对消息的入队操作的分析,那么出队操作在哪里呢?我们就要去分析一下Looper.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();
}
}
}
在Looper.loop()中,会进入一个死循环,不断调用当前MessageQueue的next()方法取出下一条待处理的消息,如果当前没有待处理的消息则阻塞。之后,当消息不为空且消息的target字段不为空的话,调用消息的target字段的dispatchMessage方法,注意,此时消息的target字段就是当初发送这条消息的Handler对象。
接下来,我们进入到Handler的dispatchMessage方法的源代码中:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在Handler的dispatchMessage方法中,首先会去判断消息的callback字段是否为空,若不为空,则调用handleCallback方法对消息进行处理,若为空,则再去判断Handler的mCallback字段是否为空(Handler的无参构造函数中mCallback被设置为空),若不为空,则调用mCallback的handleMessage方法,若mCallback字段为空,则直接调用 handleMessage方法。
至此,我们已经从源代码级别回答了上面提出的两个问题:Handler到底将消息发到哪里?为什么之后在handleMessage中又可以收到之前发送的消息?相信大家一定都有很深的理解了吧_。
下面介绍一个Android异步消息处理线程的最标准的写法,此写法引自Android官方文档:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
现在,我们来思考一个非常关键的问题:handleMessage方法何时运行在主线程中,何时运行在子线程中?
有不少朋友可能会说,handleMessage方法的定义位于主线程中,其就会在主线程中执行,handleMessage方法的定义位于子线程中,其就会在子线程中执行。
事实真的是这样吗?
其实不然,我先举个反例。HandlerThread就是一个典型的反例。我们在主线程中定义Handler并覆写其handleMessage方法,在定义Handler时,我们传入一个已经启动的HandlerThread对象的Looper作为参数,那么,我们此时的handleMessage方法是运行在子线程中的,但此时我们handleMessage方法的定义是位于主线程中的。
这是怎么回事呢?明明handleMessage方法的定义是位于主线程中的,怎么会运行在子线程里面呢?看来还得再次分析一下我们的源码,不过这一次,我们需要对源码进行逆向分析。
首先,我们知道,handleMessage方法是在dispatchMessage方法中被调用的,而dispatchMessage方法又是在Looper.loop()中调用的,也就是说,如果Looper.loop()运行在主线程,handleMessage方法也会运行在主线程,如果Looper.loop()运行在子线程,handleMessage方法也会运行在子线程。那么我们的Looper.loop()到底是运行在主线程,还是在子线程呢?其实,这就要看我们定义的Handler用的是哪个线程的Looper了,如果我们定义的Handler用的是主线程的Looper,那么它使用的MessageQueue自然也是主线程Looper对应的MessageQueue,通过该Handler发送的消息会进入该MessageQueue中,之后会在主线程的Looper对应的Looper.loop()方法中不断取出该MessageQueue中的消息进行处理,注意,此时我们主线程的Looper对应的Looper.loop()方法也是运行在主线程中的,所以此时我们的handleMessage方法也是运行在主线程中的。定义的Handler用的是子线程的Looper的分析过程同上。总结一下,如果创建Handler时用的是主线程的Looper,则相应的handleMessage方法会运行在主线程中,如果创建Handler时用的是子线程的Looper,则相应的handleMessage方法会运行在子线程中。
看到这里,相信大家已经对Android的异步消息处理机制有了一个非常深入的理解了,如果对文中内容有疑惑的话请在博客下方留言,我会尽可能地解答,谢谢大家,望大家多多支持!