[聚合文章] 深入理解Handler

消息系统 2017-10-30 14 阅读

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的类图

其具体的流程可以简单的表示为下图:

Handler实现线程切换的流程

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();

}

}

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。