深入探索Android中的Handler

开源码农 8年前
   <p>一、概述</p>    <h2>1. 什么是Handler</h2>    <p>    Handler是Android消息机制的上层接口,它为我们封装了许多底层的细节,让我们能够很方便的使用底层的消息机制。Handler的最常见应用场景之一便是通过Handler在子线程中间接更新UI。Handler的作用主要有两个:一是发送消息;二是处理消息,它的运作需要底层Looper和Message的支撑。MessageQueue即消息队列,它的底层用单链表实现;Looper则负责在一个循环中不断从MessageQueue中取消息,若取到了就交由Handler进行处理,否则便一直等待。关于Looper需要注意的一点是除了主线程之外的其他线程中默认是不存在Looper的。主线程中之所以存在,是因为在ActivityThread被创建时会完成初始化Looper的工作。</p>    <h2>2. 为什么使用Handler</h2>    <p>    总的来说,<strong>Handler的作用是将一个任务切换到指定的线程中去执行</strong>。我们知道Android只允许主线程去更新用户界面,而主线程需要时刻保持较高的响应性,因此我们要把一些耗时任务交给工作者线程去执行。那么问题来了,如果工作者线程执行完任务后想要更新UI该怎么破?我们要保证的是主线程不处于阻塞状态的同时能够接收到工作者线程的通知,并且能根据工作者线程执行任务的结果对用户界面进行相应的更新。好消息是Handler能让我们很方便的做到这些。Handler的工作过程大致如下图所示:</p>    <p> <img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/3968d808eb9a371790a5aebff780eb53.png" width="897" height="567"></p>    <p>     我们针对上图做下简单解释(详细的分析请见后文):首先我们在主线程中创建Handler对象并定义handleMessage方法,这个Handler对象默认会关联主线程中的Looper。通过在工作者线程中使用该Handler对象发送消息,相应的消息处理工作(即handleMessage方法)会在主线程中运行,这样就完成了将任务从工作者线程切换到了主线程。</p>    <p> </p>    <p>二、Handler的工作原理分析</p>    <p>    总的来说,Handler对象在被创建时会使用当前线程的Looper来构建底层的消息循环系统,若当前线程不存在Looper,则会报错。Handler对象创建成功后,就可以通过Handler的send或post方法发送消息了。调用send/post方法发送消息时,实际上会调用MessageQueue的enqueueMessage方法将该消息加入到MessageQueue中。之后Looper发现有新消息会取出,并把它交给Handler处理。下面我们通过分析相关源码来详细介绍这一过程。</p>    <h2>1. ThreadLocal的内部工作机制</h2>    <p>    ThreadLocal是一个线程内部的数据存储类。通过使用ThreadLocal,能够让同一个数据对象在不同的线程中存在多个副本,而这些副本互不影响。Looper的实现中便使用到了ThreadLocal。通过使用ThreadLocal,每个线程都有自己的Looper,它们是同一个数据对象的不同副本,并且不会相互影响。下面我们现在探索下ThreadLocal的工作原理,为分析Looper的工作原理做好铺垫。</p>    <h3>(1)ThreadLocal使用示例</h3>    <p>    作为ThreadLocal的一个简单示例,我们先创建一个ThreadLocal对象:</p>    <pre>  <code class="language-java">private ThreadLocal<Integer> mIntegerThreadLocal = new ThreadLocal<Integer>();</code></pre>    <p> </p>    <p>   然后创建两个子线程,并在不同的线程中为ThreadLocal对象设置不同的值:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 mIntegerThreadLocal.set(0);   2 Log.d(TAG, "In Main Thread, mIntegerThreadLocal = " + mIntegerThreadLocal.get());   3    4 new Thread("Thread 1") {   5     @Override   6     public void run(){   7         mIntegerThreadLocal.set(1);   8         Log.d(TAG, "In Thread 1, mIntegerThreadLocal = " + mIntegerThreadLocal.get());   9     }  10 }.start();  11   12 new Thread("Thread 2") {  13     @Override  14     public void run() {  15         Log.d(TAG, "In Thread 2, mIntegerThreadLocal = " + mIntegerThreadLocal.get());  16     }  17 }.start();</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    在以上代码中,我们在主线程中设置mIntegerThreadLocal的值为0,在Thread 1中该设置为1,而在Thread 2中未设置。我们看一下日志输出:</p>    <p><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/e959ae270a1f39a20f293d4200ff51a5.png" width="660" height="55"></p>    <p>    通过日志输出我们可以看到,主线程与Thread 1的值确实分别为我们为他设置的,而Thread 2中由于我们没有给它赋值,所以就为null。我们虽然在不同的线程中访问同一个数据对象,却可以获取不同的值。那么ThreadLocal是如何做到这一点的呢?下面我们通过源码来寻找答案。</p>    <h3> </h3>    <h3>(2)ThreadLocal的工作原理</h3>    <p>    Thread类内部有一个专门用来存储线程对象ThreadLocal数据的实例域,它的声明如下:</p>    <pre>  <code class="language-java">ThreadLocal.Values localValues;</code></pre>    <p> </p>    <p>    这样一来,每个线程中就可以维护ThreadLocal对象的一个副本,而且这些副本不会互相干扰,ThreadLocal的get方法只要到localValues中去取数据就好了,set方法也只需操作本线程的localValues。我们来看一下set方法的源码:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java">1 public void set(T value) {  2     Thread currentThread = Thread.currentThread();  3     Values values = values(currentThread);  4     if (values == null) {  5         values = initializeValues(currentThread);  6     }  7     values.put(this, value);  8 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    第3行通过values方法获取到当前线程的localValues并存入values中,接下来在第4行进行判断,若localValues为null,则调用initializeValues方法进行初始化,否则会调用put方法将value存进去。实际上,localValues内部有一个名为table的Object数组,ThreadLocal的值就存在这个数组中。下面我们来看以下put方法的源码,来了解如何将ThreadLocal的值保存到table数组中。</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 void put(ThreadLocal<?> key, Object value) {   2     cleanUp();   3    4     // Keep track of first tombstone. That's where we want to go back   5     // and add an entry if necessary.   6     int firstTombstone = -1;   7    8     for (int index = key.hash & mask;; index = next(index)) {   9         Object k = table[index];  10   11         if (k == key.reference) {  12             // Replace existing entry.  13             table[index + 1] = value;  14             return;  15         }  16   17         if (k == null) {  18             if (firstTombstone == -1) {  19                 // Fill in null slot.  20                 table[index] = key.reference;  21                 table[index + 1] = value;  22                 size++;  23                 return;  24             }  25   26             // Go back and replace first tombstone.  27             table[firstTombstone] = key.reference;  28             table[firstTombstone + 1] = value;  29             tombstones--;  30             size++;  31             return;  32         }  33   34         // Remember first tombstone.  35         if (firstTombstone == -1 && k == TOMBSTONE) {  36             firstTombstone = index;  37         }  38     }  39 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    我们主要关注一下第11行到第15行,从这几行代码我们可以了解到,ThreadLocal的值在table数组中的索引是key(即ThreadLocal对象)的reference字段所标识的对象的索引的加一。意思就是,若ThreadLocal对象的reference字段在table数组中的索引为i,那么ThreadLocal的值在table数组的索引就是i+1。</p>    <p>    了解了set方法的大致逻辑后,我们再来看一下get方法都做了些什么:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 public T get() {   2     // Optimized for the fast path.   3     Thread currentThread = Thread.currentThread();   4     Values values = values(currentThread);   5     if (values != null) {   6         Object[] table = values.table;   7         int index = hash & values.mask;   8         if (this.reference == table[index]) {   9             return (T) table[index + 1];  10         }  11     } else {  12         values = initializeValues(currentThread);  13     }  14   15     return (T) values.getAfterMiss(this);  16 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    第4行中,获取localValues。第5行若判断为null,则表示未进行设置(比如上面例子中的线程2),就会返回默认值;若判断非空就先获取table数组,然后再计算出index,根据index返回ThreadLocal的值。</p>    <p>    </p>    <p>   经过以上对get和set方法的源码的分析,我们了解到了这两个方法实际上对不同的线程对象会分别操作它们内部的localValues,所以能够实现多个ThreadLocal数据对象的副本之间的互不干扰。了解了ThreadLocal的实现原理,下面我们来探索下Looper是怎么借助ThreadLocal来实现的。</p>    <p> </p>    <h2>2. Looper的内部工作机制</h2>    <p>    在介绍Looper的工作机制之前,我们先来简单的介绍下MessageQueue。MessageQueue对消息队列进行了封装,在它的内部使用单链表来保存消息。MessageQueue主要支持以下两个操作:</p>    <ul>     <li>enqueueMessage:向消息队列中插入一个消息</li>     <li>next:从消息队列中取出一个消息(会从队列中删除该消息)。next方法内有一个无限循环,它会阻塞在这知道取到消息</li>    </ul>    <p>   </p>    <p>    大致了解了MessageQueue后,让我们一起来探索Looper的内部工作机制,看看它是如何漂亮的完成将任务切换到另一个线程这个工作的。我们首先来看一下Looper的构造方法:</p>    <pre>  <code class="language-java">private Looper(boolean quitAllowed) {      mQueue = new MessageQueue(quitAllowed);      mThread = Thread.currentThread();  }</code></pre>    <p> </p>    <p>    我们可以看到Looper的构造方法中创建了一个MessageQueue对象。之前我们提到过Handler只有在存在Looper的线程中才能创建,而我们看到Looper的构造方法是private的,那么我们怎么为一个线程创建Looper呢?答案是使用Looper.prepare方法,这个方法的源码如下:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 public static void prepare() {   2     prepare(true);   3 }   4    5 private static void prepare(boolean quitAllowed) {   6     if (sThreadLocal.get() != null) {   7         throw new RuntimeException("Only one Looper may be created per thread");   8     }   9     sThreadLocal.set(new Looper(quitAllowed));  10 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    我们可以看到prepare方法内部调用了Looper的构造器来为当前线程初始化Looper,而且当前的线程的Looper已经初始化的情况下再调用prepare方法会抛出异常。</p>    <p>    创建了Looper后,我们就可以开始通过Looper.loop方法进入消息循环了(<strong>注意,主线程中我们无需调用loop方法,因为ActivityThread的main方法中已经为我们调用了</strong>)。我们来看一下这个方法的源代码:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 public static void loop() {   2     final Looper me = myLooper();   3     if (me == null) {   4         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");   5     }   6     final MessageQueue queue = me.mQueue;   7    8     // Make sure the identity of this thread is that of the local process,   9     // and keep track of what that identity token actually is.  10     Binder.clearCallingIdentity();  11     final long ident = Binder.clearCallingIdentity();  12   13     for (;;) {  14         Message msg = queue.next(); // might block  15         if (msg == null) {  16             // No message indicates that the message queue is quitting.  17             return;  18         }  19   20         // This must be in a local variable, in case a UI event sets the logger  21         Printer logging = me.mLogging;  22         if (logging != null) {  23             logging.println(">>>>> Dispatching to " + msg.target + " " +  24                         msg.callback + ": " + msg.what);  25         }  26   27         msg.target.dispatchMessage(msg);  28   29         if (logging != null) {  30             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  31         }  32   33         // Make sure that during the course of dispatching the  34         // identity of the thread wasn't corrupted.  35         final long newIdent = Binder.clearCallingIdentity();  36         if (ident != newIdent) {  37             Log.wtf(TAG, "Thread identity changed from 0x"  38                         + Long.toHexString(ident) + " to 0x"  39                         + Long.toHexString(newIdent) + " while dispatching to "  40                         + msg.target.getClass().getName() + " "  41                         + msg.callback + " what=" + msg.what);  42         }  43   44         msg.recycleUnchecked();  45     }  46 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    通过以上代码我们可以看到,在第13行会进入一个无限循环。接着在第14行,调用了MessageQueue的next方法,之前我们介绍过这个方法会一直阻塞直到从消息队列中取出一个消息。退出这个无限循环的唯一方法就是MessageQueue返回null。这可以通过调用Looper的quit方法来实现。当Looper的quit/quitSafely方法被调用时,会导致MessageQueue的quit/quitSafely方法被调用,这会导致消息队列被标记为“退出”状态,如此一来,MessageQueue的next方法就会返回null了。这告诉了我们,如果我们不调用Looper的quit方法,他就会在loop方法中的循环里一直运行下去。</p>    <p>    若在第14行中成功从MessageQueue中取得了一个消息,接下来就会对这个消息进行处理。第27行调用了msg.target的dispatchMessage方法,其中msg.target指的是发送这条消息的Handler对象,也就是说这里调用的是发送消息的Handler对象的dispatchMessage方法。注意,Handler的dispatchMessage方法实在创建该Handler时所使用的Looper中执行的,这样一来,便成功地将任务切换到了Looper所在线程。接下来,我们以分析dispatchMessage方法的源码为切入点研究一下Handler的工作原理。</p>    <p> </p>    <h2>3. Handler的内部工作机制</h2>    <p>    首先,我们接着上一步,看一下dispatchMessage方法的源码:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java">public void dispatchMessage(Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p> </p>    <p>    我们可以看到,这个方法中会首先判断msg.callback是否为null,若不为null则调用handleCallback方法。msg.callback是一个Runnable对象,实际上就代表着我们调用post方法放入MessageQueue中的Runnable对象。也就是说,若我们post了一个Runnable对象,就会调用handleCallback方法,这个方法的源码如下:</p>    <pre>  <code class="language-java">private static void handleCallback(Message message) {      message.callback.run();  }</code></pre>    <p> </p>    <p>    从以上代码我们可以看到,这个方法就是简单的调用了Runnable对象的run方法让它开始运行。</p>    <p>    回到dispatchMessage方法的代码,若msg.callback为null,就会判断mCallback是否为null,若不为null则调用mCallback的handleMessage方法,否则调用handleMessage方法。实际上这两个handleMessage方法都是我们创建Handler对象时定义的消息处理函数,只不过分别对应了两种不同的创建Handler对象的方式。调用mCallback的handleMessage方法表示我们创建Handler对象时传入了一个实现了Callback接口的的对象,而调用handleMessage方法表示我们创建Handler对象时继承了Handler类并重写了handleMessage方法。那么mCallback是什么呢?让我们先看一下Handler的构造方法:</p>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <pre>  <code class="language-java"> 1 public Handler(Callback callback, boolean async) {   2     if (FIND_POTENTIAL_LEAKS) {   3         final Class<? extends Handler> klass = getClass();   4         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&   5                     (klass.getModifiers() & Modifier.STATIC) == 0) {   6             Log.w(TAG, "The following Handler class should be static or leaks might occur: " +   7                     klass.getCanonicalName());   8         }   9     }  10   11     mLooper = Looper.myLooper();  12     if (mLooper == null) {  13         throw new RuntimeException(  14                 "Can't create handler inside thread that has not called Looper.prepare()");  15     }  16     mQueue = mLooper.mQueue;  17     mCallback = callback;  18     mAsynchronous = async;  19 }</code></pre>    <p><a href="/misc/goto?guid=4958876358410133590" onclick="copyCnblogsCode(this)" title="复制代码"><img alt="深入探索Android中的Handler" src="https://simg.open-open.com/show/51e409b11aa51c150090697429a953ed.gif" width="20" height="20"></a></p>    <p>    我们可以看到,mCallback被赋值为我们传入的第一个参数callback,callback即为实现了Callback接口的对象,Callback接口中只有一个方法,那就是handleMessage方法。</p>    <p> </p>    <p>三、参考资料</p>    <p><strong>1. Android SDK Sources</strong></p>    <p><strong>2. 《Android开发艺术探索》</strong></p>    <p><strong>来源:博客园</strong></p>