深入理解Handler

作者: 曾是放牛娃 | 来源:发表于2017-10-29 23:32 被阅读79次

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

        }

    }

    相关文章

      网友评论

        本文标题:深入理解Handler

        本文链接:https://www.haomeiwen.com/subject/vtuupxtx.html