Android之異步消息處理機制

異步消息處理.png

前言

在看過了網(wǎng)上那么多的Android的異步消息處理機制的文章之后傲隶,總是覺得不夠系統(tǒng),要么是copy來copy去的代碼窃页,要么是凌亂的結(jié)構(gòu)跺株,讓人看的云里霧里的复濒,也可能是沒有看懂并轉(zhuǎn)化成自己的東西。官方文檔也沒有詳細的去解釋其之間的關(guān)系乒省,只有的類注釋巧颈,然后我們就只能Read the Fucking Source Code !其實我們只要把官方文檔的解釋加上源碼就能理清這個機制的原理袖扛。

PS:在此強調(diào)一點就是砸泛,去學習android的東西,一定要看官方文檔蛆封,官方文檔才是最原汁原味的唇礁,網(wǎng)上的文章都是別人消化過的,不是你的理解惨篱,所以一定要去官網(wǎng)都挨個擼一遍垒迂,做到心中有數(shù),這樣以后查起文檔就有的放矢了妒蛇。

鏈接:GoogleAndroid 開發(fā)者官方網(wǎng)站

1. Android的線程

在說異步消息處理機制之前,一定要先去看一下這篇文章《Android的單線程》楷拳,只有看了這篇文章你才知道為什么會有異步消息處理機制绣夺。

2. 異步消息處理機制類

首先我們要了了解一下這幾個類:

  • Handler
  • Looper
  • Message
  • MessageQueue

在介紹之前,我想大家最一開始使用的都是從Handler欢揖,即使沒看過源碼陶耍,也知道大概的使用方式了,所以使用方法這里我不做贅述她混,下面一一介紹:

Handler

我按照官方文檔英文翻譯一下烈钞,如果大家英語好的話也可以去原文地址去看:

一個Handler允許你發(fā)送或處理Message和Runnable對象到線程的消息隊。每一個Handler實例都和一個單一的線程相關(guān)聯(lián)坤按,就是消息隊列所在的線程毯欣。當你創(chuàng)建一個新的Handler對象,從那時起臭脓,它就會綁定到線程所創(chuàng)建的消息隊列中酗钞,它會發(fā)送消息或者Runnable接口到消息隊列,當他們從消息隊列出來之后便依次執(zhí)行来累。

然后Handler有2種主要的使用:

  1. 執(zhí)行Message對象和在未來特定時刻執(zhí)行Runnable接口砚作;
  • sendMessage(Message)
  • sendEmptyMessage(int)
  • sendEmptyMessageDelayed(int,long)
  • sendEmptyMessageAtTime(int)
  • sendMessageDelayed(int,long)
  • sendMessageAtTime(int,long)
  • sendMessageAtFrontOfQueue(Message)
  1. 在一個不同的線程加入一個動作使之執(zhí)行而不是你自己去實現(xiàn)。
  • post(Runnable)
  • postAtTime(Runnable,long)
  • postAtTime(Runnable,object,long)
  • postDelayed(Runnable,long)
  • postAtFrontOfQueue(Runnable)

然后我們在Handler.handleMessage Callback中處理回傳回來的消息嘹锁。

Looper

Looper這個類大家肯定也很熟悉葫录,還記得你第一次使用Looper,拋出RuntimeException :"Only one Looper may be created per thread"嗎领猾?沒錯就是你在某一個已經(jīng)綁定了一個Looper對象的線程中調(diào)用Looper.prepare()時產(chǎn)生的米同。
下面看一段典型的代碼:

class LooperThread extends Thread {
  public Handler mHandler;

  public void run() {
      Looper.prepare();

      mHandler = new Handler() {
          public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };

      Looper.loop();
  }}

Looper的作用其實就是一個消息循環(huán)器骇扇,它不停的從消息池中取出消息,然后在當前線程中分發(fā)消息窍霞。但是使用的時候一定要注意匠题,要綁定到一個線程,先調(diào)用prepare再調(diào)用loop但金。

那么Looper.prepare()究竟做了什么韭山,我們看源碼:

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

首先我們看第一個函數(shù)prepare()的注釋:

注釋:Initialize the current thread as a looper.This gives you a chance to create handlers that then reference
this looper, before actually starting the loop. Be sure to call loop after calling this method, and end it by
calling quit

翻譯:初始化當前的線程作為一個looper對象。在真正的開始消息循環(huán)之前冷溃,給你機會去創(chuàng)建一個或多個handler來指向到這個looper對象钱磅。但是要確保當調(diào)用完這個方法之后一定要調(diào)用Loop方法,然后調(diào)用quit來結(jié)束它似枕。

然后我們再看第二個函數(shù)的具體實現(xiàn)盖淡,我們看到了有一個類ThreadLocal貌似我們從未見過,也沒有在實際項目當中使用過凿歼,但是如果有的同學如果看過《JAVA并發(fā)編程實戰(zhàn)》第三章節(jié)中的對象的共享褪迟,應(yīng)該會有印象。
不過沒關(guān)系答憔,我們還是用過類注釋來了解它的作用:

注釋:Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.

翻譯:實現(xiàn)了一個線程局部存儲味赃,就是每一個線程都有自己對應(yīng)的值。所有的線程共享ThreadLocal對象虐拓,但
是當訪問的時候心俗,每一個線程會看到不同的值。一個線程的改變不會影響其他線程蓉驹。這種實現(xiàn)還支持存儲null值城榛。

然后我們在看ThreadLocal所包含的方法:


發(fā)現(xiàn)和WeakReference的結(jié)構(gòu)很像!然后我們繼續(xù)看下源碼:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

定位到ThreadLocalMap,在看下源碼:

static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
       ...下面代碼省略
    }}

看到?jīng)]态兴,原來是ThreadLocal內(nèi)部使用弱引用WeakReference來存儲當前線程對象狠持,以ThreadLocalKey,value為Object瞻润。另外值得注意的一點是工坊,上面的Entry繼承自WeakReference,這樣的好處就是能夠及時回收Looper中創(chuàng)建的ThreadLocal對象敢订。

接下來是Looper.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 {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       //...中間省略代碼
        msg.recycleUnchecked();
    }
}

首先第一行,Looper.myLooper():

 public static Looper myLooper() {
    return sThreadLocal.get();
}

也就是從sThreadLocal靜態(tài)對象中取出在調(diào)用Looper.prepare函數(shù)時存放的Looper對象楚午。取出之后昭齐,調(diào)用Looper中的已經(jīng)初始化好的對象MessageQueue取出消息池中的消息Message進行操作,取出之后就要將消息進行分發(fā)了矾柜,此時調(diào)用Message.target.dispatchMessage(msg)阱驾,追溯到源碼:

public final class Message implements Parcelable {
       Handler target;
}

原來Message內(nèi)部持有一個Handler對象就谜,那么這個對象是在什么時候進行賦值的呢,繼續(xù)看Handler源碼:

public class Handler {
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      msg.target = this;
      if (mAsynchronous) {
          msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);
}}

msg.target = this 就是這句里覆!
我們搞明白了Handler在什么時候傳遞給Message之后丧荐,我們就明白消息是怎么回調(diào)的了,下面看dispatchMessage(Message msg)函數(shù)的實現(xiàn):

 public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

這里有if else判斷是如果回調(diào)函數(shù)不為空喧枷,則處理回調(diào)函數(shù)虹统,否則處理消息,很簡單隧甚,這里其實再次強調(diào)了Handler兩種主要用法车荔,sendMessage & post Runnable接口。

Message & MessageQueue

由于MessageQueue實際就是操作的Message對象戚扳,所以把他們結(jié)合在一起來說忧便。
剛才講到Handler有sendMessage(message)功能,看下源碼帽借,原來是HandlerMessage對象發(fā)送到MessageQueue中珠增,下面看個流程圖:


最終定位到了下面的代碼:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

queue.enqueueMessage(msg, uptimeMillis),繼續(xù)看MessageQueue里面的這個函數(shù)

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
   
   //此處省略若干代碼...
    Message p = mMessages;
    boolean needWake;
    synchronized (this) {
       Message prev;
         for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
    }
        if (needWake) {
            nativeWake(mPtr);
        }
    return true;
}

我們不需完全看懂完整的代碼砍艾,我們只看核心的部分切平,首先for循環(huán)部分是在不停的取出消息,然后取出消息調(diào)用了nativeWake函數(shù)辐董,查看源碼后發(fā)現(xiàn):

 private native static void nativeWake(long ptr);

是本地的實現(xiàn),我們也不需要care(當然如果你有興趣可以去查看源碼)禀综,只需要知道這個函數(shù)是喚醒消息隊列再次處理消息即可简烘,而整個的消息的循環(huán)是采用Sleep-Wakeup機制。也就是說當隊列中沒有消息的時候定枷,并不會不停的去取消息孤澎,而是進行休眠,休眠的意思大家想想自己的電腦就明白了欠窒,當再次點擊電源鍵時覆旭,能夠立刻喚醒進行工作,此處消息隊列的處理也是一樣岖妄,當消息隊列中有消息的時候調(diào)用nativeWake來喚醒Looper線程型将。

另外值得注意的是,Message內(nèi)部是采用消息池的機制荐虐,這樣在獲取的消息的時候回優(yōu)先從消息池中取出可用的消息對象七兜,如果沒有再進行初始化,這樣的好處就避免了不必要的內(nèi)存開銷福扬。另外腕铸,Message類也提供了上述代碼靜態(tài)工廠方法Message.obtain()惜犀。

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

所以大家在使用的時候最好使用Message.obtain()來構(gòu)造Message對象。

3. 異步消息處理機制

說到這里狠裹,可以用一張圖來總結(jié)整個的流程:

經(jīng)過以上的幾個關(guān)鍵的類的介紹虽界,原理也大概明白了,其中間過程發(fā)生了什么涛菠,在什么時候發(fā)生莉御,大家可以在源碼中仔細查看。最后總結(jié)一下:

  • Android應(yīng)用程序的消息處理機制由消息循環(huán)碗暗、消息發(fā)送和消息處理三個部分組成的颈将;
  • Android應(yīng)用程序的消息處理內(nèi)部是使用ThreadLocal來將一個Looper對象綁定到一個線程上;
  • Android應(yīng)用程序的主線程進入空閑等待狀態(tài)時不處理消息言疗,來了消息之后利用本地nativeWake函數(shù)來進行喚醒晴圾;
  • Android應(yīng)用程序的消息采用消息池機制來存取消息国觉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末最岗,一起剝皮案震驚了整個濱河市蕉陋,隨后出現(xiàn)的幾起案子壮韭,更是在濱河造成了極大的恐慌聚凹,老刑警劉巖逼龟,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臂外,死亡現(xiàn)場離奇詭異伴澄,居然都是意外死亡碰缔,警方通過查閱死者的電腦和手機账劲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來金抡,“玉大人瀑焦,你說我怎么就攤上這事」8危” “怎么了榛瓮?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巫击。 經(jīng)常有香客問我禀晓,道長,這世上最難降的妖魔是什么坝锰? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任粹懒,我火速辦了婚禮,結(jié)果婚禮上顷级,老公的妹妹穿的比我還像新娘崎淳。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布拣凹。 她就那樣靜靜地躺著森爽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嚣镜。 梳的紋絲不亂的頭發(fā)上爬迟,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音菊匿,去河邊找鬼付呕。 笑死,一個胖子當著我的面吹牛跌捆,可吹牛的內(nèi)容都是我干的徽职。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼佩厚,長吁一口氣:“原來是場噩夢啊……” “哼姆钉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抄瓦,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤潮瓶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钙姊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毯辅,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年煞额,在試婚紗的時候發(fā)現(xiàn)自己被綠了思恐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膊毁,死狀恐怖胀莹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚媒,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布涩僻,位于F島的核電站缭召,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逆日。R本人自食惡果不足惜嵌巷,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望室抽。 院中可真熱鬧搪哪,春花似錦、人聲如沸坪圾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漓概,卻和暖如春漾月,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胃珍。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工梁肿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人觅彰。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓吩蔑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親填抬。 傳聞我的和親對象是個殘疾皇子烛芬,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容