“刨根问底”之Android 消息机制

首先我们要明白的一个问题是Android为什么会设计这样一个“消息机制”,它有什么作用??

 

要解答这个问题,我们首先要了解,Andorid中的UI控件都是运行在我们应用程序中的主线程中,它们是非线程安全的,也就说所有涉及到UI的相关操作都只能在主进程中进行操作,如果要进行跨线程的操作会报异常。

错误示例代码如下:

   1: TextView textView;
   2: Override
   3: ublic void onCreate(Bundle savedInstanceState) {
   4:    super.onCreate(savedInstanceState);
   5:    setContentView(R.layout.main);
   6:    
   7:    textView = (TextView) findViewById(R.id.textView);
   8:    Button btnAdd = (Button) findViewById(R.id.btnAdd);
   9:    btnAdd.setOnClickListener(new View.OnClickListener() {
  10:        
  11:        @Override
  12:        public void onClick(View v) {
  13:            
  14:            //开启一个线程执行更新UI操作
  15:            new Thread(new Runnable() {
  16:                
  17:                @Override
  18:                public void run() {
  19:                    
  20:                    //......执行一些费时的操作(省略)
  21:                    
  22:                    
  23:                    //执行成功之后将textview上的文字改为成功
  24:                    textView.setText("操作成功!");
  25:                    
  26:                }
  27:                
  28:            }).start();
  29:        }
  30:        
  31:    });



毫无疑问,上面的代码肯定会报错(textView是UI控件,只能在主线程进行操作)。那么要怎么解决这个问题呢,这时候就需要用到Android中的“消息机制”了,

 

要了解消息机制,我们先来了解下这里面涉及到五个关键对象:

Message: 消息对象,包含了一些描述信息和数据。
Handler:     负责消息对象的传递与处理(使用该类时我们必须先继承Handler类,然后重写Handler类中的handlerMessage(msg) 方法,在此方法中进行相关处理操作,如: 更新UI )
MessageQueue: 消息队列。存储由Handler发送过来的Message对象。
Looper:  负责为当前线程运行一个消息循环(MessageQueue)。它会循环抽取MessageQueue中的Message,并通过handler对象将其分发并处理。


消息机制的完整流程:

1. 生成消息

 

Message msg = handler.obtainMessage() / Message.obtain()

 

注意:虽然Message有公有的构造方法,但是建议还是使用上面写的这两种方法。因为,在调用这两个方法时,会去对象池中获取可重复利用的Message对象,避免了重新创建。

 

2.  发送消息

 

handler.sendMessage(msg);  或者 message.sendToTarget();   (它其实内部也是调用了handler.sendMessage(msg)方法)

 

接下来 我们通过分析源码来探究下“发送消息”这个操作的内部机制

 

通过查看源码我们可以了解sendMessage() 只是调用了Handler类中的sendMessageAtTime(),所以这里我们只用分析此方法就行-----源码如下:

 类: Handler   -451行

   1: public boolean sendMessageAtTime(Message msg, long uptimeMillis)
   2: {
   3:     boolean sent = false;
   4:     MessageQueue queue = mQueue; // 3. 问题:mQueue 从何而来????
   5:     if (queue != null) {
   6:         // 1. 将当前的handler对象保存在message的target属性中
   7:         msg.target = this;
   8:          // 2. 将message对象放入MessageQueue中
   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: }

 

上面的这个方法,其实只是做了两个操作:       

        1. 将当前对象(handler)保存在message.target 中, 方便在处理消息对象时使用。

        2. 将message对象存入到MessageQueue-- (入队)。 其实 MessageQueue 类似于我们的链表结构,我们入队的Message对象遵循“先进先出”的原则,越早被入队的Message,会被越早处理。

        除了以上的这两个操作,上面的源码中还留了一个问题?

        3. 该方法中出现的MessageQueue从何而来?? 下面我们来仔细探讨下:

             从刚才的sendMessageAtTime() 我们知道, 将message入队到MessageQueue对象实际上是 Handler 类中一个成员变量 : mQueue  。它是在Handler的构造放被初始化的,接下来我们来看下源码中Handler的构造方法:

 

类: Handler   -109行

   1: public Handler() {
   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:                 klass.getCanonicalName());
   8:         }
   9:     }
  10:     // 获取Looper对象
  11:     mLooper = Looper.myLooper();
  12:     if (mLooper == null) {
  13:         throw new RuntimeException(
  14:             "Can't create handler inside thread that has not called Looper.prepare()");
  15:     }
  16:     //从Looper对象中拿到MessageQueue对象并赋值给Handler类中的成员变量mQueue
  17:     mQueue = mLooper.mQueue;
  18:     mCallback = null;
  19: }

   从以上代码可知,MessageQueue 对象 实际是位于Looper类中,,在构造handler对象的时候,我们从Looper中取出,并将其赋值给handle类中的成员变量。

   到这里我想大家已经能明白: “发送消息”这个操作的内部操作:通过调用handler.sendMessage(msg) 方法,将传入的message对象传入到Looper对象(此对象唯一关联一个线程对象)的消息队列(MessageQueue)当中,等待被处理。

 

3. 执行消息

 

在第二步操作中我们已经知道,消息对象已经被handler发送到Looper中的MessageQueue中了。那么接下来该Message对象又做了什么呢??它最终又是怎么被执行的呢??

 

首先,我们先在Looper的源码中找到存放的消息对象的MessageQueue

   1: //.... 部分代码省略
   2: public class Looper {
   3:     
   4:     final MessageQueue mQueue;
   5:     
   6:     //在构造方法中对MessageQueue对象进行初始化
   7:     private Looper() {
   8:         
   9:         mQueue = new MessageQueue();
  10:  
  11:     }
  12: }

 

接下来我们来看下,传入的Looper类中MessageQueue中的消息对象又是怎么执行的呢?

类: Looper -106行

   1: public static final void loop() {
   2:     Looper me = myLooper();
   3:     MessageQueue queue = me.mQueue;
   4:     while (true) {
   5:         Message msg = queue.next(); 
   6:         if (msg != null) {
   7:             if (msg.target == null) {
   8:                 return;
   9:             }
  10:             msg.target.dispatchMessage(msg);
  11:  
  12:             msg.recycle();
  13:         }
  14:     }
  15: }

 

通过查看上面的源码我们看到,Looper类中有一个Loop() 方法,它里面维护了一个死循环,负责抽取该类中的MessageQueue中的消息对象,然后该消息对象被对应的handler对象(在第二步我们已经知道,handler对象已经被保存在msg的 target属性上了), 传到 handler 对象中的dispatchMessage方法中。 源码如下:

类: Handler -90行

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

 

在此处方法中只是回调了handler.handlerMessage(msg) 方法, 该处理方法默认为空 。 所以,如果我们要对message进行的处理的话,必须继承Handler类然后重写handlerMessage方法,来完成自己的处理。例如:更新 UI。

 

 

好的,到这里Android的消息机制我们已经大概明白了, 过程如下:

 

   ①生成消息(生成Message对象)

       –》

   ②发送消息(消息入队) –》

 

   ③ 执行消息

          (Loop()方法将Message从消息队列中抽取出并传递到与Message相关的handler对象中)

             – 》

          我们重写handler方法中的handlerMessage() 进行相关处理

 

正确示例代码如下:

   1: public class HandlerDemoActivity extends Activity {
   2:    
   3:     TextView textView;
   4:     MyHandler myHandler;
   5:     
   6:     @Override
   7:     public void onCreate(Bundle savedInstanceState) {
   8:         super.onCreate(savedInstanceState);
   9:         setContentView(R.layout.main);
  10:         
  11:         //生成handler对象(注意: handler对象的创建位置非常关键,这个待会儿会详解)
  12:         myHandler = new MyHandler();
  13:         
  14:         textView = (TextView) findViewById(R.id.textView);
  15:         Button btnAdd = (Button) findViewById(R.id.btnAdd);
  16:         btnAdd.setOnClickListener(new View.OnClickListener() {
  17:             
  18:             @Override
  19:             public void onClick(View v) {
  20:                 
  21:                 //开启一个线程执行更新UI操作
  22:                 new Thread(new Runnable() {
  23:                     Handler handler;
  24:                     @Override
  25:                     public void run() {
  26:                         
  27:                         //......执行一些费时的操作(省略)
  28:                         
  29:                         
  30:                         //执行成功之后将textview上的文字改为成功
  31:                         //textView.setText("操作成功!");-- 这是错误的
  32:                         
  33:                         
  34:                         // 1. 生成消息
  35:                         Message message = handler.obtainMessage();
  36:                         //message.setData(data); 可以设置一些参数数据
  37:                         
  38:                         // 2. 发送消息
  39:                         handler.sendMessage(message);
  40:                         
  41:                         
  42:                     }
  43:                     
  44:                 }).start();
  45:             }
  46:             
  47:         });
  48:     }
  49:     
  50:     //继承Handler类,并重写handlerMessage方法
  51:     class MyHandler extends Handler{
  52:         
  53:         
  54:         public MyHandler() {
  55:             super();
  56:         
  57:         }
  58:         
  59:         //3. 处理Message
  60:         @Override
  61:         public void handleMessage(Message msg) {
  62:             
  63:             
  64:             textView.setText("操作成功!");
  65:             
  66:         }
  67:     }
  68: }

 

上面的代码是一个完整的Android消息机制实现,我们先来通过代码重温下 Android消息机制的过程:

       ① 示例代码第30行:

             创建了一个消息对象(我们可以设置一些额外的数据,通过Message对象上的属性)

       ② 示例代码第39行:

             将创建的消息对象发送出去,该消息对象会被发送到Looper中的MessageQueue中。

       ③Looper类源码第106行:( Loop() 方法 )

   1: public static final void loop() {
   2:     Looper me = myLooper();
   3:     MessageQueue queue = me.mQueue;
   4:     while (true) {
   5:         Message msg = queue.next(); 
   6:         if (msg != null) {
   7:             if (msg.target == null) {
   8:                 return;
   9:             }
  10:             msg.target.dispatchMessage(msg);
  11:  
  12:             msg.recycle();
  13:         }
  14:     }
  15: }

        循环MessageQueue并调用所对应的hangdler对象的dispatchMessage(msg) 方法,将其分发出去,被分发出去的方法将会在Handler的handlerMessage方法接收到。

      ④ 示例代码第61行: (handlerMessage() 方法)

        我们消息对象终于到到“目的地”,并且可以被处理了。

 

 

通过这个过程我们发现 Message对象是由handler发送,然后绕了一大圈儿之后又被送回到handler对象, 并进行相应的处理。 它之所以这么“麻烦”就是为了变相解决“在别的线程操作UI” 的问题。----- 在其它线程中进行一些相关操作(一般是比较费时的)后,并不是在当前线程中更新UI,而是传递一个消息到与UI线程相关联的Looper中,  然后会在Looper中的Loop方法被抽取并传送至handler对象的handlerMessage方法。

 

最后,我们还要解决一个疑问就是: 为什么能在handler中进行UI操作 -- textView.setText("操作成功!"); --- 示例代码 39 行

 

答案的关键点就是handler对象的创建位置:

 

   1: public class HandlerDemoActivity extends Activity {
   2:    
   3:     TextView textView;
   4:     MyHandler myHandler;
   5:     
   6:     @Override
   7:     public void onCreate(Bundle savedInstanceState) {
   8:         super.onCreate(savedInstanceState);
   9:         setContentView(R.layout.main);
  10:         
  11:         //生成handler对象(此操作是在UI主线程中)
  12:         myHandler = new MyHandler();
  13:  
  14:        //..........以下代码省略

 

从上面代码我们看出来我们的handler对象实际是在UI线程中创建的,,当我们创建在UI线程中创建handler对象时,android会将此对象和当前UI线程对象中的消息队列绑定, 源码如下:

   1: public Handler() {
   2:  
   3:     //.... 部分代码省略    
   4:     
   5:     //从当前线程中取出Looper对象并向其赋值给Handler的成员变量mLooper
   6:     mLooper = Looper.myLooper();
   7:     if (mLooper == null) {
   8:         throw new RuntimeException(
   9:             "Can't create handler inside thread that has not called Looper.prepare()");
  10:     }
  11:     // 从Looper中取出MessageQueue对象并赋值
  12:     mQueue = mLooper.mQueue;
  13:     mCallback = null;
  14: }

下面是Looper.myLooper()方法的源码:

   1: public static final Looper myLooper() {
   2:     //从当前线程中获取Looper对象
   3:     return (Looper)sThreadLocal.get();
   4: }

 

通过这两段代码我们就明白了,handler其实是间接关联(handler关联Looper,而Looper又唯一对应一个线程,并负责为当前线程运行消息循环)当前线程对象,如果当前线程是UI线程的话那么我们就可以处理同在UI线程中的UI控件了。

 

另外,有一点要提醒的就是,如果我们的Handler对象不是在UI线程中进行初始化的,那么我们就得自己为当前线程构造一个Looper对象,如果要运行的话还得自己启动位于Looper中的消息循环(Loop()方法). 示例代码如下:

   1: class LooperThread extends Thread {
   2:      public Handler mHandler;
   3:  
   4:      public void run() {
   5:          //构造一个Looper对象,并向其放入到当前线程中(感兴趣的同学可以通过源码查看明细)
   6:          Looper.prepare();
   7:  
   8:          mHandler = new Handler() {
   9:              public void handleMessage(Message msg) {
  10:                  // process incoming messages here
  11:              }
  12:          };
  13:          
  14:          //启动消息处理循环
  15:          Looper.loop();
  16:      }
  17:  }

如果handler是在UI线程中进行构造的,那么上面代码中注释的两句代码,android会自动帮我们执行。

 

 

ok, 到此,此文结束!

posted @ 2011-06-16 09:40  胡言乱语  阅读(1525)  评论(0编辑  收藏  举报