Android Handler消息传递机制深入解析
众所周知,Handler被广泛用于Android多线程之间的消息传递,我们带着几个问题对Handler消息机制进行分析:
1、Handler是如何实现跨线程的消息传递?
2、为什么在子线程中创建Handler之前,要先调用Looper.prepare()? 为什么主线程中不用?
3、子线程中能不能创建多个Looper?为什么?
4、为什么Handler创建消息一般用handler.obtainMessage(),而不是直接new Message()?
首先我们来看看handler发送消息
Handler handler = new Handler();
handler.sendMessage(handler.obtainMessage(1, new Object()));
使用handler包装一个消息,然后使用sendMessage发送消息。这里为什么要使用obtainMessage()包装生成Message实例呢?
我们看到handler.obtainMessage(),定位到Message类中的obtain()方法。
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();
}
Message类中维护了一个消息回收池,如果消息回收池中没有消息对象,就会新建一个消息返回,如果消息回收池中有消息对象,则利用缓存对象。消息回收池中的消息是在消息被处理之后加入到回收池中,因此使用handler.obtainMessage()方法获取消息实例,能有效地利用被回收的消息对象,避免过多的重复创建,从而提高效率。
继续看sendMessage方法,最终会调用下面的方法,意思是将消息加入到消息队列MessageQueue中。
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);
}
那么,这个消息队列MessageQueue哪里来的呢?我们看到Handler的构造方法中,MessageQueue是由mLooper提供。
mLooper 为空时,会抛出异常,这个异常很眼熟,在现场中创建Handler,如果没有调用Looper.prepare(),就会抛出该异常。
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;
}
我们发现myLooper()就是获取Looper 对象,prepare()方法创建Looper对象,因此子线程中没有调用prepare()会报错。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
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));
}
那为什么主线程中不调用该方法没有问题呢?我们看到主线程ActivityThread类中的main方法,原来主线程已经帮我们做了这件事,自然就不会报错了。
public static void main(String[] args) {
//省略代码
Looper.prepareMainLooper();
//省略代码
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//省略代码
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们再来看看Looper.prepare()方法,如果重复调用prepare()方法程序将会报错,为什么呢?试想如果一个线程产生多个Looper对象,将产生多个消息队列,这样存消息和取消息都将发生紊乱,因此一个线程可以有多个handler实例,但是只能有一个Looper实例和一个消息队列。
发送消息是这样那是如何获取消息的呢?有意思的是我们在子线程中不写Looper.loop(),代码正常运行,但是handler无法收到消息,因此我们猜测,消息通知是通过Looper.loop()方法执行的。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
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 {
}
// 省略代码
msg.recycleUnchecked();
}
}
通过源码分析,我们发现Looper.loop()建立了一个死循环,不断的从消息队列中获取消息进行派发,msg.target就是我们的handler,因此我们能通过handler.handleMessage(Message msg)方法获取到发送的消息对象。
总结
回想一下,发现线程间传递数据,实际上就是多线程之间共享数据对象,对象存在JVM堆上,堆是所有线程共享的数据区域。
Handler为我们搭建了完善的数据缓存机制与消息传递机制,保障消息准确传递的同时,避免了过多的开销。
其它文章
转载于:https://www.jianshu.com/p/1885469f877c
推荐阅读