Android异步消息处理机制解析
点击上方“Android技术杂货铺”,选择“标星”
干货文章,第一时间送达!
本文来自手指尖的按恋投稿,博客地址:https://blog.csdn.net/gaolh89/article/details/90814343
Android异步消息处理机制主要是指Handler
的运行机制以及Handler
所附带的MessageQueue
和Looper
的工作过程。
本文将通过结合源码(api-28)的形式,全面解析Handler
和MessageQueue
、Looper
的关系。并分析Android异步消息机制的相关原理。在分析之前,先给出结论性的东西,便于在分析过程中有一个主脉络。
一、Handler
在分析Handler源码之前,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:
//代码片1
public class MainActivity extends Activity {
private Handler handler1;
private Handler handler2;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler1 = new Handler();
new Thread(new Runnable() {
public void run() {
handler2 = new Handler();
}
}).start();
}
}
运行代码之后,会报错: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
并且提示是第16行代码报错.说是不能在没有调用Looper.prepare()
的线程中创建Handler。
那我们在第16行代码前面尝试先调用一下Looper.prepare()
呢,即代码如下:
//代码片2
new Thread(new Runnable() {
public void run() {
Looper.prepare();
handler2 = new Handler();
}
}).start();
运行后发现的确没有报错了。
或许我们有疑问,为什么在子线程中加了Looper.prepare()
就不再报错了呢? 并且为什么第12行代码没有加Looper.prepare()
却没有报错。
先带着第一个问题(即 子线程加了Looper.prepare()
后不报错),来看看Handler的源码吧:
//代码片3
public Handler(Callback callback, boolean async) {
......
//仅贴出核心代码
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到,在第5行调用了Looper.myLooper()
方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误正是Can't create handler inside thread that has not called Looper.prepare()!
那什么时候Looper对象才可能为空呢?这就要看看Looper.myLooper()
中的代码了,如下所示:
//代码片4
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这个方法非常简单,就是从sThreadLocal
对象中调用get()
方法。
从名字我们可以大胆猜测一下,这是从sThreadLocal
取出Looper,如果没有Looper存在自然就返回空了。
既然有get
,应该也有set()
方法,我们搜索一下(在Looper.java
类中)sThreadLocal.set
,果然有:
代码如下:
//代码片5
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));
}
上述代码片即为Looper.prepare()
方法。
可以看到,首先判断sThreadLocal
中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()
方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
现在我们来通过源码分析 代码片1第12行代码没有添加Looper.myLooper()
方法也没有报错 的原因。这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()
方法。查看ActivityThread中的main()
方法,代码如下所示:
//代码片6
public static void main(String[] args) {
.....
//核心代码如下
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread(); //创建主线程
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到,在第5行调用了Looper.prepareMainLooper()
方法,而这个方法又会再去调用Looper.prepare()
方法,代码如下所示:
//代码片7
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//代码片8
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));
}
因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()
方法了。
简单总结一下: Android的主线程是ActivityThread,主线程的入口为main
,在main
方法中系统会通过Looper.prepareMainLooper()
来创建主线程的Looper对象,同时也会生成其对应的MessageQueue
对象,即主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象需手动通过Looper.prepare()
创建。
学习完了Handler如何创建对象,我们看一下如何发送消息:
这个流程相信大家也已经非常熟悉了,new出一个Message对象,然后可以使用setData()
方法或arg
参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了,示例代码如下:
//代码片9
new Thread(new Runnable() {
public void run() {
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();
那我们来一下sendMessage方法干了什么。源码如下:
//代码片10
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
//代码片11
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//代码片12
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);
}
//代码片13
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //把当前的Hanlder实例对象作为msg的target属性
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); //执行接下来即将分析的代码片14
}
代码片10~代码片13这四个方法都是连贯在一起的。
sendMessageAtTime()
方法接收两个参数,其中msg
参数就是我们发送的Message对象,而uptimeMillis
参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数( SystemClock.uptimeMillis()
)再加上延迟时间,如果你调用的不是sendMessageDelayed()
方法,延迟时间就为0
,然后将这两个参数都传递到MessageQueue
的enqueueMessage()
方法中。
也说明了handler发出的消息msg
,最终会保存到消息队列(MessageQueue
)中去。
二、MessageQueue
在一中已经讲到handler
发出的消息保存到了MessageQueue
中。消息队列在Android中指的是MessageQueue
,MessageQueue
主要包括两个操作: 插入和读取,分别对应enqueueMessage
和next
,其中enqueueMessage
的作用是往消息队列中插入一条消息,而next
的作用是从消息队列中取出一条消息并将其从消息队列中移除。
需要说明的是,MessageQueue
虽然叫消息队列,但它的内部实现并不是用的队列,而是通过一个单链表的数据结构来维护消息队列,单链表在插入和删除上比较有优势。
1. enqueueMessage源码
我们先来分析MessageQueue
的enqueueMessage
源码:
//代码片14
boolean enqueueMessage(Message msg, long when) {
.....
//仅贴出核心代码
synchronized (this) {
.....
//仅贴出核心代码
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//若消息队列里没有消息,则将当前插入的消息作为队头 & 若此时消息队列处于等待状态,则唤醒
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//若消息队列中有消息,则根据消息(message)创建的时间插入到队列中
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
观察上面的代码核心代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis
参数。具体的操作方法就根据时间的顺序调用msg.nex
t,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()
方法来发送消息的,它也会调用enqueueMessage()
来让消息入队,只不过时间为0
,这时会把mMessages
赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages
,这样也就完成了添加消息到队列头部的操作。
2. next 源码
入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?
这里先告诉结论,在Looper.loop()
中有这样一行代码(见 稍后会分析的代码片21的第12行)。
next()
方法在MessageQueue.class
中。这段代码比较长,我们只贴出核心代码:
//代码片15
Message next() {
......
//仅贴出核心代码
// 该参数用于确定消息队列中是否还有消息,从而决定消息队列应处于出队消息状态 or 等待状态
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
// 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1。下次循环时,消息队列则处于等待状态
nextPollTimeoutMillis = -1;
}
......
//仅贴出核心代码
} // 因为是死循环,回到分析原处
}
MessageQueue的next()
方法关键思路在源码中我已经通过中文注释标明了,它的简单逻辑就是如果当前MessageQueue
中存在mMessages
(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages
,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()
方法的代码片第12行,每当有一个消息出队,就将它传递到msg.target
的dispatchMessage()
方法中,那这里msg.target
又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()
方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()
方法的源码了,如下所示(Handler,class
中):
//代码片16
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在第5~6行进行判断,如果mCallback
不为空,则调用mCallback
的handleMessage()
方法,否则直接调用Handler
的handleMessage()
方法,并将消息对象作为参数传递过去。那 handleMessage(msg)、handleCallback(msg)
具体是干嘛的呢? 我们继续看:
//代码片17
public void handleMessage(Message msg) {
}
可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage
方法,然后根据msg.what
进行消息处理。例如:
//代码片18
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg){
switch (msg.what){
case value:
//do something
break;
default:
//do something
break;
}
};
};
至于handleCallback
的代码如下:
//代码片19
private static void handleCallback(Message message) {
message.callback.run();
}
执行的是Message 回调的run
方法.其实就是我们平时写的如下代码中的run
方法:
//代码片20
public class MainActivity extends Activity {
private Handler handler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
public void run() {
//do something
}
}).start();
}
}
三 、Looper
Looper
的源码分析在一、二部分或多或少提到过 ,这里集中分析。
Looper
主要是prepare()
和loop()
两个方法。
1. Looper.prepare()方法:
在Handler部分的代码片3、4、5、6、7、8及说明中已经谈论过,这里略过。
2. Looper.loop()方法
这段代码比较长,我们还是看核心代码:
//代码片21
public static void loop() {
final Looper me = myLooper();
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(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked(); //释放资源
}
}
可以看到,这个方法从第11行开始,进入了一个死循环,然后不断地调用的MessageQueue
的next()
方法(即代码片15部分,也就是 二中MessageQueue
的next()
部分),我想你已经猜到了,这个next()方法就是消息队列的出队方法。在二中已经分析过了。
总结一下,Looper主要作用:
与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
loop()
方法,不断从MessageQueue中去取消息,交给消息的target
属性的dispatchMessage
去处理。
到此,这个流程已经解释完毕,让我们首先总结一下
1、首先
Looper.prepare()
在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()
在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。2、
Looper.loop()
会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)
方法。3.Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
4.Handler的
sendMessage
方法,会给msg
的target
赋值为handler自身,然后加入MessageQueue中。(代码片13第3行有中文注释说明)5.在构造Handler实例时,我们会重写
handleMessage
方法,也就是msg.target.dispatchMessage(msg)
最终调用的方法。
看完了上述总结后,一个最标准的异步消息处理线程的写法应该是这样:
//代码片22
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();
}
}
那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()
方法中,这时的handleMessage()
方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:
四 、 其他在子线程操作UI的方法
另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:
1、 Handler的post()
方法
2、 View的post()
方法
3、 Activity的runOnUiThread()
方法
我们先看Handler的post()
方法:
//代码片23
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
这里分析一下getPostMessage(r)
方法:
//代码片24
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
原来这里还是调用了sendMessageDelayed()
方法去发送一条消息啊,并且还使用了getPostMessage()
方法将Runnable对象转换成了一条消息.
//代码片25
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
到了这里的sendMessageAtTime
方法,其实已经和第11个代码片完全一样了.
然后再来看一下View中的post()
方法,代码如下所示:
//代码片26
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
如果attachInfo
不为空,就调用Handler中的post()
方法。
如果为空,我们看看一下代码片25第10行代码:
//代码片27
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
即 看getRunQueue().post(action)
做看什么,只需要在HandlerActionQueue类中查看post()
方法即可。
继续看(HandlerActionQueue.class
类)
//代码片28
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
结合这篇文章(点击跳转),可以看出,如果attachInfo
为空,最终也会调用Handler中的post()
方法。
我们最后再来看一下Activity中的runOnUiThread()
方法:
//代码片29
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果当前线程不是UI线程,就调用Handler的post()
方法,反之直接运行(也可以结合方法上的注释看)。
对此,Android的异步消息处理机制全部分析完毕。
--- end---
推荐阅读:
Android 基于Transform实现更高效的组件化路由框架