一、引言
说起消息机制,相信每一个Android开发都不陌生。从开始写Android代码的第一天起,我就一直在试图理解handler机制,这几年中也不断去读handler源码。最近有了一些收获,觉得自己已经非常了解handler源码了,故在这里做下总结,希望分享给需要的小伙伴。
首先先看如上这个异常:只有创建View的原始线程才能更新UI。这个异常在什么情况下出现呢?在Android系统中,只能在主线程更新UI,如果在工作线程更新UI,就会报这个异常。出现这个问题的根本原因是:Android中的View不是线程安全的。
最简单的办法是给View加同步锁使其变成线程安全的。这样做不是不可以,只是有两点坏处:
1. 加锁会导致效率低下
2. 多个地方更新UI,开发时很容易导致时序错误。
基于以上两点,这种方式被抛弃。所以在Android中,专门设置了一个线程来处理UI控件的更新,这样就做到了“职责明确,各司其职”,我们开发时也很好理解。当其他线程要更新UI,就发送消息交给UI线程去处理。
实现原理呢?一条线程如何发送一个消息给另外一个线程呢?是不是可以找到两个线程共用某个资源的地方?不同进程享用不同的内存空间,但是同一个进程的不同线程享用同一片内存空间,那让其他线程吧要处理的消息放到特定的内存空间上,处理消息的线程来这个内存空间来去消息处理不就可以了吗?事实上,在Android消息机制中,扮演这个特定内存空间的对象就是MessageQueue。Handler和Looper都是为了配合线程切换用的。
其实不仅仅线程之间,不同进程之间进行消息传递也是这个思路,找到一个公用的一个资源点,文件系统啊,共享内存啊。
在Android应用启动时会自动创建一个线程(程序的主线程,负责View的展示,事件消息的分发,所以主线程常常被称为UI线程)。在Android平台中引入Handler,通过在子线程发送消息给主线程来更新UI。
二、什么是Handler?
Handler是Android消息机制的上层接口。在我们日常的开发工作中,我们只需要知道如何创建Handler并复写它的handleMessage方法就可以使用Handler了。我们可以简单地把Handler认为是更新UI的一种机制,它可以让耗时操作放在子线程,更新UI的操作放在主线程。
Handler通过发送和处理Message和Runnable对象来关联相应线程的MessageQueue。
三、子线程如何更新UI?
Handler在这个时候就闪亮登场了!!
可以看到,在接口返回数据后,红框框出来的部分是发送了一个Message,而没有直接更新UI。然后在handleMessage中更新UI。
以上就是Handler的一个具体使用。
总结一下,Handler该如何使用呢?
(1)post(runnable)
通过查看post(runnable)源码可知,其源码实现其实是通过sendMessage来实现。
(2)sendMessage
其背后的原理又是什么呢?
四、Handler消息机制图解
首先来看这样一张图,这个图里包含了4大部分:Looper、MessageQueue、Message、Handler。
Looper是每个线程独有的,它通过loop方法,读取MessageQueue中的Message,并发送给Handler进行处理。
五、从源码角度理解Android消息机制
mHandler.sendMessage(msg)其实最终调用的是sendMessageAtTime(msg,long)方法。
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);
}
它其实就是调用了enqueueMessage方法将Message发送到MessageQueue尾部。接下来就等着别人来调用Handler中的方法了,可是是谁调用的,在哪调用的?
Loop.loop()方法一直遍历MessageQueue,阻塞线程,直到获取到一个Message,然后调用Message的一个成员变量target(其实就是Handler)的dispatchMessage(msg
)方法。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到最终调用了Handler的handleMessage方法。
这里应该注意的是loop方法,它其实是一个死循环,当ActivityThread一启动Looper的loop方法就一直在执行!其中,ActivityThread其实就是我们所谓的主线程。
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
不过在看过下面的源码你可能会疑问:这个loop方法明明在msg为null时return了啊,这不就执行结束了吗?其实不是的。我们来看看queue.next方法,可以看到它其实也是一个死循环,只有当发送过来的消息是“quit”的时候才会返回null。
在我们创建Handler的时候,它需要与Looper关联。MessageQueue就是Looper的成员变量mQueue。如果没有looper,那就会报错“Can't create handler inside thread that has not called Looper.prepare()”。所以在new Handler的时候一定要调用过Looper.prepare(),在主线程中new Handler()没有调用Looper.prepare()是因为主线程默认就调过这个方法。
public Handler(Callback callback, boolean async) {
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 = callback;
mAsynchronous = async;
}
到此,我们就深入理解了消息队列、Handler、Looper三者之间的关系:一一对应!
接着再看Looper类的 loop方法,我们知道,这其实是一个死循环,这里面一直在做一件事:
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
其中,msg.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);
}
}
一目了然,就是在handleMessage。
六、Handler引起的内存泄漏以及它的解决办法
内部类持有外部类的强引用,导致外部Activity无法被释放。
如何解决呢?
(1)handler内部持有Activity的弱引用,并把Handler改为静态内部类
(2)在Activity的onDestroy方法中removeMessageAndCallbacks
七、最后
这里从源码角度讲清楚了MessageQueue、Handler、Looper的关系。如有任何疑问,欢迎留言或邮件联系zhshan@ctrip.com,博主每天都会查看。
~~~~~~~~~~~~~~~华丽丽的分割线:View.post()到底做了啥?~~~~~~~~~~~~~~~~~~~~
在日常的开发过程中,很多奇葩问题总可以通过View.post()方法解决。比如,View.post()中的操作执行时,View的宽高已经计算完毕,所以经常看见在Activity的onCreate()里调用View.post()来解决获取View宽高为0的问题。我们来梳理一下,View.post()原理到底是什么,内部都做了啥事。
View.post()方法很简单,代码很少,我们来分析一下。
如果AttachInfo不为空,那就调用mAttachInfo.mHandler.post()方法;如果为空,那就调用getRunQueue.post()方法。
那就找一下,mAttachInfo是什么时候赋值的。通过查看源码,可以看到在dispatchAttachedToWindow中赋值,在dispatchDetachedFromWindow中置空。那dispatchAttachedToWindow什么时候被调用呢?至少我们清楚一点,在Activity的onCreate期间,这个方法还没有调。那为什么View.post()方法可以保证在View宽高计算完毕呢?
我们来看下getRunQueue()方法里做了什么。
所以,其实调用的是HandlerActionQueue.post()方法,我们跟进去看一下。
可以看到是将runnable作为参数创建一个HandlerAction对象,然后放到mActions数组里。那保存到数组之后,什么时候会执行呢?我们找到了这个方法executeActions
从名字可以看出这是从数组里取出Action处理,那它是在哪里调用的呢?
可以看到是在dispatchAttachedToWindow生命周期方法里调用的。这下我们的思路就清晰了。当执行View.post()方法时,页面还没有渲染完毕,那就会等到dispatchAttachedToWindow生命周期方法回调时调用。
我们可以猜想,这个方法肯定就是在页面渲染结束后才回调的,这里就不写验证过程了,具体可以去查看源码。
参考