Android Handler消息机制深入浅出

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38408493

作为Android开发人员,Handler这个类应该是再熟悉不过了,因为几乎任何App的开发,都会使用到Handler这个类,有些同学可能就要说了,我完全可以使用AsyncTask代替它,这个确实是可以的,但是其实AsyncTask也是通过Handler实现的,具体的大家可以去看看源码就行了,Handler的主要功能就是实现子线程和主线程的通信,例如在子线程中执行一些耗时操作,操作完成之后通知主线程跟新UI(因为Android是不允许在子线程中跟新UI的)。

下面就使用一个简单的例子开始这篇文章吧

[java] view plain copy
 
 print?
  1. public class MainActivity extends Activity  
  2. {  
  3.   public static final int MSG_DOWNLOAD_FINISH=0X001;  
  4.   //创建一个Handler的匿名内部类  
  5.   private Handler handler=new Handler()  
  6.   {  
  7.   
  8.     @Override  
  9.     public void handleMessage(Message msg)  
  10.     {  
  11.       switch(msg.what)  
  12.       {  
  13.         case MSG_DOWNLOAD_FINISH:  
  14.           Log.v("yzy", "handler所在的线程id是-->"+Thread.currentThread().getName());  
  15.           break;  
  16.       }  
  17.     }  
  18.       
  19.   };  
  20.   
  21.   @Override  
  22.   protected void onCreate(Bundle savedInstanceState)  
  23.   {  
  24.     super.onCreate(savedInstanceState);  
  25.     setContentView(R.layout.activity_main);  
  26.   }  
  27.     
  28.   //启动一个下载线程  
  29.   public void download(View view)  
  30.   {  
  31.     new Thread()  
  32.     {  
  33.       public void run() {  
  34.         try  
  35.         {  
  36.           Log.v("yzy", "下载子线程的Name是--->"+Thread.currentThread().getName());  
  37.           //在子线程运行,模拟一个下载任务  
  38.           Thread.sleep(2000);  
  39.           //下载完成后,发送下载完成消息  
  40.           handler.sendEmptyMessage(MSG_DOWNLOAD_FINISH);  
  41.         } catch (InterruptedException e)  
  42.         {  
  43.           e.printStackTrace();  
  44.         }  
  45.       };  
  46.     }.start();  
  47.   }  


运行结果:
08-03 16:31:46.418: V/yzy(30486): 下载子线程的Name是--->Thread-22588
08-03 16:31:48.421: V/yzy(30486): handler所在的线程Name是-->main

 

 

上面例子就是模拟了一个下载任务,当下载完成后,通过Handler对象发送一个消息,最后被handlerMessage方法接收并处理,通过运行结果可以发现,download方法是在子线程中完成的,而handlerMessage是在UI线程中被调用的。到了这里一个简单的Handler使用案例就结束了,如果你已经明白了上面的代码,那么说明你已经明白了Handler的最基本使用。
下面再来一个简单的例子:

 

[java] view plain copy
 
 print?
  1. public void postRun(View view)  
  2.   {  
  3.     new Thread(){  
  4.       public void run() {  
  5.         try  
  6.         {  
  7.           Log.v("yzy", " 下载子线程的Name是--->"+Thread.currentThread().getName());  
  8.           Thread.sleep(2000);  
  9.           handler.post(new Runnable()  
  10.           {  
  11.               
  12.             @Override  
  13.             public void run()  
  14.             {  
  15.               Log.v("yzy", "handler post run -->"+Thread.currentThread().getName());  
  16.             }  
  17.           });  
  18.         } catch (InterruptedException e)  
  19.         {  
  20.           e.printStackTrace();  
  21.         }  
  22.          
  23.       };  
  24.         
  25.     }.start();  
  26.      
  27.   }  


运行结果:
08-03 16:31:52.528: V/yzy(30486):  下载子线程的Name是--->Thread-22589
08-03 16:31:54.531: V/yzy(30486): handler post run -->main

 

在Handler中除了可以发送Message对象还可以发送Runnable对象,从运行结果来看发送的Runnable也是在main线程中执行的
那么是不是通过handler发出的Message和Runnable都是在UI线程中执行的呢,这个可不一定,现在我在onCreate方法中创建一个handler2

 

[java] view plain copy
 
 print?
  1. HandlerThread thread=new HandlerThread("yzy");  
  2.     thread.start();  
  3.       
  4.     handler2=new Handler(thread.getLooper())  
  5.     {  
  6.       public void handleMessage(Message msg) {  
  7.         switch(msg.what)  
  8.         {  
  9.           case MSG_DOWNLOAD_FINISH:  
  10.             Log.v("yzy", "handler所在的线程Name是-->"+Thread.currentThread().getName());  
  11.             break;  
  12.         }  
  13.           
  14.       };  
  15.     };  


然后将上面代码中使用handler的地方换为handler2,然后运行代码:代码中创建了一个名称为"yzy"的线程,然后再yzy线程中初始化了handler2
结果如下:
08-03 17:07:10.571: V/yzy(8224): 下载子线程的Name是--->Thread-23005
08-03 17:07:12.574: V/yzy(8224): handler所在的线程Name是-->yzy

我们发现通过handler2发出的Message和Runnable都是在yzy线程中执行的。
如果对于上面的代码和运行结果你都知道原因,那么说明你已经对Handler的使用非常熟悉了,如果不清楚上面的运行结果,那么请往下继续吧

开始分析原因之前我想提出以下几个问题:
1、Handler发出的Message和Runnable是如何传递到UI线程的?
2、在什么情况下通过Handler发出的Message和Runnable不会传递到UI线程?
3、为什么我是在HandlerThread初始化一个Handler?

下面我们就逐一解决上述问题吧,在解决问题之前我们需要分析一个Hanlder这个类的源码。


首先看看Handler的构造函数,它有好几个构造函数,我们一个个的看吧

 

 

[java] view plain copy
 
 print?
  1. //无参构造函数  
  2.   public Handler() {  
  3.         //检查Handler是否是static的,如果不是的,那么有可能导致内存泄露,具体可以百度  
  4.         if (FIND_POTENTIAL_LEAKS) {  
  5.             final Class<? extends Handler> klass = getClass();  
  6.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  7.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
  8.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  9.                     klass.getCanonicalName());  
  10.             }  
  11.         }  
  12.         //很重要的一个属性,暂时就将Looper理解成一个消息队列的管理者吧,用来从消息队列中取消息的,稍后会分析这个类  
  13.         mLooper = Looper.myLooper();  
  14.         if (mLooper == null) {  
  15.             //这个异常非常常见哦,Handler一定要在有Looper的线程上执行,这个也就是为什么我在HandlerThread中初始化Handler  
  16.             //而没有在Thread里面初始化,如果在Thread里面初始化需要先调用Looper.prepare方法  
  17.             throw new RuntimeException(  
  18.                 "Can't create handler inside thread that has not called Looper.prepare()");  
  19.         }  
  20.         //将mLooper里面的消息队列复制到自身的mQueue,这也就意味着Handler和Looper是公用一个消息队列  
  21.         mQueue = mLooper.mQueue;  
  22.         //回调函数默认是Null  
  23.         mCallback = null;  
  24.     }  
  25.   
  26.     //这个和上面一样的,只不过传入了一个回调函数  
  27.     public Handler(Callback callback) {  
  28.         if (FIND_POTENTIAL_LEAKS) {  
  29.             final Class<? extends Handler> klass = getClass();  
  30.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  31.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
  32.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  33.                     klass.getCanonicalName());  
  34.             }  
  35.         }  
  36.   
  37.         mLooper = Looper.myLooper();  
  38.         if (mLooper == null) {  
  39.             throw new RuntimeException(  
  40.                 "Can't create handler inside thread that has not called Looper.prepare()");  
  41.         }  
  42.         mQueue = mLooper.mQueue;  
  43.         mCallback = callback;  
  44.     }  
  45.   
  46.     /** 
  47.      * 传入一个Looper,并和Looper公用一个消息队列 
  48.      */  
  49.     public Handler(Looper looper) {  
  50.         mLooper = looper;  
  51.         mQueue = looper.mQueue;  
  52.         mCallback = null;  
  53.     }  
  54.   
  55.     /** 
  56.      * 和上面一个差不多,就不在赘述 
  57.      */  
  58.     public Handler(Looper looper, Callback callback) {  
  59.         mLooper = looper;  
  60.         mQueue = looper.mQueue;  
  61.         mCallback = callback;  
  62.     }  


看了Handler的构造函数,发现Hanlder里面有一个消息队列,这个消息队列是从Looper里面传入的,并且Handler需要在有Looper的线程中调用。既然它和Looper关系这么紧密,我们看看Looper到底是什么东西吧,打开Looper的源码,顶部看到这样的一个例子:

 

 

[java] view plain copy
 
 print?
  1. *  class LooperThread extends Thread {  
  2.   *      public Handler mHandler;  
  3.   *        
  4.   *      public void run() {  
  5.   *          Looper.prepare();  
  6.   *            
  7.   *          mHandler = new Handler() {  
  8.   *              public void handleMessage(Message msg) {  
  9.   *                  // process incoming messages here  
  10.   *              }  
  11.   *          };  
  12.   *            
  13.   *          Looper.loop();  
  14.   *      }  
  15.   *   


这个就是为了说明Handler只能在有Looper的线程中创建,UI线程是有Looper的,对于没有Looper的线程,需要调用Loope.prepare,Looper.loop等函数,如上例。
那我们正式进入Looper的源码看看吧

 

 

[java] view plain copy
 
 print?
  1. //Looper的构造函数,主要是对消息队列等属性初始化  
  2.      private Looper() {  
  3.         mQueue = new MessageQueue();  
  4.         mRun = true;  
  5.         //记录所在线程  
  6.         mThread = Thread.currentThread();  
  7.     }  
  8.       
  9.    //在上面的例子中,看到当在一个没有Looper的线程中创建Handler,就需要执行这个函数,  
  10.    //这个函数主要是new 一个Looper,燃火放入ThreadLocal中保存,做到一个线程就创建一次。第二次调用这个方法会抛出异常的  
  11.     public static final void prepare() {  
  12.         if (sThreadLocal.get() != null) {  
  13.             throw new RuntimeException("Only one Looper may be created per thread");  
  14.         }  
  15.         sThreadLocal.set(new Looper());  
  16.     }  
  17.       
  18.     //prepare是创建并保存,这个方法就是取出Looper  
  19.     public static final Looper myLooper() {  
  20.         return (Looper)sThreadLocal.get();  
  21.     }  
  22.       
  23.     //这个方法是给系统调用的,UI线程通过调用这个线程,从而保证UI线程里有一个Looper  
  24.     //需要注意:如果一个线程是UI线程,那么myLooper和getMainLooper是同一个Looper,通过这个代码很好理解  
  25.     public static final void prepareMainLooper() {  
  26.         prepare();  
  27.         setMainLooper(myLooper());  
  28.         if (Process.supportsProcesses()) {  
  29.             myLooper().mQueue.mQuitAllowed = false;  
  30.         }  
  31.     }  
  32.   
  33.   
  34.       
  35.     //获得UI线程的Looper,通常我想Hanlder的handleMessage在UI线程执行时通常会new  Handler(getMainLooper());  
  36.     public synchronized static final Looper getMainLooper() {  
  37.         return mMainLooper;  
  38.     }  
  39.   
  40.     /** 
  41.      *  上面的例子是执行了这个方法的,这个方法是一个死循环,一直从消息队列中读取消息,并分发出去 
  42.      */  
  43.     public static final void loop() {  
  44.         //得到本线程的Looper  
  45.         Looper me = myLooper();  
  46.         //拿到消息队列  
  47.         MessageQueue queue = me.mQueue;  
  48.         while (true) {  
  49.             //从消息队列中拿一个消息  
  50.             Message msg = queue.next(); // might block  
  51.             //取到了一个消息  
  52.             if (msg != null) {  
  53.                 //这个一般不等于Null,通常就是发出这个Message的Handler  
  54.                 if (msg.target == null) {  
  55.                     // No target is a magic identifier for the quit message.  
  56.                     return;  
  57.                 }  
  58.                 if (me.mLogging!= null) me.mLogging.println(  
  59.                         ">>>>> Dispatching to " + msg.target + " "  
  60.                         + msg.callback + ": " + msg.what  
  61.                         );  
  62.                 //调用Handler的dispatchMessage方法  
  63.                 msg.target.dispatchMessage(msg);  
  64.                 if (me.mLogging!= null) me.mLogging.println(  
  65.                         "<<<<< Finished to    " + msg.target + " "  
  66.                         + msg.callback);  
  67.                 //将消息放入消息池  
  68.                 msg.recycle();  
  69.             }  
  70.         }  
  71.     }  
  72.       
  73.     //返回Looper的消息队列  
  74.     public static final MessageQueue myQueue() {  
  75.         return myLooper().mQueue;  
  76.     }  


 看了Looper的源码之后总结一下:
  在一个线程的run方法里面先调用prepare方法,主要保证了该线程里面有了一个Looper对象,如果在这个线程里面创建一个Hanlder,那么看看上面Handler的空的构造方法,Handler里面的Looper就是线程里面保存的Looper,从而可以创建的Handler和线程公用一个Looper,也就是公用一个消息队列。说的更具体一点就是Hanlder和Looper所在的线程公用一个消息队列。最后调用loop方法,这个线程不断从消息队列取消息,然后调用Hanlder的dispatchMessage方法。
  
最后看看Handler的其他几个比较重要的方法吧

 

 

[java] view plain copy
 
 print?
  1. //在Handler中发送一个消息到消息队列,类似的方法很多,我只选了这一个  
  2.    public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  3.     {  
  4.         boolean sent = false;  
  5.         MessageQueue queue = mQueue;  
  6.         if (queue != null) {  
  7.             //将target设置成了Handler  
  8.             msg.target = this;  
  9.             sent = queue.enqueueMessage(msg, uptimeMillis);  
  10.         }  
  11.         else {  
  12.             RuntimeException e = new RuntimeException(  
  13.                 this + " sendMessageAtTime() called with no mQueue");  
  14.             Log.w("Looper", e.getMessage(), e);  
  15.         }  
  16.         return sent;  
  17.     }  
  18.     //这个方法是在loop方法中被调用的  
  19.      public void dispatchMessage(Message msg) {  
  20.         //首先检查msg中callback(是一个Runnable对象)是否为Null,如果不为null,则直接调用callback中的run方法  
  21.         if (msg.callback != null) {  
  22.             handleCallback(msg);  
  23.         } else {  
  24.             //检查Handler中的callback是否为空,如果不为空,则直接调用callback中的handleMessage,如果返回TRUE,则直接返回不在调用Handler中的handleMessage  
  25.             if (mCallback != null) {  
  26.                 if (mCallback.handleMessage(msg)) {  
  27.                     return;  
  28.                 }  
  29.             }  
  30.             //最后调用Handler中的handlerMessage  
  31.             handleMessage(msg);  
  32.         }  
  33.     }  


在前面的例子中,我们曾经使用过Handler中的post方法,我们看看它是怎么实现的

 

 

[java] view plain copy
 
 print?
  1. public final boolean post(Runnable r)  
  2.    {  
  3.       return  sendMessageDelayed(getPostMessage(r), 0);  
  4.    }  
  5.   
  6.  private final Message getPostMessage(Runnable r) {  
  7.        Message m = Message.obtain();  
  8.        m.callback = r;  
  9.        return m;  
  10.    }  
  11.   
  12.   
  13. public final boolean sendMessageDelayed(Message msg, long delayMillis)  
  14.    {  
  15.        if (delayMillis < 0) {  
  16.            delayMillis = 0;  
  17.        }  
  18.        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
  19.    }  


我想如果前面明白了的,这个就不用我说了的吧。
其实在Activity中有一个方法runOnUiThread,就是使用这个原理实现的,如下:

 

 

[java] view plain copy
 
 print?
  1. //让Runnable在UI线程执行  
  2.         public final void runOnUiThread(Runnable action) {  
  3.         //如果当前线程不是UI线程,那么通过Handler转发到UI线程  
  4.         if (Thread.currentThread() != mUiThread) {  
  5.             mHandler.post(action);  
  6.         } else {  
  7.             action.run();  
  8.         }  
  9.     }  


View里面也有类似的方法:

 

 

[java] view plain copy
 
 print?
  1. public boolean post(Runnable action) {  
  2.         Handler handler;  
  3.         if (mAttachInfo != null) {  
  4.             handler = mAttachInfo.mHandler;  
  5.         } else {  
  6.             // Assume that post will succeed later  
  7.             ViewRoot.getRunQueue().post(action);  
  8.             return true;  
  9.         }  
  10.   
  11.         return handler.post(action);  
  12.     }  


原理是上面是一样的。
  根据上面的学习,可以得出结论:
  1、如果Handler是在UI线程创建的,那么Handler和UI线程共享消息队列,所以通过Hanlder发出的消息都是在UI线程中执行的
  2、如果要让Handler发出的消息在子线程中执行,很简单,在创建Handler的时候传入子线程的Looper(通过Looper.prepare创建)
  到这里相信在前面我提及的三个问题都已经有答案了吧,如果有没有明白的,欢迎留言。。。。

posted @ 2017-03-12 12:00  天涯海角路  阅读(107)  评论(0)    收藏  举报