深入探讨Android异步精髓Handler

移动开发 Android
Google采用Handler把主线程和子线程精巧地联系起来——子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。除此以外,还有别的方式可以实现类似的操作么?答案是肯定的,我们也可以利用AsyncTask或者IntentService进行异步的操作。Handler是Android异步操作的核心和精髓,它在众多领域发挥着极其重要甚至是不可替代的作用。

前言

众所周知,Android的UI是在其主线程中进行刷新的,所以Google建议开发人员切勿在主线程中进行耗时的操作否则很容易导致应用程序无响应(ANR)。鉴于此几乎接近硬性的要求,我们常把耗时的操作(比如网络请求)置于子线程中进行;但是子线程不能直接访问UI。

至此,这个矛盾就凸显出来了:

  • 主线程可以刷新UI,但不能执行耗时操作
  • 子线程可以执行耗时操作 ,但是不能直接刷新UI

嗯哼,那有没有一个东西可以调和并化解这个矛盾呢?当然是有的,Google采用Handler把主线程和子线程精巧地联系起来——子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。除此以外,还有别的方式可以实现类似的操作么?答案是肯定的,我们也可以利用AsyncTask或者IntentService进行异步的操作。这两者又是怎么做到的呢?其实,在AsyncTask和IntentService的内部亦使用了Handler实现其主要功能。抛开这两者不谈,当我们打开Android源码的时候也随处可见Handler的身影。所以,Handler是Android异步操作的核心和精髓,它在众多领域发挥着极其重要甚至是不可替代的作用。

在此,对Handler的工作原理和实现机制进行系统的梳理。

ThreadLocal简介及其使用

对于线程Thread大家都挺熟悉的了,但是对于ThreadLocal可能就要陌生许多了。虽然我们对于它不太了解,但是它早在JDK1.2版本中就已问世并且被广泛的使用,比如hibernate,EventBus,Handler都运用了ThreadLocal进行线程相关的操作。如果单纯地从ThreadLocal这个名字来看,它带着浓浓的“本地线程”的味道; 然而,喝一口之后才发现根本就不是这个味儿。其实,ThreadLocal并不是用来操作什么本地线程而是用于实现不同线程的数据副本。当使用ThreadLocal维护变量时,它会为每个使用该变量的线程提供独立的变量副本;每一个线程都可以独立地改变自己的副本并且不会影响其它线程所持有的对应的副本。所以,ThreadLocal的实际作用并不与它的名字所暗含的意义相吻合,或许改称为ThreadLocalVariable(线程本地变量)会更合适一些。

接下来,我们通过一个实例来瞅瞅ThreadLocal的使用方式

  1. /** 
  2.      * 原创作者: 
  3.      * 谷哥的小弟 
  4.      * 
  5.      * 博客地址: 
  6.      * http://blog.csdn.net/lfdfhl 
  7.      */ 
  8.     private void testThreadLocal(){ 
  9.         mThreadLocal.set("东京热"); 
  10.         new HotThread1().start(); 
  11.         new HotThread2().start(); 
  12.         hot3=mThreadLocal.get(); 
  13.         try{ 
  14.             Thread.sleep(1000*4); 
  15.             Log.i(TAG,"HotThread1获取到的变量值: "+hot1); 
  16.             Log.i(TAG,"HotThread2获取到的变量值: "+hot2); 
  17.             Log.i(TAG,"MainThread获取到的变量值: "+hot3); 
  18.         }catch (Exception e){ 
  19.  
  20.         } 
  21.     } 
  22.  
  23.     private class HotThread1  extends Thread{ 
  24.         @Override 
  25.         public void run() { 
  26.             super.run(); 
  27.             mThreadLocal.set("北京热"); 
  28.             hot1=mThreadLocal.get(); 
  29.         } 
  30.     } 
  31.  
  32.     private class HotThread2  extends Thread{ 
  33.         @Override 
  34.         public void run() { 
  35.             super.run(); 
  36.             mThreadLocal.set("南京热"); 
  37.             hot2=mThreadLocal.get(); 
  38.         } 
  39.     }  

查看输出结果:

  1. HotThread1获取到的变量值: 北京热 
  2. HotThread2获取到的变量值: 南京热 
  3. MainThread获取到的变量值: 东京热  

在这段代码中使用ThreadLocal保存String类型的数据,并且在主线程和两个子线程中为ThreadLocal设置了不同的值,然后再将这些值分别取出。结合输出日志可以发现:在不同的线程中访问了同一个ThreadLocal对象,但是通过mThreadLocal.get()得到的值却是不一样的;也就是说:它们之间没有发生相互的影响而是保持了彼此的独立。明白了ThreadLocal的这个特性之后,我们再去理解Looper的工作机制就会容易得多了。

Looper、线程、消息队列的关系

Google官方建议开发人员使用Handler实现异步刷新UI,我们在平常的工作中也很好地采纳了这个提议:首先在主线程中建立Handler,然后在子线程中利用handler.sendMessage(message)发送消息至主线程,最终消息在handleMessage(Message msg) {}得到相应的处理。这个套路,大家都再熟悉不过了;现在换个角度,我们试试在子线程中建立Handler

  1. private class LooperThread  extends Thread{ 
  2.         @Override 
  3.         public void run() { 
  4.             super.run(); 
  5.             Handler handler=new Handler(); 
  6.             //doing something 
  7.         } 
  8.     }  

此处的代码很简单:LooperThread继承自Thread,并且在其run( )方法中新建一个Handler。

嗯哼,再运行一下,喔哦,报错了:

  1. Can’t create handler inside thread that has not called Looper.prepare(). 

咦,有点出师不利呢,刚开始试就出错了…….没事,生活不就是无尽的挫折和希望嘛,这点小事嘛也不算。既然是在调用Handler的构造方法时报的错那就从该构造方法的源码入手,一探究竟:

  1. public Handler() { 
  2.     this(nullfalse); 
  3.  
  4. public Handler(Callback callback) { 
  5.     this(callback, false); 
  6.  
  7. public Handler(Callback callback, boolean async) { 
  8.     if (FIND_POTENTIAL_LEAKS) { 
  9.         final Class<? extends Handler> klass = getClass(); 
  10.         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && 
  11.                 (klass.getModifiers() & Modifier.STATIC) == 0) { 
  12.             Log.w(TAG, "The following Handler class should be static or leaks might occur"); 
  13.         } 
  14.     } 
  15.  
  16.     mLooper = Looper.myLooper(); 
  17.     if (mLooper == null) { 
  18.         throw new RuntimeException 
  19.         ("Can't create handler inside thread that has not called Looper.prepare()"); 
  20.     } 
  21.     mQueue = mLooper.mQueue; 
  22.     mCallback = callback; 
  23.     mAsynchronous = async; 
  24.  

请注意第20行代码:

如果mLooper == null那么系统就会抛出刚才的错误:Can’t create handler inside thread that has not called Looper.prepare()。这句话的意思是:如果在线程内创建handler必须调用Looper.prepare()。既然这个提示已经提示了我们该怎么做,那就加上这一行代码:

  1. private class LooperThread  extends Thread{ 
  2.         @Override 
  3.         public void run() { 
  4.             super.run(); 
  5.             Looper.prepare(); 
  6.             Handler handler=new Handler(); 
  7.             System.out.println("add code : Looper.prepare()"); 
  8.             //doing something 
  9.         } 
  10.     }  

嘿嘿,果然不再报错了,运行一下:

 

既然Looper.prepare()解决了这个问题,那我们就去瞅瞅在该方法中做了哪些操作:

  1. /**Initialize the current thread as a looper. 
  2.  * This gives you a chance to create handlers that then reference 
  3.  * this looper, before actually starting the loop. Be sure to call 
  4.  * loop() after calling this method, and end it by calling quit(). 
  5.  */ 
  6. public static void prepare() { 
  7.     prepare(true); 
  8.  
  9. private static void prepare(boolean quitAllowed) { 
  10.     if (sThreadLocal.get() != null) { 
  11.         throw new RuntimeException("Only one Looper may be created per thread"); 
  12.     } 
  13.     sThreadLocal.set(new Looper(quitAllowed)); 
  14.  

从这段源码及其注释文档我们可以看出:

在prepare()中利用一个Looper来初始化当前线程或者说初始化一个带有Looper的线程。

请注意第14行代码,它是这段源码的核心,现对其详细分析:

  1. sThreadLocal.set(new Looper(quitAllowed)); 

在该行代码中一共执行了两个操作

(1) 构造Looper

  1. private Looper(boolean quitAllowed) { 
  2.     mQueue = new MessageQueue(quitAllowed); 
  3.     mThread = Thread.currentThread(); 
  4.  

在Looper的构造方法中初始化了一个消息队列MessageQueue和一个线程Thread。从这可看出:一个Looper对应着一个消息队列以及当前线程。

当收到消息Message后系统会将其存入消息队列中等候处理。至于Looper,它在Android的消息机制中担负着消息轮询的职责,它会不间断地查看MessageQueue中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。

(2) 将此Looper保存到sThreadLocal中。

此处的sThreadLocal是定义在Looper类中的一个ThreadLocal类型变量

  1. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

Looper是framework中的一个类,sThreadLocal是它的一个static final变量。当在某一个Thread中执行Looper.prepare()时系统就会将与该Thread所对应的Looper保存到sThreadLocal中。不同的线程对着不同的Looper,但它们均由系统保存在sThreadLocal中并且互不影响,相互独立;并且可以通过sThreadLocal.get()获取不同线程所对应的Looper.

在调用prepare()方法后需要调用loop()方法开始消息的轮询,并且在需要的时候调用quit()方法停止消息的轮询

假若再次执行Looper.prepare()系统发现sThreadLocal.get()的值不再为null于是抛出异常:

Only one Looper may be created per thread,一个线程只能创建一个Looper!

小结:

  1. 一个线程对应一个Looper
  2. 一个Looper对应一个消息队列
  3. 一个线程对应一个消息队列
  4. 线程,Looper,消息队列三者一一对应

所以,在一个子线程中使用Handler的方式应该是这样的:

  1. class LooperThread extends Thread {  
  2.     public Handler mHandler; 
  3.     public void run() {  
  4.         Looper.prepare();  
  5.         mHandler = new Handler() {  
  6.             public void handleMessage(Message msg) {  
  7.  
  8.             }  
  9.         }; 
  10.         Looper.loop();  
  11.       }  
  12.   }  

看到这个范例,有的人可能心里就犯嘀咕了:为什么我们平常在MainActivity中使用Handler时并没有调用Looper.prepare()也没有报错呢?

这是因为UI线程是主线程,系统会自动调用Looper.prepareMainLooper()方法创建主线程的Looper和消息队列MessageQueue

Message的发送和处理过程

在讨论完Looper、线程、消息队列这三者的关系之后我们再来瞅瞅Android消息机制中对于Message的发送和处理。

平常最常用的方式:

handler.sendMessage(message)——>发送消息

handleMessage(Message msg){}——>处理消息

先来分析消息的入队。

Handler可以通过post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法发送消息,除了postAtFrontOfQueue()之外这几个方法均会执行到sendMessageAtTime(Message msg, long uptimeMillis)方法,源码如下:

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 
  2.     MessageQueue queue = mQueue; 
  3.     if (queue == null) { 
  4.         RuntimeException e = new RuntimeException( 
  5.                 this + " sendMessageAtTime() called with no mQueue"); 
  6.         Log.w("Looper", e.getMessage(), e); 
  7.         return false
  8.     } 
  9.     return enqueueMessage(queue, msg, uptimeMillis); 
  10.  
  11. public final boolean sendMessageAtFrontOfQueue(Message msg) { 
  12.     MessageQueue queue = mQueue; 
  13.     if (queue == null) { 
  14.         RuntimeException e = new RuntimeException( 
  15.                 this + " sendMessageAtTime() called with no mQueue"); 
  16.         Log.w("Looper", e.getMessage(), e); 
  17.         return false
  18.     } 
  19.     return enqueueMessage(queue, msg, 0); 
  20.  
  21. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 
  22.     msg.target = this; 
  23.     if (mAsynchronous) { 
  24.         msg.setAsynchronous(true); 
  25.     } 
  26.     return queue.enqueueMessage(msg, uptimeMillis); 
  27.  

在这里可以看到sendMessageAtTime()内部又调用了enqueueMessage(),在该方法内的重要操作:

  • ***步:

给msg设置了target,请参见代码第25行此处的this就是当前Handler对象本身。在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。不难发现,哪个Handler发出了消息就由哪个Handler负责处理。

  • 第二步:

将消息放入消息队列中,请参见代码第29行在enqueueMessage(msg,uptimeMillis)中将消息Message存放进消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。直觉告诉我们此处的消息队列mQueue就是该线程所对应的消息队列。可是光有直觉是不够的甚至是不可靠的。我们再回过头瞅瞅Handler的构造方法,从源码中找到确切的依据 

  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.         } 
  8.     } 
  9.  
  10.     mLooper = Looper.myLooper(); 
  11.     if (mLooper == null) { 
  12.         throw new RuntimeException 
  13.         ("Can't create handler inside thread that has not called Looper.prepare()"); 
  14.     } 
  15.     mQueue = mLooper.mQueue; 
  16.     mCallback = callback; 
  17.     mAsynchronous = async; 
  18.  

(1) 获取Looper,请参见代码第10行

(2) 利用Looper的消息队列为mQueue赋值,请参见代码第15行

(3) 为mCallback赋值,,请参见代码第16行

(4) 为mAsynchronous赋值,,请参见代码第17行

嗯哼,看到了吧,这个mQueue就是从Looper中取出来的。在之前我们也详细地分析了Looper、线程、消息队列这三者的一一对应关系,所以此处的mQueue正是线程所对应的消息队列。

看完了消息的入队,再来分析消息的出队。

请看Looper中的loop()方法源码: 

  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.         final Printer logging = me.mLogging; 
  22.         if (logging != null) { 
  23.             logging.println(">>>>> Dispatching to " + msg.target + " " + 
  24.                     msg.callback + ": " + msg.what); 
  25.         } 
  26.  
  27.         final long traceTag = me.mTraceTag; 
  28.         if (traceTag != 0) { 
  29.             Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); 
  30.         } 
  31.         try { 
  32.             msg.target.dispatchMessage(msg); 
  33.         } finally { 
  34.             if (traceTag != 0) { 
  35.                 Trace.traceEnd(traceTag); 
  36.             } 
  37.         } 
  38.  
  39.         if (logging != null) { 
  40.             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 
  41.         } 
  42.  
  43.         // Make sure that during the course of dispatching the 
  44.         // identity of the thread wasn't corrupted. 
  45.         final long newIdent = Binder.clearCallingIdentity(); 
  46.         if (ident != newIdent) { 
  47.             Log.wtf(TAG, "Thread identity changed from 0x" 
  48.                     + Long.toHexString(ident) + " to 0x" 
  49.                     + Long.toHexString(newIdent) + " while dispatching to " 
  50.                     + msg.target.getClass().getName() + " " 
  51.                     + msg.callback + " what=" + msg.what); 
  52.         } 
  53.  
  54.         msg.recycleUnchecked(); 
  55.     } 
  56.  

我们发现关于消息的处理是在一个死循环中就行的,请参见代码第13-55行。也就是说在该段代码中Looper一直在轮询消息队列MessageQueue。假若消息队列中没有未处理的消息(即queue.next()==null)则其进入阻塞block状态,假若消息队列中有待处理消息(即queue.next()!=null)则利用msg.target.dispatchMessage(msg)将该消息派发至对应的Handler。

到了这,可能有的人会有一个疑问:系统怎么知道把消息发送给哪个Handler呢?

嘿嘿,还记不记得enqueueMessage()中系统给msg设置了target从而确定了其目标Handler么?嗯哼,所以只要通过msg.target.dispatchMessage(msg)就可以将消息派发至对应的Handler了。那在dispatchMessage()中又会对消息做哪些操作呢?我们继续跟进源码

  1. public void dispatchMessage(Message msg) { 
  2.     if (msg.callback != null) { 
  3.         handleCallback(msg); 
  4.     } else { 
  5.         if (mCallback != null) { 
  6.             if (mCallback.handleMessage(msg)) { 
  7.                 return
  8.             } 
  9.         } 
  10.         handleMessage(msg); 
  11.     } 
  12.  

哇哈,看到这,心情就舒畅多了,基本上回到了我们熟悉的地方;在此处对Message消息进行了处理,我们来瞅瞅主要的步骤

  • ***步:

处理Message的回调callback,请参见代码第3行

比如调用handler.post(Runnable runnable)时,该runnable就会被系统封装为Message的callback。

关于这点在源码中也有非常直观的体现:

  1. private static Message getPostMessage(Runnable r) { 
  2.    Message m = Message.obtain(); 
  3.    m.callback = r; 
  4.    return m; 
  5.  
  • 第二步:

处理Handler的回调callback,请参见代码第6行

比如执行Handler handler=Handler(Callback callback)时就会将callback赋值给mCallback,关于这点已经在介绍Handler构造方法时分析过了,不再赘述。

第三步:

调用handleMessage()处理消息Message,请参见代码第10行

handleMessage()的源码如下:

  1. public void handleMessage(Message msg) { 
  2.  
  3.  

嗯哼,它是一个空的方法。所以Handler的子类需要覆写该方法,并在其中处理接收到的消息。

梳理Handler工作机制

至此,关于Handler的异步机制及其实现原理已经分析完了。在此,对其作一个全面的梳理和总结。

Android异步消息机制中主要涉及到:Thread、Handler、MessageQueue、Looper,在整个机制中它们扮演着不同的角色也承担着各自的不同责任。

  • Thread负责业务逻辑的实施。

线程中的操作是由各自的业务逻辑所决定的,视具体情况进行。

  • Handler负责发送消息和处理消息。

通常的做法是在主线程中建立Handler并利用它在子线程中向主线程发送消息,在主线程接收到消息后会对其进行处理

  • MessageQueue负责保存消息。

Handler发出的消息均会被保存到消息队列MessageQueue中,系统会根据Message距离触发时间的长短决定该消息在队列中位置。在队列中的消息会依次出队得到相应的处理。

  • Looper负责轮询消息队列。

Looper使用其loop()方法一直轮询消息队列,并在消息出队时将其派发至对应的Handler.

为了更好地理解这几者的相互关系及其作用,请参见如下示图 

 

 

 

使用Handler的错误姿势及其潜在风险

关于Handler的具体用法,尤其是那些常规的使用方式在此就不再一一列举了。

责任编辑:庞桂玉 来源: Android开发中文站
相关推荐

2012-02-28 14:43:43

2009-12-23 16:13:00

WPF Attache

2010-01-26 13:47:57

Android电话功能

2010-07-21 09:38:15

PHP缓存技术

2010-11-22 14:18:32

MySQL锁机制

2009-11-20 17:17:08

Oracle函数索引

2021-05-17 05:36:02

CSS 文字动画技巧

2015-09-02 08:57:56

JavaHashMap工作原理

2011-02-25 09:23:00

Java类加载器

2009-08-27 11:27:58

foreach语句C# foreach语

2010-03-05 13:44:00

Python序列

2010-03-31 14:58:03

云计算

2023-01-12 17:18:06

数据库多云

2009-10-16 09:17:39

屏蔽布线系统

2009-11-12 13:56:54

2024-01-26 06:42:05

Redis数据结构

2009-12-07 13:55:58

PHP array_m

2009-12-07 16:07:03

PHP类的继承

2009-12-14 14:40:10

Ruby全局域变量

2013-07-11 09:45:48

扁平化扁平化设计
点赞
收藏

51CTO技术栈公众号