hanlder机制 总结篇

一.背景

  总结了很多大神对handler机制(感谢各位大神),很早以前就写了,最近回想居然想不起来,于是在硬盘的某个阴暗角落把他找出来了,方便以后查阅,写成博客,方便自己,也方便大家大笑

二.概述

        Android使用消息机制实现线程间的通信,线程通过Looper建立自己的消息循环,MessageQueue是FIFO的消息队列,Looper负责从MessageQueue中取出消息,并且分发到消息指定目标Handler对象。Handler对象绑定到线程的局部变量Looper,封装了发送消息和处理消息的接口

三.例子

 

      这个例子实现点击按钮之后从主线程发送消息"hello"到另外一个名为” CustomThread”的线程。

 
  1.  class CustomThread extends Thread {

  2.         @Override

  3.         public void run() {

//建立消息循环的步骤

//1.初始化Looper(下面有解释具体过程)

     Looper.prepare();//1、初始化Looper

//2.绑定handler到CustonThread实例的Looper(下面有解释具体过程)     

 

            mHandler = new Handler(){

//3.定义处理消息的方法   

 

 

 
  1. public void handleMessage (Message msg) {//3、定义处理消息的方法

  2. switch(msg.what) {

  3. case MSG_HELLO:

  4. Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);

  5. }

  6. }

  7. };

//4.启动消息循环(下面有解释具体过程)

 

 

 
  1. Looper.loop();

  2. }

  3. }

 

我们看到,为一个线程建立消息循环有四个步骤:

1、 初始化Looper

 

//Looper.java

 

 
  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. * {@link #loop()} after calling this method, and end it by calling

  5. * {@link #quit()}.

  6. */

  7. public static void prepare() {

  8. prepare(true);

  9. }

  10.  
  11. private static void prepare(boolean quitAllowed) {

  12. if (sThreadLocal.get() != null) {

  13. throw new RuntimeException("Only one Looper may be created per thread");

  14. }

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

  16. }

2、 绑定handler到CustomThread实例的Looper对象

一个线程在调用Looper的静态方法prepare()时,这个线程会新建一个Looper对象,并放入到线程的局部变量中,而且这个变量是不和其他线程共享的(关于ThreadLocal的介绍)。

 

 

下面我们看看Looper()这个构造函数:

 

 
  1. final MessageQueue mQueue;

  2. private Looper(boolean quitAllowed) {

  3. mQueue = new MessageQueue(quitAllowed);

  4. mRun = true;

  5. mThread = Thread.currentThread();

  6. }

 

可以看到在Looper的构造函数中,创建了一个消息队列对象mQueue,此时,调用Looper. prepare()的线程就建立起一个消息循环的对象(此时还没开始进行消息循环,需要调用loop才会开始循环)。

//handler.java

 
  1. final MessageQueue mQueue;

  2. final Looper mLooper;

  3. final Callback mCallback;

 
  1. public Handler() {

  2. this(null, false);

  3. }

然后就会调用这个

 
  1. public Handler(Callback callback, boolean async) {

  2. if (FIND_POTENTIAL_LEAKS) {

  3. final Class<? extends Handler> klass = getClass();<pre name="code" class="java">

if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }

 
 
  1. public static @Nullable Looper myLooper() {

  2. return sThreadLocal.get();

  3. }

 

Handler通过mLooper = Looper.myLooper();绑定到线程的局部变量mLooper上去,同时Handler通过mQueue =mLooper.mQueue;获得线程的消息队列。此时,Handler就绑定到创建此Handler对象的线程的消息队列上了

3、 定义处理消息的方法

 

子类需要覆盖这个方法,实现接受到消息后的处理方法。

4、 启动消息循环

所有准备工作都准备好了,是时候启动消息循环了!Looper的静态方法loop()实现了消息循环。

 
  1. /**

  2. * Run the message queue in this thread. Be sure to call

  3. * {@link #quit()} to end the loop.

  4. */

  5. public static void loop() {

  6. final Looper me = myLooper();

  7. if (me == null) {

  8. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

  9. }

  10. final MessageQueue queue = me.mQueue;

  11.  
  12. // Make sure the identity of this thread is that of the local process,

  13. // and keep track of what that identity token actually is.

  14. Binder.clearCallingIdentity();

  15. final long ident = Binder.clearCallingIdentity();

  16.  
  17. for (;;) {

  18. Message msg = queue.next(); // might block

  19. if (msg == null) {

  20. // No message indicates that the message queue is quitting.

  21. return;

  22. }

  23.  
  24. // This must be in a local variable, in case a UI event sets the logger

  25. Printer logging = me.mLogging;

  26. if (logging != null) {

  27. logging.println(">>>>> Dispatching to " + msg.target + " " +

  28. msg.callback + ": " + msg.what);

  29. }

  30.  
  31. msg.target.dispatchMessage(msg);

  32.  
  33. if (logging != null) {

  34. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

  35. }

  36.  
  37. // Make sure that during the course of dispatching the

  38. // identity of the thread wasn't corrupted.

  39. final long newIdent = Binder.clearCallingIdentity();

  40. if (ident != newIdent) {

  41. Log.wtf(TAG, "Thread identity changed from 0x"

  42. + Long.toHexString(ident) + " to 0x"

  43. + Long.toHexString(newIdent) + " while dispatching to "

  44. + msg.target.getClass().getName() + " "

  45. + msg.callback + " what=" + msg.what);

  46. }

  47.  
  48. msg.recycleUnchecked();

  49. }

  50. }

 

while(true)体现了消息循环中的“循环“,Looper会在循环体中调用queue.next()获取消息队列中需要处理的下一条消息。当msg != null且msg.target != null时,调用msg.target.dispatchMessage(msg);分发消息,当分发完成后,调用msg.recycle();回收消息

msg.target是一个handler对象,表示需要处理这个消息的handler对象。

//handler.java

 
  1. public final boolean sendMessage(Message msg)

  2. {

  3. return sendMessageDelayed(msg, 0);

  4. }

 

 
  1. public final boolean sendMessageDelayed(Message msg, long delayMillis)

  2. {

  3. if (delayMillis < 0) {

  4. delayMillis = 0;

  5. }

  6. return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

  7. }

 

 
  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. }

 

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

msg.target = this;

 
  1. if (mAsynchronous) {

  2. msg.setAsynchronous(true);

  3. }

  4. return queue.enqueueMessage(msg, uptimeMillis);

  5. }

然后就把当前调用的handler传给msg.tartget

下面是分发消息

 

 
  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. }

 

 

可见,当msg.callback== null 并且mCallback == null时(msg.callback 就是Handler的post方法传递的Runable,mCallback 是 Handler handler = new Handler(callback)),这个例子是由handleMessage(msg);处理消息,上面我们说到子类覆盖这个方法可以实现消息的具体处理过程。

总结:从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。

method1:

 

 
  1. private Handler handler = new Handler() {

  2. @Override

  3. public void handleMessage(Message msg) {

  4. super.handleMessage(msg);

  5. }

  6. };

 

method2:

 

 

 
  1. Handler handler = new Handler(new Handler.Callback() {

  2. @Override

  3. public boolean handleMessage(Message msg) {

  4. return false;

  5. }

  6. });

method3:

 
  1. new Handler().postDelayed(new Runnable() {

  2. @Override

  3. public void run() {

  4. }

  5. }, 200);


 

 

 

四.总结

 

职责

 

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。

同时通过sendmessage来指定msg.target(把当前handler设置为msg.target)

MessageQueue:消息队列(采用单链表的数据结构来存储信息),用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。

Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

关系

Handler,Looper和MessageQueue就是简单的三角关系。Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue。而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue。

这样说来,多个Handler都可以共享同一Looper和MessageQueue了。当然,这些Handler也就运行在同一个线程里。(looper是绑定当前线程)(但是在handler.sendmessage中已经把msg.target,设定为当前handler对象,然后放在消息队列中,然后looper.loop()中轮询消息队列,获得消息,可以调用msg.target来handlemessage)

 

 

 如何更新UI

剩下的部分,我们将讨论一下Handler所处的线程及更新UI的方式。

在主线程(UI线程)里,如果创建Handler时不传入Looper对象,那么将直接使用主线程(UI线程)的Looper对象(系统已经帮我们创建了);在其它线程里,如果创建Handler时不传入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. // process incoming messages here

  8. }

  9. };

  10. Looper.loop();

  11. }

  12. }

 

      在创建Handler之前,为该线程准备好一个Looper(Looper.prepare),然后让这个Looper跑起来(Looper.loop),抽取Message,这样,Handler才能正常工作。

因此,Handler处理消息总是在创建Handler的线程里运行。而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的

注意:

如果手动创建Looper,那么所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态,而且如果退出Looper之后,这个线程会立即终止,因此建议不需要的时候终止Looper.

 

  (1)quit  直接退出

  (2)quitsafely   只是设定了一个退出标记,然后把消息队列中的已有消息处理完毕后才能安全退出

 

 还有Looper.loop是一个死循环,唯一跳出循环的方式就是MessageQueue的next方法返回null(只有当Looper的quit方法被调用时,才会让next方法返回null,也就是说Looper必须退出),否则loop方法就会无限循环下去,而且当没有消息的时候,next方法就会一直阻塞在那里,也就导致loop方法一直阻塞在那里

 

如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程:

·      Activity.runOnUiThread(Runnable)

·      View.post(Runnable) (把消息放到UI线程的消息循环中)

·      View.postDelayed(Runnable, long)

·      Handler

其中,重点说一下的是View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

 几点小结

·      Handler的处理过程运行在创建Handler的线程里

·      一个Looper对应一个MessageQueue

·      一个线程对应一个Looper

·      一个Looper可以对应多个Handler

·      不确定当前线程时,更新UI时尽量调用post方法

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值