Handler应该是Android开发过程中使用最频繁的类了,但你真的理解Handler了吗?本文深入剖析Handler内部的实现机制,以及分析使用过程中常出现的内存泄漏的问题。本文针对使用过Handler的用户,没有再介绍Handler的使用。
Handler的用途
与Handler的相识相知,一般是通过子线程更新UI的Exception创造的机会。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Handler可以用于切换到主线程更新UI,但是它的作用绝不仅于此。源码中Handler类的注释说的很简单、明确。摘录如下,翻译水平有限:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
Handler通过线程的MessageQueue来发送和处理Message、Runnable。每一个Handler实例关联一个特定的线程和这个线程的消息队列。当创建一个新的Handler的时候,它会绑定到创建它的线程的消息队列,从这一刻起,该Handler就会分发messages和runnables到这个消息队列,并在他们出队的时候执行他们。
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
Handler的主要用途有2个:(1)延时执行(在将来某个时间调度)messages和runnables;(2)切换线程来排队处理一个动作(action)。
归结起来,Handler的用途有三个关键词:延时、切换线程、排队任务。更新UI主要是应用了Handler切换线程的功能(当然此时的排队处理也是附带在其中的),排队有时也是很重要的一个特性,例如一种场景,某些后台耗时任务需要顺序执行,此时就可以绑定一个Handler到子线程,然后发送任务,这些任务就可以顺序执行了。
那么,Handler是如何实现延时和线程切换的呢?延时是通过sleep的方式吗?
Handler的内部机制
我们从Handler的send**方法和post方法的调用开始,顺藤摸瓜,来探究其内部是如何实现线程切换和延时执行的?
所有的send**方法和post**方法最终都会调用下面两个方法中的一个:
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);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
可以看出这两个方法几乎是一样的,最终都会调用enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage方法没有做实际的工作,直接转到了MessageQueue的enqueueMessage方法,把一些异常判断去掉,保留基本的逻辑如下:
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 {
// 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;
}
这就是把消息插入队列,可以看到尽管MessageQueue叫做消息队列,但是它的内部实现是并不是队列,而是一个单链表的数据结构,mMessages就是链表的头Head。上面的enqueueMessage就是实现了链表的插入操作,不需要做过多的解释了。
现在消息已经放在消息队列中了,那么谁会到消息队列取消息呢?这里就不卖关子了,就是Handler中持有的Looper,Looper中的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;
// 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.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();
}
}
Looper的loop方法的工作过程也比较好理解,loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。在此循环中,会不停的通过MessageQueue的next方法去取消息,然后通过msg.target.dispatchMessage(msg),msg.target即是Handler对象,所以msg会交给对应的Handler处理。如果MessageQueue中没有消息时,next方法会一直阻塞在那里,导致loop方法也一直阻塞。
由于,此处的loop方法运行在创建Handler时绑定的Looper(线程)上,这样就完成了将代码逻辑切换到指定的线程中去执行了。
回过头了,再来详细看一下MessageQueue的next方法和Handler的dispatchMessage方法。
Message next() {
……
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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());
}
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
可以看到,next方法是一个无限循环方法,如果没有消息,那么next会一直阻塞;如果有新的消息就会跳出循环,从单链表中删除这条消息,并返回它。可以看到,阻塞是通过native代码实现的,next方法里调用nativePollOnce实现阻塞,具体的也分为两种情况,有消息,但是当前消息还没到处理的时间,此时会阻塞固定的时间;还有一种情况是,消息队列已经没有消息,此时会阻塞无限长的时间,直到外部来激活它(enqueueMessage方法中的nativeWake方法),具体的细节可以参看大神罗升阳的博客Android应用程序消息处理机制(Looper、Handler)分析。
此外,当当前没有消息需要处理,在进入阻塞前,会处理注册的IdleHandler接口,利用此接口也可以实现很多有价值的功能,具体可以参看Bugly公众号的一篇文章你知道Android的MessageQueue.IdleHandler吗。
接下来再来看一下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的同学都能明白,其中的handleMessage(msg)语句调用的就是我们重写的handleMessage方法。此处有一个需要讲解的点就是这里的mCallback有什么用处,Handler中的注释其实就说的很明白,此Callback接口的一个好处就是避免在实例化Handler时不得不实现其子类(也就是重写handleMessage方法),也可以通过设置callback而不去实现子类。
Handler的内部机制基本就是这样,其中有一个点说的不是很透彻,就是Looper.loop()中的final Looper me = myLooper(),是如何拿到当前线程的Looper的,简单的说,就是使用了Java中的ThreadLocal类的特性,它是一个线程内部的数据存储类,线程只能获取该线程存储的数据,而不会获取到其他线程存储的数据,后面准备单独写一篇文章来学习这个类,此处不再详细的说明,有兴趣的同学可以自行百度。
总结一下,Handler的内部机制由Handler、Looper、MessageQueue、Message共同实现了线程切换和延时执行的功能。下面的类图列出了相互的关系,各个类只列出了关键的几个public方法,切换线程的核心是目标线程中的Looper.loop方法不停的获取、处理消息队列的消息来实现的;延时执行时通过native代码的阻塞来间接实现的。
其具体的流程可以简单的表示为下图:
Handler使用过程中的内存泄漏问题
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
Android中使用Handler造成内存泄露的原因:
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 1) {
doSomeThing();
}
} };
上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
使用Handler导致内存泄露的解决方法可以有以下两个方法:
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类,内部使用弱引用持有外部类对象。
这是由于在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。
这里推荐使用第二种方法,这里引出下面Handler使用的最佳实践。
最佳实践
将Handler声明为静态类后,实现如下:
private static class MyHandler extends Handler {
private final WeakReferencemActivity;
public MyHandler(HandlerActivity activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
if (mActivity.get() == null) {
return;
}
if (msg.what == 1) {
mActivity .get().doSomeThing();
}
}
}
除此之外,当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,这些处理有必要? 正常Activitiy finish后,已经没有必要对消息处理,那需要怎么做呢? 解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。
如果在一个大型的工程的,我们也可以实现一个基类,来规范Handler的使用,例如谷歌内置的LatinIME中实现了这样一个基类可以借鉴,所有使用Handler的地方,都继承此基类来实现具体子类。
public class LeakGuardHandlerWrapperextends Handler {
private final WeakReferencemOwnerInstanceRef;
public LeakGuardHandlerWrapper(final T ownerInstance) {
this(ownerInstance, Looper.myLooper());
}
public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
super(looper);
if (ownerInstance == null) {
throw new NullPointerException("ownerInstance is null");
}
mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
public T getOwnerInstance() {
return mOwnerInstanceRef.get();
}
}