最近通过微信公众号推文以及博客文章学习了Android的消息机制的原理,然后抽空写下了这篇文章,对自己学到知识进行梳理,也方便以后自己查阅。
主线程不处理耗时操作,子线程不更新UI
Handler消息处理机制在Android中占有非常重要的地位
系统启动后,系统主线程会创建一个Looper对象,然后调用Looper.loop()开启一个死循环,循环会不断地从消息队列中(MessageQueue)中取出待处理的消息(Message),Looper取到消息后就会回调Handler中的方法处理消息。消息队列中的消息是从哪里来的呢?没错,就是我们通过Handler发送过去的。大概流程如下图:
如果我们想使用消息机制进行更新UI的话有两种方法:
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
setText();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
子线程不是不能更新UI吗?怎么第二种方法更新UI的时候好像是在子线程。接下来看看源码吧,这个问题看完源码后自然会有答案。
首先看看上面使用到的Handler的两个构造函数
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
上面两个构造函数最终都是调用Handler(Callback callback, boolean async),接着往下看
public Handler(Callback callback, boolean async) {
//省略代码
//这里获取了一个Looper对象
mLooper = Looper.myLooper();
//Looper对象很重要,如果当前线程中没有Looper对象的话,会抛出异常,提示用户应该用Looper.prepare()创建一个
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
//从Looper中获取消息队列对象
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
到此为止,一个Handler就创建好了。然后呢,如果是使用第一种方式更新UI,就需要调用Handler的sendMessage()发送消息了,看看这一步是怎么处理的吧
//直接把延迟时间设为0
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
//对延迟时间进行校验
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在这里,把this,也就是当前Handler对象封装进消息对象中
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//最终调用消息队列中的enqueueMessage()方法
return queue.enqueueMessage(msg, uptimeMillis);
}
最终是调用MessageQueue中的enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
//省略代码...
synchronized (this) {
//...
msg.when = when;
Message p = mMessages;
boolean needWake;
//注意看when
从上面的源码中可以看出来,MessageQueue中的数据结构是单链表。到这里,就已经把消息成功插入到消息队列当中去了。好了,第一种方法已经成功插入消息了,接下来看看第二种方法了,从哪里开始看起呢?没错,就是从post()开始看:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
post()方法里面调用了sendMessageDelayed()方法,那最终还是调用enqueueMessage(),这里面的流程再上一步已经看过了,唯一的不同就是这个getPostMessage()方法:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public static Message obtain() {
synchronized (sPoolSync) { //从消息池中获取消息,避免创建新对象
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
在getPostMessage中把Runnable封装进了消息当中,最终把这个消息插入到消息队列里。
这时候,就已经成功把消息(Message)插入到MessageQueue中了,并且知道了Handler把自己封装进了Message当中。走到这里,我们好像走进了一个死胡同,走不下去了?先来看看下面几个问题:
我们在上面的步骤中,没有创建过Looper对象,而Looper对象却已经存在了,那唯一的一种可能就是,当我们启动系统的时候,系统已经帮我们创建好Looper对象了。我们在ActivityThread类中找到main()方法,ActivityThread类是一个隐藏类,我们可以在SDK文件夹中查找出来:
public static void main(String[] args) {
//...
Looper.prepareMainLooper(); //创建一个Looper对象
//...
Looper.loop(); //开启循环
//...
}
果然,在main()中发现了Looper。main()方法中通过Looper.prepareMainLooper()创建一个Looper对象,prepareMainLooper()方法实际上就是调用Looper.prepare(boolean quitAllowed),这个方法是不是很熟悉,没有记错,它就是在Hanlder构造方法里面的异常提示中出现的方法,不过main()中调用的是带参的prepare()方法,而我们之前看过的是Looper.prepare(),先看看源码:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
public static void prepare() {
prepare(true);
}
可以看到Looper.prepare()最终也是调用Looper.prepare(boolean quitAllowed)。
很简单,prepare()中就是new了一个Looper,并把Looper set进ThreadLocal中(ThreadLocal是什么来的?我暂时还没有去了解,有时间会补充),所以Looper在系统启动时就创建好了,创建Handler时才不会报错
Looper创建完成之后,然后Looper.loop()就会开启一个循环,开启循环做什么呢?就是之前说过的,从MessageQueue中不断的取出消息来
//下面省略了部分代码
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);
} finally {
//...
}
}
}
很明显可以看出loop方法一直遍历MessageQueue,阻塞线程,直到获取到一个Message,然后调用Message的一个成员变量target的dispatchMessage方法。之前说过了,target其实就是Handler,dispatchMessage方法最终就调用我们重写的Handler的handlerMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
到这里好像已经都结束了,对了,好像还有之前使用post()方法时的一个问题,看看上面的源码,msg.callback不正是当初使用post传进来的Runnable,它不为空的话就调用handleCallback():
private static void handleCallback(Message message) {
//直接调用子线程的run方法
message.callback.run();
}
由Java多线程的知识,主线程直接调用子线程的run方法,那相当于还是在主线程中,和普通的调用方法没有区别,所以我们用post传入Runnable更新UI的时候不会报错。
以上就是消息机制的原理了,让我们整理一下上面的知识:
知道消息机制的原理后,我们就可以轻松的在任何线程下使用handler了:
new Thread() {
@Override
public void run() {
Looper.prepare();
Looper.loop();
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
}
}.start();
最后,我们对消息机制有一些了解了,先写这么多吧,如有错误欢迎指出。