Android Handler机制

来源:互联网 发布:商场导视软件 编辑:程序博客网 时间:2024/05/18 00:33

好长时间都没有发布新的博客了,今天来写一篇关于Android中使用频率极高,但是也经常让大家感觉摸不着头脑的handler.

下面呢 主要从以下几个方面来讲.

1.     为什么禁止在非UI线程更新UI

2.     Handler概述

3.     Handler的几种使用方式

4.     Handler的原理,以及handler与message queue, looper之间的关系

5.     HandlerThread是什么?

6.     异步更新UI 的几种方式

7.     非UI线程真的不能更新UI吗?

为什么禁止在非UI线程更新UI?

         Handler翻译成中文就是”处理者”的意思,实际上也确实如此.当我们在处理Android中多线程问题的时候,有需要处理的事情就可以去找Handler了.那么为什么会产生多线程问题呢?

        Google在设计的framework的时候禁止开发者在非UI线程去更新界面UI.那么Google为什么要这么做呢?查看源码之后可以发现更新UI 的相关方法为了保证效率都是没有加上同步锁的.而更新UI绘制图形会调用底层硬件相关方法,如果不加以限制可能会产生意想不到的后果.所以为了兼顾效率与安全,就设计成只能在主线程更新UI.另外不只是Google,iOS系统也是禁止在主线程更新UI的,足以证明这样的设计是一种通用做法.

Handler概述

       但是如果只是简简单单设计成只能在主线程更新UI的话,那么对开发者的素质要求势必会提高,java中的多线程问题想必大家学习的时候也是一大难点吧.开发者为了保证线程间通信的代码不会乱掉势必要自己设计一套线程间通信的框架.这样的要求未免太高,不利于Android平台的初期拓展.于是Google就在framework中封装好了这么一套线程间通信的框架.这套框架在Google的代码中也是应用相当广泛,我们常用的Activity的生命周期回调,事件传递等framework层中的代码中,Handler机制也是随处可见.下面就让我们来学习一下Handler的基本使用吧.

Handler的基本使用

   Handler的使用主要和下面几个方法相关:

      sendMessage()

      sendMessageDelayed()

      post()

      postDelayed()

   带delayed后缀的方法都是在原方法上延迟几秒的方法.下面只演示sendMessage()方法和post()方法.

废话不多说上代码:

   首先是sendMessage()方法

[java] view plaincopy
  1. public class MainActivity extends ActionBarActivity {  
  2.     private Handler handler = new Handler(){  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             textView.setText("文字被改变啦");  
  6.         }  
  7.     };  
  8.     private TextView textView;  
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.         textView = (TextView) findViewById(R.id.test);  
  15.         new Thread(new Runnable() {  
  16.             @Override  
  17.             public void run() {  
  18.                                try {  
  19.                     Thread.sleep(2000);  
  20.                     handler.sendEmptyMessage(1);  
  21.                 } catch (InterruptedException e) {  
  22.                     e.printStackTrace();  
  23.                 }  
  24.             }  
  25.             }  
  26.         }).start();  
  27.     }  
  28. }  

运行结果:

     这段代码里我们使用handler在子线程向主线程里发送了一条空消息.这样handlemessage方法就可以在主线程执行了

     Message也可以指定并传递数据,message共有几个主要的属性.what,object,arg1,arg2.

使用示例:

[java] view plaincopy
  1. public class MainActivity extends ActionBarActivity {  
  2.     private Handler handler = new Handler(){  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             String string = "";  
  6.             for (String temp : (ArrayList<String>)msg.obj)  
  7.                 string += temp;  
  8.             textView.setText("what:" + msg.what + "arg1" + msg.arg1 + "arg2" + msg.arg2 + "objString:"+string);  
  9.         }  
  10.     };  
  11.     private TextView textView;  
  12.   
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.         textView = (TextView) findViewById(R.id.test);  
  18.         new Thread(new Runnable() {  
  19.             @Override  
  20.             public void run() {  
  21.                 try {  
  22.                     Thread.sleep(2000);  
  23.                     Message message = Message.obtain();  
  24.                     message.what = 1;  
  25.                     message.arg1 = 100;  
  26.                     message.arg2 = 200;  
  27.                     ArrayList<String> strings = new ArrayList<String>();  
  28.                     strings.add("这是第一条");  
  29.                     strings.add("这个第二条");  
  30.                     message.obj = strings;  
  31.                     handler.sendMessage(message);  
  32.                 } catch (InterruptedException e) {  
  33.                     e.printStackTrace();  
  34.                 }  
  35.             }  
  36.         }).start();  
  37.     }  
  38. }  

    可以注意到上面这段代码使用Message.obtain()方法获取message对象.这样就可以复用系统提供给我们的message对象了.当然,你不在乎内存的话也可以直接new,但是不推荐那么写.

运行结果:




   下面是post()方法的使用.

代码示例

[java] view plaincopy
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.     textView = (TextView) findViewById(R.id.test);  
  6.     new Thread(new Runnable() {  
  7.         @Override  
  8.         public void run() {  
  9.             try {  
  10.                 Thread.sleep(2000);  
  11.                 handler.post(new Runnable() {  
  12.                     @Override  
  13.                     public void run() {  
  14.                         textView.setText("post被执行了");  
  15.                     }  
  16.                 });  
  17.             } catch (InterruptedException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.         }  
  21.     }).start();  
  22. }  

执行结果:

    这样的话post里面的代码就可以运行在主线程了.需要注意的是如果使用post方法执行,那么handlemessage方法就不会再执行了.

    此外message处理方式还允许使用者指定一个callback(),在handlemessage之前执行,如果该callback()返回true,则该message会被拦截.返回false就可以继续向handler传递.

具体示例如下:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.     private Handler handler = new Handler(new Handler.Callback() {  
  3.         @Override  
  4.         public boolean handleMessage(Message msg) {  
  5.             textView.setText("消息被callback拦截");  
  6.             return true;  
  7.         }  
  8.     }){  
  9.         @Override  
  10.         public void handleMessage(Message msg) {  
  11.             textView.setText("handler接收到消息,hanlemessage执行");  
  12.         }  
  13.     };  
  14.     private TextView textView;  
  15.   
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.         textView = (TextView) findViewById(R.id.test);  
  21.         new Thread(new Runnable() {  
  22.             @Override  
  23.             public void run() {  
  24.                 try {  
  25.                     Thread.sleep(2000);  
  26.                     handler.sendEmptyMessage(1);  
  27.                 } catch (InterruptedException e) {  
  28.                     e.printStackTrace();  
  29.                 }  
  30.             }  
  31.         }).start();  
  32.     }  
  33. }  

执行结果:

  看到这里有的哥们可能就迷糊了,完全可以直接在handlemessage()方法根据message.what判断,为什么还要多此一举设计一个callback出来呢.其实我个人猜想,这样的设计可能都是为了扩展性考虑的.如果有大牛写了第三方框架,但是又希望给使用框架的程序员控制框架的一些行为,就可以对外把callback提供出去.让使用者程序员自行拦截message.但是笔者还没有使用过这样的框架.

Handler的原理

         好的,说完了handler的基本使用,下面就轮到handler的原理了.说到handler的原理,就不得不提到message queue和looper.

         下面让我们来追踪一下源码,看看调用sendmessage()方法之后,到底发生了什么.

查看源码可知,不论是post(),postDelayed(),sendmessage()还是sendmessageDelayed()最终都会辗转调用到sendMessageAtTime().

[java] view plaincopy
  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);  

经过一些健壮性判断之后,调用了enqueueMessage()方法.

继续追踪,

[java] view plaincopy
  1.    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  
  2.         msg.target = this;  
  3.         if (mAsynchronous) {  
  4.             msg.setAsynchronous(true);  
  5.         }  
  6.         return queue.enqueueMessage(msg, uptimeMillis);  
  7. }  

继续追踪

[java] view plaincopy
  1. boolean enqueueMessage(Message msg, long when) {  
  2.       if (msg.target == null) {  
  3.           throw new IllegalArgumentException("Message must have a target.");  
  4.       }  
  5.       if (msg.isInUse()) {  
  6.           throw new IllegalStateException(msg + " This message is already in use.");  
  7.       }  
  8.   
  9.       synchronized (this) {  
  10.           if (mQuitting) {  
  11.               IllegalStateException e = new IllegalStateException(  
  12.                       msg.target + " sending message to a Handler on a dead thread");  
  13.               Log.w("MessageQueue", e.getMessage(), e);  
  14.               msg.recycle();  
  15.               return false;  
  16.           }  
  17.   
  18.           msg.markInUse();  
  19.           msg.when = when;  
  20.           Message p = mMessages;  
  21.           boolean needWake;  
  22.           if (p == null || when == 0 || when < p.when) {  
  23.               // New head, wake up the event queue if blocked.  
  24.               msg.next = p;  
  25.               mMessages = msg;  
  26.               needWake = mBlocked;  
  27.           } else {  
  28.               // Inserted within the middle of the queue.  Usually we don't have to wake  
  29.               // up the event queue unless there is a barrier at the head of the queue  
  30.               // and the message is the earliest asynchronous message in the queue.  
  31.               needWake = mBlocked && p.target == null && msg.isAsynchronous();  
  32.               Message prev;  
  33.               for (;;) {  
  34.                   prev = p;  
  35.                   p = p.next;  
  36.                   if (p == null || when < p.when) {  
  37.                       break;  
  38.                   }  
  39.                   if (needWake && p.isAsynchronous()) {  
  40.                       needWake = false;  
  41.                   }  
  42.               }  
  43.               msg.next = p; // invariant: p == prev.next  
  44.               prev.next = msg;  
  45.           }  
  46.   
  47.           // We can assume mPtr != 0 because mQuitting is false.  
  48.           if (needWake) {  
  49.               nativeWake(mPtr);  
  50.           }  
  51.       }  
  52.       return true;  
  53.   }  

可以发现,message被放入messagequeue管理的一个线性结构中.到这里handler的使命就完成了.那么message是如何被主线程使用的呢,handlemessage()怎么样在主线程执行的呢?这里就要请出我们的第三个主角looper了.顾名思义,looper是一个和旋转和圆有关的东西.事实也确实如此,looper中有个loop()方法.让我们来看看它的代码.

[java] view plaincopy
  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.   }  

   可以看到,loop()方法的任务就是在loop关联的queue中遍历,如果取到了message,就调用该message的target属性的dispatchMessage()方法.那么message的target属性又是在哪里被赋值的呢.

   大家还记得上面追踪到的handler.enqueueMessage()方法吗,这个方法里有这么一句msg.target =this;所以message的target对象就是发送它的handler对象.所以所有使用handler.sendMessage()发送的消息,最终都会发送给handler自己.

  那么如果我们不希望发送给默认的target对象该怎么办呢.除了直接调用目标handler的sendMessage()方法,这里补充一个发送消息的方法.

[java] view plaincopy
  1. Message message = Message.obtain();  
  2. message.setTarget(handler);  
  3. message.sendToTarget();  

    好的补充完毕,我们继续追踪dispatchMessage()方法.

[java] view plaincopy
  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. }  

        这里需要注意的是,出现了两个callback,一个是message对象的callback,另一个是mCallback.其中如果使用post()相关的方法发送消息.那么message.callback属性就会被赋值传进来的runnable对象.而mCallback则是创建handler时传进去的拦截消息的方法.

      到了这里,逻辑应该很清楚了,handlecallback()方法就是调用了message.callback的run()方法.

      那么我们该如何,让looper工作起来呢.如果希望代码是在主线程工作的,那么只需要初始化handler的代码是在主线程执行的.其余的不用管,一股脑的发就行.

       但是如果代码的目的地不是主线程.那么就必须在Handler初始化之前调用looper.Prepare().并在最后调用looper.loop()方法让looper工作起来.总而言之,Handler是在那个线程中初始化的(未指定looper参数),那么handlemessage方法就会在哪个线程中执行.但本质上handler的handlemessage方法在哪个线程中执行最主要决定权还是在looper.如果未传递,那么looper就会与当前线程关联,从而在当前线程执行该方法.

HandlerThread是什么?

         首先,看下面代码:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         MyRunnable myRunnable = new MyRunnable();  
  8.         new Thread(myRunnable).start();  
  9.         myRunnable.handler.sendEmptyMessage(1);  
  10.   
  11.     }  
  12.   
  13.     private class MyRunnable implements Runnable {  
  14.         private Handler handler;  
  15.   
  16.         @Override  
  17.         public void run() {  
  18.             Looper.prepare();  
  19.             handler = new Handler(){  
  20.                 @Override  
  21.                 public void handleMessage(Message msg) {  
  22.                     Log.e("tag""currentThread:" + Thread.currentThread());  
  23.                 }  
  24.             };  
  25.             Looper.loop();  
  26.         }  
  27.     }  
  28. }  

    这段代码并没有如预期般打出log,而是报出了空指针异常.那么是什么原因呢?主要的问题就在于多线程的安全问题.

    当使用Handler发送消息时,此时的handler对象在另一条线程中还未初始化.所以就报出了空指针异常.

   解决这个问题,系统已经给我们封装好了一个handlerThread类,使用方法如下:

[java] view plaincopy
  1. public  class MainActivity extends Activity {  
  2.    
  3.     @Override  
  4.     protected void onCreate(BundlesavedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         HandlerThread handlerThread = newHandlerThread("handler thread");  
  8.         handlerThread.start();  
  9.         Handler handler = newHandler(handlerThread.getLooper()){  
  10.             @Override  
  11.             public void handleMessage(Messagemsg) {  
  12.                 Log.e("TAG",Thread.currentThread().toString());  
  13.             }  
  14.         };  
  15.         handler.sendEmptyMessage(1);  
  16.     }  
  17. }  

      这样就可以实现主线程向子线程发送消息了.

      那么HandlerThread为什么可以解决这一问题呢,查看源码可以发现,内部采用了wait()和notify()机制来保证getLooper()每次都可以获得非空的值.

更新UI 的几种方式

         下面来总结一些Android中更新UI 的几种方式:下面列举的几种方式其实本质都是用Handler实现.

1.     Activity    runOnUiThread();

2.     Handler    post()

3.     Handler    sendMessage()

4.     View      post()

非UI线程真的不能更新UI吗

      实际上这种说法是不严谨的.下面这段代码实现了在非UI线程更新UI.

[java] view plaincopy
  1. @Override  
  2.     protected voidonCreate(Bundle savedInstanceState) {  
  3.        super.onCreate(savedInstanceState);  
  4.        setContentView(R.layout.activity_main);  
  5.         textView = (TextView)findViewById(R.id.test);  
  6.         new Thread(newRunnable() {  
  7.             @Override  
  8.             public void run(){  
  9.                textView.setText("子线程中执行");  
  10.             }  
  11.         }).start();  
  12. }  

运行效果:


          看到这里可能大家的世界观都崩塌了.一直查看各种资料都是说子线程中无法更新UI,而这里为什么又可以用子线程更新UI呢.要弄明白这个问题,我们就来查看一下更新UI的调用步骤.

  ->android.widget.TextView.setText

   -> android.widget.TextView.checkForRelayout

     -> android.view.View.invalidate

       -> android.view.ViewGroup.invalidateChild

         -> android.view.ViewRoot.invalidateChildInParent

           -> android.view.ViewRoot.invalidateChild

             -> android.view.ViewRoot.checkThread

         由调用栈可以看到,经过各种调用最后调用了ViewRoot的checkThread()方法.奥秘就在这里,因为ViewRoot的创建时机是在OnResume()方法内.所以在oncreate()时,是不会调用到ViewRoot的checkThread()方法的.也就不会报出异常了.

         可是如果我们让子线程睡眠2秒再去执行settext方法,那么就又会报出无法执行在origin线程执行更新UI的操作.

         实际上,如果我们让子线程也拥有自己的ViewRoot的话,那么子线程也是可以更新UI的.具体实践步骤可以参见参考资料的这篇文章,这里就不详细展开了.

转载:http://blog.csdn.net/u012990751/article/details/43493961

0 0