解析Android消息機(jī)制

前言

這篇博客將會涉及以下內(nèi)容:

1、消息機(jī)制概述

2、UML圖解消息機(jī)制相關(guān)類

3、從在主線程更新UI的方法帶你暢游消息機(jī)制的源碼,更加方便自己理解

4、Handler

5、Looper

6、MessageQueue和Message

7、消息機(jī)制的應(yīng)用

消息機(jī)制概述

Android 系統(tǒng)在設(shè)計的初期就已經(jīng)考慮到了 UI 的更新問題,由于 Android 中的 View 是線程不安全的,然而程序中異步處理任務(wù)結(jié)束后更新 UI 元素也是必須的。這就造成了一個矛盾,最簡單的解決方法肯定是給 View 加同步鎖使其變成線程安全的。這樣做不是不可以,只是會有兩點壞處:
1、加鎖會有導(dǎo)致效率底下

2、由于可以在多個地方更新 UI,開發(fā)就必須很小心操作,開發(fā)起來就很麻煩,一不小心就出錯了。

基于以上兩個缺點,這種方式被拋棄。于是機(jī)智如我谷歌。。。設(shè)置一個線程專門處理 UI控件的更新,如果其他線程也需要對 UI 進(jìn)行更新,不好意思,您把您想做的告訴那個專門處理 UI 線程的家伙,讓它幫你做。

那么您也看出來了,消息機(jī)制其實可以很簡單的用一句話概括,就是:其他線程通過給特定線程發(fā)送消息,將某項專職的工作,交給這個特定的線程去做。比如說其他線程都把處理 UI 顯示的工作通過發(fā)送消息交給 UI 線程去做。

實現(xiàn)的原理呢,我是這么理解的,現(xiàn)在要做的主要工作就是切換線程去操作,怎么切換呢?把兩條線程看作是兩條并行的公路,如果要從一條公路轉(zhuǎn)到另一條公路上,要怎么做呢?是不是只要找到兩條公路交叉或者共用某個資源的地方,如果說交叉路口,比如說加油站。當(dāng)然了,線程是不存在交叉的地方的,那么可以考慮他們公用資源的地方,不同的進(jìn)程享用不同的內(nèi)存空間,但是同一個進(jìn)程的不同線程享用的是同一片內(nèi)存空間,那讓其他線程把要處理的消息放到這個特定的內(nèi)存空間上,處理消息的線程來這個內(nèi)存空間上來取消息去處理不就可以了嗎。事實正是如此,在 Android 的消息機(jī)制中,扮演這個特定內(nèi)存空間的對象就是 MessageQueue 對象,發(fā)送和處理的消息就是 Message 對象。其他的Handler 和 Looper 都是為了配合線程切換用的。

這樣理解起來是不是就是 so easy 了呢?

UML圖解消息機(jī)制相關(guān)類

不知道上面的說法您是否可以對消息機(jī)制有了一個基本的認(rèn)知呢?用類圖來對消息機(jī)制中涉及到的幾個類有一個概括的認(rèn)識;通過時序圖,可以很清晰的觀察到整個消息機(jī)制的處理過程。

消息主要設(shè)計到下面幾個類:

  • Handler:這是消息的發(fā)出的地方,也是消息處理的地方。

  • Looper:這是檢測消息的地方。

  • MessageQueue: 這是存放消息的地方,Handler 把消息發(fā)到了這里,Looper 從這里取出消息交給 Handler 進(jìn)行處理

  • Message:嗚嗚嗚…他們發(fā)的是我,處理的也是我。

  • Thread:我在這里專門指代的是,處理消息的線程。消息的發(fā)送是在別的線程。

話不多說,先來看一張圖(UML 忘的差不多了,剛補(bǔ)的,如果有錯誤,麻煩大家指出)

image

暢游源碼

圖在這里了,怎么看呢,整個消息機(jī)制相關(guān)的類密密麻麻支撐了一張網(wǎng),咋個看嘛......不急不急,咱們先來思考一下咱們常用的更新UI是怎么一個操作步驟。

  1. 在主線程新建一個 Handler 對象,在構(gòu)造方法中傳入一個實現(xiàn) Handler.Callback 接口的匿名類的對象,實現(xiàn)接口中的 handleMessage 方法

  2. 在非 UI 線程使用 Handler 的 sendMessage 或者 post 方法發(fā)送一個消息

  3. 然后 handleMessage 方法會在不久的將來馬上執(zhí)行,實現(xiàn)更新 UI 的操作。

那咱們就跟著這個思路來看一看這張圖,先看 Handler 類,你會發(fā)現(xiàn),Handler 真的是個相當(dāng)關(guān)鍵的核心(當(dāng)然,其他部分也是不可或缺的),他幾乎擁有所有其他相關(guān)對象的引用。

  • Handler 擁有 Looper 的引用,通過得到 Looper 對象獲得 Looper 中保存的 MessageQueue 對象

  • Handler 擁有 MessageQueue 的引用,使 Handler 得以擁有發(fā)送消息(將 Message 放入 MessageQueue )的能力

  • Handler 擁有 Handler.Callback 的引用,使得 Handler 可以方便的進(jìn)行消息的處理。

來思考一個問題:為什么 Handler 在其他線程發(fā)送消息之后,就跑到了主線程的 handleMessage方 法中去更新 UI ?

這個問題暫時先放著,等下回過頭再來看。

我們現(xiàn)在先跟著第1,2,3步看看系統(tǒng)都幫我們做了什么操作呢?這就是在看源碼,不要覺得很高深。下面是鮮活的代碼,為了方便您查看,我?guī)湍鰜砹?。如果有興趣,您也可以在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;       
 }    
});    
//這是我們在主線程中創(chuàng)建Handler時會使用的構(gòu)造方法   
 public Handler(Callback callback) {        
this(callback, false);//調(diào)用了下面的這個構(gòu)造方法↓   
 }    //先不要管第二個參數(shù)。跟緊主線,別跟丟了    
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為空,直接拋出異常,說你不能在一個沒有調(diào)用Looper.prepare()方法       
 //的線程里創(chuàng)建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 就創(chuàng)建好了,(還有一個問題是 Looper.prepare 方法很重要,但是我們還沒有去考慮他是干嘛的,不急不急,先順著一條線看,不然看源代碼的過程會把你搞死翹翹的)先面就該進(jìn)行第二步,看看 Handler.sendMessage 干了啥,代碼段又來嘍!

//在一個新建的線程里使用創(chuàng)建好的Handler發(fā)送一個消息  
  new Thread(new Runnable() {       
 @Override        public void run() {           
 //在這兒干點你想干的吧,一些耗時的計算或者網(wǎng)絡(luò)操作啥的         
   Message message = new Message();           
 message.what = 1;          
 handler.sendMessage(message);    
    }
    }).start();    //直接調(diào)用的是這個函數(shù)    
public final boolean sendMessage(Message msg){   
     return sendMessageDelayed(msg, 0);    }   
 //轉(zhuǎn)而調(diào)用了這個函數(shù)  
  public final boolean sendMessageDelayed(Message msg, long delayMillis)    { 
       if (delayMillis < 0) {        
    delayMillis = 0;      
  }      
  return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }    //轉(zhuǎn)而又來到了這里    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,看看類圖,上面有這個細(xì)節(jié)        msg.target = this;        if (mAsynchronous) {       
     msg.setAsynchronous(true);        }  
      //最后的最后,調(diào)用了MessageQueue的enqueueMessage方法  
      return queue.enqueueMessage(msg, uptimeMillis);    }   
 //再看一下MessageQueue的enqueueMessage方法, 
   //我把其他一些無關(guān)的細(xì)節(jié)給刪掉了,只為了更加容易閱讀
    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) {                  
      //找到了尾部,現(xiàn)在的結(jié)構(gòu)是這樣的。   
                     //A->B->C...->pre(p)->null       
                 break;               
     }           
     }           
     msg.next = p; // invariant: p == prev.next  
              prev.next = msg;//把next插入鏈表的尾部  
          }     
  }   
     return true;    } 

到此為止,第二步就結(jié)束了,成功把一個消息插入到了 MessageQueue 的尾部??墒悄愫芸炀蜁l(fā)現(xiàn),第三步好像從這條路探尋不下去了。接下來就等著別人來調(diào)用 Handler 中的方法了,可是是誰調(diào)用的,在哪兒調(diào)用的?我們現(xiàn)在好像毫無頭緒了?怎么辦?怎么辦?我們剛才不是看到一個 Looper.myLooper() 和 Looper.prepare() 方法,說是很重要但是一直沒看嗎,既然現(xiàn)在搜尋不下去了,是不是可以回頭看看了?還有一點,Looper , 看起來是在循環(huán),循環(huán)什么玩意兒呢?我們?nèi)ズ煤每纯?Looper 類吧。 一共就三百來行代碼,仔細(xì)看看,你會發(fā)現(xiàn)有一個核心方法:Looper.loop(); 同樣的,我把影響閱讀的非主線代碼剔除了,發(fā)現(xiàn) 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 ,然后調(diào)用了 Message 的一個成員變量 target( 其實就是 Handler )的 dispatchMessage(msg) 方法,嗨,還真的又跟 Handler 扯上關(guān)系了,既然這里扯上關(guān)系了,而且還是一個分發(fā)消息的方法,可以大膽猜測就是讓Handler去處理這個消息的。
那么我們來看看這個方法:

  /**     * Handle system messages here.     * 如果Message中callback對象不為空(這是調(diào)用handler.post(Runnable)方法發(fā)送的消息),     * 就調(diào)用callback的run方法     * 否則如果創(chuàng)建Handler的時候如果設(shè)置了Callback就調(diào)用創(chuàng)建時候的傳入的     * 實現(xiàn)Handler.Callback接口的類的對象的handleMessage方法,看這就是回調(diào)方法被調(diào)用的地方。     * 再如果沒有mCallback對象,就調(diào)用自身的handleMessage方法,為了Handler的子類復(fù)寫了該方法的時候,方便調(diào)用,如,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);        }  
  }

到了這里,三步走已經(jīng)看完了,我想消息機(jī)制在我們心里已經(jīng)又清晰了一層,但是不用急,咱們前邊提的一個問題不是還沒有解決嗎,先把他解決掉吧,一起繼續(xù)來看源碼。

到了這里,三步走已經(jīng)看完了,我想消息機(jī)制在我們心里已經(jīng)又清晰了一層,但是不用急,咱們前邊提的一個問題不是還沒有解決嗎,先把他解決掉吧,一起繼續(xù)來看源碼。

咱們現(xiàn)在已知的是這樣的,在主線程創(chuàng)建的Handler發(fā)送了一個消息,發(fā)送消息的代碼運(yùn)行在其他線程,將代碼加入消息隊列也是在其他線程(加了線程同步鎖)。然后 handleMessage 發(fā)生在主線程,那么調(diào)用該方法的 dispatchMessage 方法也是運(yùn)行在主線程的,dispatchMessage 是在 Looper.loop 方法中調(diào)用的,也就是說loop方法也運(yùn)行在主線程,那么問題就明朗了,可是 loop 方法是誰調(diào)用的,在哪里調(diào)用的呢?當(dāng)然是系統(tǒng)啟動的時候創(chuàng)建主線程之后再主線程的 run 方法中調(diào)用了 Looper.prepare 和 Looper.loop 方法,但是這點我還沒看,留著以后再看吧。

通過上面的分析,我們是不是可以自己來試著建立這樣一個模型:

  1. 創(chuàng)建一個線程 A

  2. 在這個線程的 run 方法中調(diào)用 Looper.prepare 和 Looper.loop 方法使該線程阻塞,等待消息發(fā)過來,然后處理

  3. 在該線程中創(chuàng)建一個 Hanlder,用來處理 looper 發(fā)送來的待處理的消息

  4. 創(chuàng)建一些其他的線程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 看成主線程,在回調(diào)方法更新 UI,那這不就是 Android 系統(tǒng)中更新 UI 使用的套路嗎?不錯,事實本就如此,工具是工具,用它來更新 UI 是可以的,那你當(dāng)然也可以用來做一些其他的工作啊。

在這里,通過以上的分析,不難得到下面的這個整個消息機(jī)制運(yùn)行過程的時序圖:

image

ok 啦,源碼閱讀到此為止。其他的細(xì)節(jié),有興趣的可以再細(xì)細(xì)研究一下。

下面我們來對涉及到的類進(jìn)行一下總結(jié)。

Handler

handler在消息機(jī)制中,扮演的是消息的發(fā)送方和處理方(通過回調(diào)函數(shù))。消息在一個線程通過 Handler 發(fā)送到 MessageQueue 中。Looper 獲取到 Message 之后,根據(jù) Message 中保存的 handler 對象調(diào)用 handler 對象的 dispatch 方法,進(jìn)行消息的處理。

Looper

Looper 在這里扮演的是一個輪詢消息隊列的角色,以為不停地去問 MessageQueue 要消息,得到消息之后,根據(jù) Message 中保存的 handler 對象調(diào)用 handler 對象的 dispatch 方法,進(jìn)行消息的處理。

MessageQueue 和 Message

MessageQueue 實質(zhì)上是一個單鏈表的結(jié)構(gòu),里面以鏈表的形式保存著 Handler 發(fā)送過來的消息,當(dāng)有新消息發(fā)來時放在鏈表的尾部,Looper 要取消息的時候從鏈表的頭部取出消息返回給 Looper 處理。Message 對象中保存在要處理的信息,同時也持有消息發(fā)送方(Handler)的引用,Looper 在得到該 Message 的時候,可以從 Message 中拿到消息的發(fā)送方,調(diào)用發(fā)送方的回調(diào)方法將消息傳遞過去交給 Handler 進(jìn)行處理。

消息機(jī)制的應(yīng)用

在 Android 中有很多消息機(jī)制的應(yīng)用,如:
UI 的更新

HandlerThread

IntentService

UI 的更新

UI 線程持有一個 Looper 對象,Looper 對象的 loop 方法在 UI 線程中一直不停的進(jìn)行死循環(huán),直到有新的消息發(fā)來的時候,交給特定的組件進(jìn)行處理,當(dāng)然了這個處理也是在主線程運(yùn)行的(如我們設(shè)置的點擊事件也是等著被 UI 線程調(diào)用的),正是由于這個原因,我們不能在主線程處理耗時操作。如果我們一個 View 的點擊事件里做了大量耗時的操作,由于這個操作也在主線程中運(yùn)行,主線程必須等著這個操作操作結(jié)束才能去處理其他的消息,這個時候表現(xiàn)的就是系統(tǒng)卡頓甚至報 ANR 的錯誤。

HandlerThread

HandlerThread 繼承自 Thread,內(nèi)部保存一個 Looper 對象。
這是一個系統(tǒng)幫我們包裝好的 Thread,這個線程的 run 方法已經(jīng)調(diào)用了 Looper.prepare 和Looper.loop(即已經(jīng)綁定了一個Looper對象,并且可以開始輪詢消息),創(chuàng)建該對象之后可以通過獲得對象獲取到一個 Looper 對象,將 Looper 對象傳遞給 Handler,完成 Handler和 Looper 以及 MessageQueue 的綁定。最后再其他的線程中調(diào)用 Handler 的 sendMessage 或者 post(Runable)方法發(fā)送消息,handler 中的 callback.handleMessage 方法會在 HandlerThread 中運(yùn)行。即,將消息發(fā)送到了特定的線程(此處是 HandlerThread)處理。

IntentService

IntentService 繼承自 Service,運(yùn)行時優(yōu)先級更高,內(nèi)部使用了 HandlerThread 作為處理消息的線程。內(nèi)部有一個私有內(nèi)部類 ServiceHandler 繼承自 Handler,并且會創(chuàng)建一個ServiceHandler 對象。

使用 startService()方法啟動 IntentService 時,不會重新創(chuàng)建一個服務(wù),會調(diào)用 ServiceHandler 對象發(fā)送包含該 Intent 的 Message 對象,該對象通過 HandlerThread 處理后交給ServiceHandler 重寫的 handleMessage 方法進(jìn)行處理,處理的方式是調(diào)用 IntentService 的 onHandleIntent(Intent)方法,所以使用的方式就是創(chuàng)建一個繼承自 IntentService 類的子類,并重寫 onHandleIntent 方法,在該方法中處理 startService 時傳遞的 Intent。Intent 中包含有要交給 Service 處理的信息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容