android 开发Handler源码剖析

Android的消息机制主要是Handler的运行机制,而讲Handler的机制,又需要和MessageQueue和Looper结合。MessageQueue中文意思是消息队列,虽说叫队列,但是其内部结构并不是队列组成的,而是采用了单链表的形式来存储消息。MessageQueue只是负责存储消息,并不处理消息(这里指消息的轮训),Looper刚好弥补了这个空缺。我在知道,Handler创建的时候,会默认为我们创建一个Looper对象,那么如何获取当前的Looper呢,这里就使用到了一个TheadLocal的概念,TheadLocal可以轻松的获取当前使用的Looper。

Handler的使用:

Handler 主要有两种用法:( 1 )调度消息和 runnable 对象,并在某一时间点执行。( 2 )依次存放分属不同线程的行为。

 

线程在默认的时候是没有Looper与之相关联。在线程中,我们可以通过调用prepare 方法来启动一个消息loop,调用loop方法来通知Looper来处理消息,直至结束。Looper提供了很多的静态方法来与线程、消息队列进行交互。一个线程最多允许创建一个Looper。

以下是SDK文档中介绍的在线程中使用handler的一种方法:

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">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.         }</span>  


注意这里,官方的例子是和Looper关联了,那么如果我们不关联Looper的话会怎么样呢

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;"mRunnable = new  Runnable(){  
  2.            @Override  
  3.            public void run() {  
  4.               // TODO Auto-generated method stub      
  5.               String s = mHandler.getLooper().getThread().getName();  
  6.               Log.e("handler", s);  
  7.               mHandler.postDelayed(mRunnable, 1000);  
  8.            }};  
  9. Thread t  = new Thread(mRunnable,"thread");  
  10.         t.start();</span>  

运行结果:
ERROR/handler(1630): main,可以发现我们创建了一个名为“thread”的线程,病通过handler发送消息,我们创建的handler自动和UI线程的Looper关联上了。

 

那么,可不可以在主线程中定义handler,在代码中动态改变handler使之与其他线程的looper相关联呢?是可以的。

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">mRunnable = new  Runnable(){  
  2.               @Override  
  3.               public void run() {  
  4.                   // TODO Auto-generated method stub  
  5.                   Looper.prepare();  
  6.                   mHandler = new Handler(Looper.myLooper());  
  7.                   String s = mHandler.getLooper().getThread().getName();  
  8.                   Log.e("handler", s);  
  9.                   mHandler = new Handler(Looper.getMainLooper());  
  10.                    s = mHandler.getLooper().getThread().getName();  
  11.                   Log.e("handler", s);          
  12.               }};  
  13.     Thread t  = new Thread(mRunnable,"subthread");  
  14.             t.start();</span>  

运行结果:

 

 

ERROR/handler(4049):subthread

ERROR/handler(4049): main

不过我们Android为我们提供了一个包含Looper的Thead类,HandlerThread  HandlerThread自带了一个Lopper,

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;"handlerThread=new HandlerThread("handlerThead");  
  2.         handlerThread.start();  
  3.         handler=new Handler(handlerThread.getLooper());  
  4.         handler.post(new Runnable() {  
  5.             @Override  
  6.             public void run() {  
  7.                 String s=handler.getLooper().getThread().getName();  
  8.                 Log.d("test",s);  
  9.             }  
  10.         });</span>  

注:android认为在非线程中操作UI界面是不安全的,因此禁止在其它线程中修改UI界面。解决的方法有两种,一是通过在主线程中定义的handler更新界面,二是直接调用被修改的view的postInvalidate方法刷新单个view。

其实上面的描述是不准确的,实际上在activity第一次执行的时候,是可以在子线程直接更新主线程数据的,条件是不能执行Thread.sleep(),

原因是什么呢?

1.当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。
2.-->刚启动的时候,立即在非UI线程更新->不报错(onResume还没有执行)
--->休眠2s钟以后,更新——————>报错

最后贴上一张图

 

分析完了Android消息机制的流程,那么我们接下来分别理解一些重要的概念。主要从以下几个方面增强理解,Handler,MessageQueue,Looper和Threadloacal.

posted @ 2017-04-21 22:48  天涯海角路  阅读(104)  评论(0)    收藏  举报