解析Android消息机制

前言

这篇博客将会涉及以下内容:

1、消息机制概述

2、UML图解消息机制相关类

3、从在主线程更新UI的方法带你畅游消息机制的源码,更加方便自己理解

4、Handler

5、Looper

6、MessageQueue和Message

7、消息机制的应用

消息机制概述

Android 系统在设计的初期就已经考虑到了 UI 的更新问题,由于 Android 中的 View 是线程不安全的,然而程序中异步处理任务结束后更新 UI 元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给 View 加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:
1、加锁会有导致效率底下

2、由于可以在多个地方更新 UI,开发就必须很小心操作,开发起来就很麻烦,一不小心就出错了。

基于以上两个缺点,这种方式被抛弃。于是机智如我谷歌。。。设置一个线程专门处理 UI控件的更新,如果其他线程也需要对 UI 进行更新,不好意思,您把您想做的告诉那个专门处理 UI 线程的家伙,让它帮你做。

那么您也看出来了,消息机制其实可以很简单的用一句话概括,就是:其他线程通过给特定线程发送消息,将某项专职的工作,交给这个特定的线程去做。比如说其他线程都把处理 UI 显示的工作通过发送消息交给 UI 线程去做。

实现的原理呢,我是这么理解的,现在要做的主要工作就是切换线程去操作,怎么切换呢?把两条线程看作是两条并行的公路,如果要从一条公路转到另一条公路上,要怎么做呢?是不是只要找到两条公路交叉或者共用某个资源的地方,如果说交叉路口,比如说加油站。当然了,线程是不存在交叉的地方的,那么可以考虑他们公用资源的地方,不同的进程享用不同的内存空间,但是同一个进程的不同线程享用的是同一片内存空间,那让其他线程把要处理的消息放到这个特定的内存空间上,处理消息的线程来这个内存空间上来取消息去处理不就可以了吗。事实正是如此,在 Android 的消息机制中,扮演这个特定内存空间的对象就是 MessageQueue 对象,发送和处理的消息就是 Message 对象。其他的Handler 和 Looper 都是为了配合线程切换用的。

这样理解起来是不是就是 so easy 了呢?

UML图解消息机制相关类

不知道上面的说法您是否可以对消息机制有了一个基本的认知呢?用类图来对消息机制中涉及到的几个类有一个概括的认识;通过时序图,可以很清晰的观察到整个消息机制的处理过程。

消息主要设计到下面几个类:

  • Handler:这是消息的发出的地方,也是消息处理的地方。

  • Looper:这是检测消息的地方。

  • MessageQueue: 这是存放消息的地方,Handler 把消息发到了这里,Looper 从这里取出消息交给 Handler 进行处理

  • Message:呜呜呜…他们发的是我,处理的也是我。

  • Thread:我在这里专门指代的是,处理消息的线程。消息的发送是在别的线程。

话不多说,先来看一张图(UML 忘的差不多了,刚补的,如果有错误,麻烦大家指出)

image

畅游源码

图在这里了,怎么看呢,整个消息机制相关的类密密麻麻支撑了一张网,咋个看嘛......不急不急,咱们先来思考一下咱们常用的更新UI是怎么一个操作步骤。

  1. 在主线程新建一个 Handler 对象,在构造方法中传入一个实现 Handler.Callback 接口的匿名类的对象,实现接口中的 handleMessage 方法

  2. 在非 UI 线程使用 Handler 的 sendMessage 或者 post 方法发送一个消息

  3. 然后 handleMessage 方法会在不久的将来马上执行,实现更新 UI 的操作。

那咱们就跟着这个思路来看一看这张图,先看 Handler 类,你会发现,Handler 真的是个相当关键的核心(当然,其他部分也是不可或缺的),他几乎拥有所有其他相关对象的引用。

  • Handler 拥有 Looper 的引用,通过得到 Looper 对象获得 Looper 中保存的 MessageQueue 对象

  • Handler 拥有 MessageQueue 的引用,使 Handler 得以拥有发送消息(将 Message 放入 MessageQueue )的能力

  • Handler 拥有 Handler.Callback 的引用,使得 Handler 可以方便的进行消息的处理。

来思考一个问题:为什么 Handler 在其他线程发送消息之后,就跑到了主线程的 handleMessage方 法中去更新 UI ?

这个问题暂时先放着,等下回过头再来看。

我们现在先跟着第1,2,3步看看系统都帮我们做了什么操作呢?这就是在看源码,不要觉得很高深。下面是鲜活的代码,为了方便您查看,我帮您摘出来了。如果有兴趣,您也可以在AS里点开看看

 //这是在主线程中   
 Handler handler = new Handler(new Handler.Callback() {        
@Override       
 public boolean handleMessage(Message msg) {           
 switch (msg.what) {                
case 1:                   
 Toast.makeText(mContext, "你真漂亮", Toast.LENGTH_SHORT).show();                    break;               
 case 2:                    
Toast.makeText(mContext, "你也很帅呢", Toast.LENGTH_SHORT).show();                    break;               
 default:                   
 break;            
}           
 return false;       
 }    
});    
//这是我们在主线程中创建Handler时会使用的构造方法   
 public Handler(Callback callback) {        
this(callback, false);//调用了下面的这个构造方法↓   
 }    //先不要管第二个参数。跟紧主线,别跟丢了    
public Handler(Callback callback, boolean async) {       
 if (FIND_POTENTIAL_LEAKS) {           
 final Class<? extends Handler> klass = getClass();            
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());           
 }       
 }         
 //在这里获取到Looper对象,怎么获取的,稍后再看      
  mLooper = Looper.myLooper();      
  //如果获取的mLooper为空,直接抛出异常,说你不能在一个没有调用Looper.prepare()方法       
 //的线程里创建Handler       
 //如此看来,Looper.prepare()方法重要的嘞      
  if (mLooper == null) {           
 throw new RuntimeException(               
 "Can't create handler inside thread that has not called Looper.prepare()");        }
        //通过mLooper对象获取MessageQueue这个消息队列(单链表)        
mQueue = mLooper.mQueue;      
  mCallback = callback;      
  mAsynchronous = async;    
}

到此为止,一个 Handler 就创建好了,(还有一个问题是 Looper.prepare 方法很重要,但是我们还没有去考虑他是干嘛的,不急不急,先顺着一条线看,不然看源代码的过程会把你搞死翘翘的)先面就该进行第二步,看看 Handler.sendMessage 干了啥,代码段又来喽!

//在一个新建的线程里使用创建好的Handler发送一个消息  
  new Thread(new Runnable() {       
 @Override        public void run() {           
 //在这儿干点你想干的吧,一些耗时的计算或者网络操作啥的         
   Message message = new Message();           
 message.what = 1;          
 handler.sendMessage(message);    
    }
    }).start();    //直接调用的是这个函数    
public final boolean sendMessage(Message msg){   
     return sendMessageDelayed(msg, 0);    }   
 //转而调用了这个函数  
  public final boolean sendMessageDelayed(Message msg, long delayMillis)    { 
       if (delayMillis < 0) {        
    delayMillis = 0;      
  }      
  return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }    //转而又来到了这里    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;    
    if (queue == null) {      
      RuntimeException e = new RuntimeException(        
            this + " sendMessageAtTime() called with no mQueue");         
   Log.w("Looper", e.getMessage(), e);      
      return false;        }     
   return enqueueMessage(queue, msg, uptimeMillis);    }  
  //最后的最后来到了这里
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {      
  //target就是Message绑定的Handler,看看类图,上面有这个细节        msg.target = this;        if (mAsynchronous) {       
     msg.setAsynchronous(true);        }  
      //最后的最后,调用了MessageQueue的enqueueMessage方法  
      return queue.enqueueMessage(msg, uptimeMillis);    }   
 //再看一下MessageQueue的enqueueMessage方法, 
   //我把其他一些无关的细节给删掉了,只为了更加容易阅读
    boolean enqueueMessage(Message msg, long when) {    
    synchronized (this) {         
   Message p = mMessages;            
if (p == null) {          
      // New head, wake up the event queue if blocked.       
         msg.next = p;        
        mMessages = msg;         
   } else {       
         //下面的错误就是遍历单链表,找到链表的尾部,这个没有难度的吧?                Message prev;        
        for (;;) {           
         prev = p;          
          p = p.next;            
        if (p == null || when < p.when) {                  
      //找到了尾部,现在的结构是这样的。   
                     //A->B->C...->pre(p)->null       
                 break;               
     }           
     }           
     msg.next = p; // invariant: p == prev.next  
              prev.next = msg;//把next插入链表的尾部  
          }     
  }   
     return true;    } 

到此为止,第二步就结束了,成功把一个消息插入到了 MessageQueue 的尾部。可是你很快就会发现,第三步好像从这条路探寻不下去了。接下来就等着别人来调用 Handler 中的方法了,可是是谁调用的,在哪儿调用的?我们现在好像毫无头绪了?怎么办?怎么办?我们刚才不是看到一个 Looper.myLooper() 和 Looper.prepare() 方法,说是很重要但是一直没看吗,既然现在搜寻不下去了,是不是可以回头看看了?还有一点,Looper , 看起来是在循环,循环什么玩意儿呢?我们去好好看看 Looper 类吧。 一共就三百来行代码,仔细看看,你会发现有一个核心方法:Looper.loop(); 同样的,我把影响阅读的非主线代码剔除了,发现 Looper.loop 方法就长这样:

 /**     * Run the message queue in this thread. Be sure to call     * {@link #quit()} to end the loop.     */   
 public static void loop() {       
 final Looper me = myLooper();      
  if (me == null) {         
   throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");    
    }      
  final MessageQueue queue = me.mQueue;     
   for (;;) {       
     Message msg = queue.next(); // might block      
      if (msg == null) {      
          // No message indicates that the message queue is quitting.    
            return;            }           
 try {          
      msg.target.dispatchMessage(msg);       
     } finally {       
     }      
  }  
  }

清晰可见的是,Looper.loop() 方法一直遍历 MessqgeQueue,阻塞线程,直到获取到一个Message ,然后调用了 Message 的一个成员变量 target( 其实就是 Handler )的 dispatchMessage(msg) 方法,嗨,还真的又跟 Handler 扯上关系了,既然这里扯上关系了,而且还是一个分发消息的方法,可以大胆猜测就是让Handler去处理这个消息的。
那么我们来看看这个方法:

  /**     * Handle system messages here.     * 如果Message中callback对象不为空(这是调用handler.post(Runnable)方法发送的消息),     * 就调用callback的run方法     * 否则如果创建Handler的时候如果设置了Callback就调用创建时候的传入的     * 实现Handler.Callback接口的类的对象的handleMessage方法,看这就是回调方法被调用的地方。     * 再如果没有mCallback对象,就调用自身的handleMessage方法,为了Handler的子类复写了该方法的时候,方便调用,如,IntentService里的ServiceHandler就是继承自Handler的,并且重写了handleMessage方法。     */   
 public void dispatchMessage(Message msg) {  
      if (msg.callback != null) {       
     handleCallback(msg);      
  } else {         
   if (mCallback != null) {  
              if (mCallback.handleMessage(msg)) {        
            return;       
         }      
      }        
    handleMessage(msg);   
     }  
  }   
 private static void handleCallback(Message message) {        message.callback.run();    }  
  //ServiceHandler继承自Service并且重写了handleMessage方法  
  private final class ServiceHandler extends Handler {   
     public ServiceHandler(Looper looper) {       
     super(looper);        } 
       @Override       
 public void handleMessage(Message msg) {      
      onHandleIntent((Intent)msg.obj);    
        stopSelf(msg.arg1);        }  
  }

到了这里,三步走已经看完了,我想消息机制在我们心里已经又清晰了一层,但是不用急,咱们前边提的一个问题不是还没有解决吗,先把他解决掉吧,一起继续来看源码。

到了这里,三步走已经看完了,我想消息机制在我们心里已经又清晰了一层,但是不用急,咱们前边提的一个问题不是还没有解决吗,先把他解决掉吧,一起继续来看源码。

咱们现在已知的是这样的,在主线程创建的Handler发送了一个消息,发送消息的代码运行在其他线程,将代码加入消息队列也是在其他线程(加了线程同步锁)。然后 handleMessage 发生在主线程,那么调用该方法的 dispatchMessage 方法也是运行在主线程的,dispatchMessage 是在 Looper.loop 方法中调用的,也就是说loop方法也运行在主线程,那么问题就明朗了,可是 loop 方法是谁调用的,在哪里调用的呢?当然是系统启动的时候创建主线程之后再主线程的 run 方法中调用了 Looper.prepare 和 Looper.loop 方法,但是这点我还没看,留着以后再看吧。

通过上面的分析,我们是不是可以自己来试着建立这样一个模型:

  1. 创建一个线程 A

  2. 在这个线程的 run 方法中调用 Looper.prepare 和 Looper.loop 方法使该线程阻塞,等待消息发过来,然后处理

  3. 在该线程中创建一个 Hanlder,用来处理 looper 发送来的待处理的消息

  4. 创建一些其他的线程a、b、c,做一点操作之后,通过 Handler 把消息传递出去,让线程 A 去处理。

public class MyThread extends Thread {
    private static final String TAG = "MyThread";   
 Handler handler = new Handler(new Handler.Callback() {   
     @Override    
    public boolean handleMessage(Message msg) {  
          switch (msg.what) {  
              case 1:                  
  Log.i(TAG,"你真漂亮");          
          break;              
  case 2:                
    Log.i(TAG,"你也很帅呢");                 
   break;           
     default:          
          break;        
    }         
   return false;     
   } 
   });  
  public Handler getHandler() {      
  return handler;  
  }   
 @Override  
  public void run() { 
       super.run(); 
       Looper.prepare(); 
       Looper.loop();  
  }  
  @Override  
  public void destroy() {     
   super.destroy();     
   Looper.myLooper().quit();  
  }
}//    private void testMyThread() {  
      MyThread thread = new MyThread();  
      thread.start();    
    final Handler handler = thread.getHandler();   
     Message message = new Message();  
      message.what = 1;  
      handler.sendMessage(message);   
     new Thread(new Runnable() {      
      @Override         
   public void run() {           
     try {                 
   sleep(400);               
     Message message = new Message();    
                message.what = 2;            
        handler.sendMessage(message);     
           } catch (InterruptedException e) {        
            e.printStackTrace();    
            }       
     }   
     }).start();    }

试着想一想,如果把线程 A 看成主线程,在回调方法更新 UI,那这不就是 Android 系统中更新 UI 使用的套路吗?不错,事实本就如此,工具是工具,用它来更新 UI 是可以的,那你当然也可以用来做一些其他的工作啊。

在这里,通过以上的分析,不难得到下面的这个整个消息机制运行过程的时序图:

image

ok 啦,源码阅读到此为止。其他的细节,有兴趣的可以再细细研究一下。

下面我们来对涉及到的类进行一下总结。

Handler

handler在消息机制中,扮演的是消息的发送方和处理方(通过回调函数)。消息在一个线程通过 Handler 发送到 MessageQueue 中。Looper 获取到 Message 之后,根据 Message 中保存的 handler 对象调用 handler 对象的 dispatch 方法,进行消息的处理。

Looper

Looper 在这里扮演的是一个轮询消息队列的角色,以为不停地去问 MessageQueue 要消息,得到消息之后,根据 Message 中保存的 handler 对象调用 handler 对象的 dispatch 方法,进行消息的处理。

MessageQueue 和 Message

MessageQueue 实质上是一个单链表的结构,里面以链表的形式保存着 Handler 发送过来的消息,当有新消息发来时放在链表的尾部,Looper 要取消息的时候从链表的头部取出消息返回给 Looper 处理。Message 对象中保存在要处理的信息,同时也持有消息发送方(Handler)的引用,Looper 在得到该 Message 的时候,可以从 Message 中拿到消息的发送方,调用发送方的回调方法将消息传递过去交给 Handler 进行处理。

消息机制的应用

在 Android 中有很多消息机制的应用,如:
UI 的更新

HandlerThread

IntentService

UI 的更新

UI 线程持有一个 Looper 对象,Looper 对象的 loop 方法在 UI 线程中一直不停的进行死循环,直到有新的消息发来的时候,交给特定的组件进行处理,当然了这个处理也是在主线程运行的(如我们设置的点击事件也是等着被 UI 线程调用的),正是由于这个原因,我们不能在主线程处理耗时操作。如果我们一个 View 的点击事件里做了大量耗时的操作,由于这个操作也在主线程中运行,主线程必须等着这个操作操作结束才能去处理其他的消息,这个时候表现的就是系统卡顿甚至报 ANR 的错误。

HandlerThread

HandlerThread 继承自 Thread,内部保存一个 Looper 对象。
这是一个系统帮我们包装好的 Thread,这个线程的 run 方法已经调用了 Looper.prepare 和Looper.loop(即已经绑定了一个Looper对象,并且可以开始轮询消息),创建该对象之后可以通过获得对象获取到一个 Looper 对象,将 Looper 对象传递给 Handler,完成 Handler和 Looper 以及 MessageQueue 的绑定。最后再其他的线程中调用 Handler 的 sendMessage 或者 post(Runable)方法发送消息,handler 中的 callback.handleMessage 方法会在 HandlerThread 中运行。即,将消息发送到了特定的线程(此处是 HandlerThread)处理。

IntentService

IntentService 继承自 Service,运行时优先级更高,内部使用了 HandlerThread 作为处理消息的线程。内部有一个私有内部类 ServiceHandler 继承自 Handler,并且会创建一个ServiceHandler 对象。

使用 startService()方法启动 IntentService 时,不会重新创建一个服务,会调用 ServiceHandler 对象发送包含该 Intent 的 Message 对象,该对象通过 HandlerThread 处理后交给ServiceHandler 重写的 handleMessage 方法进行处理,处理的方式是调用 IntentService 的 onHandleIntent(Intent)方法,所以使用的方式就是创建一个继承自 IntentService 类的子类,并重写 onHandleIntent 方法,在该方法中处理 startService 时传递的 Intent。Intent 中包含有要交给 Service 处理的信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容