消息機制

異步消息處理

一辙培、Looper

Looper負責的就是創(chuàng)建一個MessageQueue,然后進入一個無限循環(huán)體不斷從該MessageQueue中讀取消息,而消息的創(chuàng)建者就是一個或多個Handler 杨刨。

在構造方法中,創(chuàng)建了一個MessageQueue(消息隊列)。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

方法1. static void prepare() 靜態(tài)的方法氏仗,準備
ThreadLocal中添加進一個新的對象,sThreadLocal是一個ThreadLocal對象夺鲜,可以在一個線程中存儲變量皆尔。prepare()中判斷了當前線程的 Looper 對象是否為null,不為 null則拋出異常币励。這也就說明了一個線程Looper.prepare()方法不能被調用兩次慷蠕,同時也保證了一個線程中只有一個Looper實例

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}

方法2. loop() 方法

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

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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    
    // 無限循環(huán)階段
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        
        //使用調用 msg.target.dispatchMessage(msg);把消息交給msg的target的dispatchMessage方法去處理。
        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycle();
    }
}

Looper 的靜態(tài)方法 prepare() 中新建了一個 Looper 對象食呻,Looper 的構造方法中初始化了 MessageQueue(新建) 和 Thread (當前線程)流炕。這兩個對象是 Looper 對象持有的,每個 Looper 對象中都有一個 MessageQueue 和 當前線程對象

prepare() 中將新建的 Looper 對象添加到了 ThreadLocal<Looper> 對象中仅胞,這個對象是
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal<Looper> 在類的最初就會實例化每辟,在存儲 Looper 的時候會以當前線程的對象作為一個參數(shù),在取Looper
時也會通過當前線程來取

prepare() 方法中會判斷當前線程是否已經綁定了 Looper 對象干旧,如果綁定了則會拋出
throw new RuntimeException("Only one Looper may be created per thread");
并且在取 Looper 時也會根據(jù)當前線程判斷是否綁定了 Looper 渠欺,如果沒有綁定則會拋出
throw new IllegalStateException("No Looper; Looper.prepare() wasn't called on this thread.");
通過這個過程,保證了每個線程只能綁定一個 Looper 椎眯,并且每個 Looper 都有一個 MessageQueue 對象

Looper.myLooper()獲取了當前線程保存的 Looper 實例挠将,然后在又獲取了這個 Looper 實例中保存的
MessageQueue(消息隊列)胳岂,這樣就保證了 handler 的實例與我們 Looper 實例中 MessageQueue 關聯(lián)上了。

Looger.getMainLooper(); 獲取主線程的 Looper 對象舔稀,主線程會自動調用 Looper.prepareMainLooper() 方法
完成 Looper 對象的綁定旦万,并將該 Looper 對象賦值給一個靜態(tài)的 Looper 變量,在調用 getMainLooper() 方法
時將該 Looper 對象返回镶蹋。
Looper.myLooper() 方法是獲取當前線程的 Looper 對象成艘。

Looper主要作用:

  1. 與當前線程綁定,保證一個線程只會有一個Looper實例贺归,同時一個Looper實例也只有一個MessageQueue淆两。
  2. loop()方法,不斷從MessageQueue中去取消息拂酣,交給消息的target屬性的dispatchMessage去處理秋冰。
    好了,我們的異步消息處理線程已經有了消息隊列(MessageQueue)婶熬,Looper 負責輪詢消息隊列剑勾,下面分析發(fā)送消息的 Handler

二、Handler


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());
        }
    }
    
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Handler 對象中有一個 Looper 對象和一個 MessageQueue 對象赵颅,這兩個屬性都是final 的
在創(chuàng)建一個 Handler 對象的時候虽另,如果當前線程沒有綁定 Looper 對象,則會拋出異常
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
所以在一個線程中創(chuàng)建 Handler 的時候當前線程必須調用 Looper.prepare(); 方法

然后看我們最常用的sendMessage方法,方法最后調用了sendMessageAtTime饺谬,在此方法內部有直接
獲取 MessageQueue 然后調用了 enqueueMessage 方法捂刺,我們再來看看此方法: enqueue : 入隊

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

enqueueMessage 中首先為 meg.target 賦值為this,【Looper的loop方法會取出每個msg然后交給
msg.target.dispatchMessage(msg) 去處理消息】募寨,也就是把當前的handler作為msg的 target 屬性族展。最終
會調用 queue 的 enqueueMessage 的方法,也就是說 handler 發(fā)出的消息拔鹰,最終會保存到消息隊列中去仪缸。enqueueMessage 方法中使用 synchronize 鎖 MessageQueue 對象,保證不同線程插入數(shù)據(jù)時的同步問題列肢。

Looper會調用prepare()和loop()方法恰画,在當前執(zhí)行的線程中保存一個Looper實例,這個實例會保存一個
MessageQueue對象例书,然后當前線程進入一個無限循環(huán)中去锣尉,不斷從MessageQueue中讀取Handler發(fā)來的消息。
然后再回調創(chuàng)建這個消息的handler中的dispathMessage方法决采,下面我們趕快去看一看這個方法:

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg); // 使用創(chuàng)建 Message 時構造方法中的 Runnable 處理消息
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  // 使用創(chuàng)建 Handler 時構造方法中的 Handler.Callback 處理消息
                return;  
            }  
        }  
        handleMessage(msg);  // 重寫的處理消息方法處理
    }  
} 

這個流程已經解釋完畢自沧,讓我們首先總結一下

  1. 首先Looper.prepare()在本線程中保存一個Looper實例,然后該實例中保存一個MessageQueue對象;
    因為Looper.prepare()在一個線程中只能調用一次拇厢,所以MessageQueue在一個線程中只會存在一個爱谁。
  2. Looper.loop()會讓當前線程進入一個無限循環(huán),不端從MessageQueue的實例中讀取消息孝偎,然后回調
    msg.target.dispatchMessage(msg)方法访敌。
  3. Handler的構造方法,會首先得到當前線程中保存的Looper實例衣盾,進而與Looper實例中的MessageQueue想關聯(lián)寺旺。
  4. Handler的sendMessage方法,會給msg的target賦值為handler自身势决,然后加入MessageQueue中阻塑。
  5. 在構造Handler實例時,我們會重寫handleMessage方法果复,也就是msg.target.dispatchMessage(msg)最終調用的方法陈莽。

好了,總結完成虽抄,大家可能還會問走搁,那么在Activity中,我們并沒有顯示的調用Looper.prepare()和Looper.loop()方法迈窟,
為啥Handler可以成功創(chuàng)建呢私植,這是因為在Activity的啟動代碼中,已經在當前UI線程調用了Looper.prepareMainLooper()和Looper.loop()方法菠隆。

關于Handler處理消息的方式

  1. 創(chuàng)建Message對象時兵琳,指定Runnable對象,
    例如Message.obtain(Handler handler, Runnable callback)
  2. 創(chuàng)建Handler.Callback實現(xiàn)類對象骇径,并作為創(chuàng)建Handler的構造方法的參數(shù)
  3. 自定義類繼承自Handler,并重寫handlerMessage()方法
    以上3種方法的執(zhí)行順序:如果存在方式1者春,則由方式1直接處理破衔;如果存在方式2,
    則方式2處理消息钱烟,且晰筛,如果方式2返回true,則處理完畢拴袭,否則读第,方式3也會處理消息。

Handler.post(); // 等多久執(zhí)行... run() 方法中的代碼

mHandler.post(new Runnable() {  
    @Override      
    public void run(){  
        og.e("TAG", Thread.currentThread().getName());  
        mTxt.setText("test");  
    }  
});  

然后run方法中可以寫更新UI的代碼拥刻,其實這個Runnable并沒有創(chuàng)建什么線程怜瞒,而是發(fā)送了一條消息,下面看源碼:

public final boolean post(Runnable r)  {  
  return  sendMessageDelayed(getPostMessage(r), 0);  
}  

private static Message getPostMessage(Runnable r) {  
  Message m = Message.obtain();  
  m.callback = r;  
  return m;  
}   

可以看到,在getPostMessage中吴汪,得到了一個Message對象惠窄,然后將我們創(chuàng)建的Runable對象作為callback屬性,賦值給了此 message.

  • 注:產生一個Message對象漾橙,可以new 杆融,也可以使用Message.obtain()方法;兩者都可以霜运,但是更建議使用obtain方法脾歇,
    因為Message內部維護了一個Message池用于Message的復用,避免使用new 重新分配內存淘捡。
    最終和handler.sendMessage一樣藕各,調用了sendMessageAtTime,然后調用了enqueueMessage方法案淋,給msg.target賦值為 handler座韵,最終加入MessagQueue.

  • msg的 callback 和target都有值,那么會執(zhí)行哪個呢踢京?
    如果 callback 不為null誉碴,則執(zhí)行callback回調,也就是我們的Runnable對象

  • Handler 對象創(chuàng)建之后可以在任何線程中發(fā)消息瓣距,最終消息的處理都將回到 創(chuàng)建 Handler 時使用的 Looper 所在的線程處理黔帕。所以在子線程中可以使用主線程中創(chuàng)建的 Handler 對象發(fā)消息,在主線程中處理消息蹈丸。
  • Activity 中可以創(chuàng)建多個 Handler 成黄,處理消息時就調用 msg.targe 也就是 Handler 來處理.Looper 將消息發(fā)送給發(fā)送消息時使用的 Handler 對象。
    子線程不可以直接創(chuàng)建 Handler逻杖, 必須在子線程先調用 Looper.prepare();線程中調用 Looper.loop(); 方法后才會開啟輪循

  • 一個線程中只能有一個 Looper 奋岁,也就是只能有一個 MessageQueue ,可以有多個 Handler荸百。一個 Handler 可以在不同的線程中發(fā)送消息闻伶,但是消息的處理都是在創(chuàng)建 Handler 時使用的 Looper 所在的線程。

參考:http://blog.csdn.net/lmj623565791/article/details/47079737

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末够话,一起剝皮案震驚了整個濱河市蓝翰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌女嘲,老刑警劉巖驻龟,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔗牡,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機蛇耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事谷徙。” “怎么了驯绎?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵完慧,是天一觀的道長。 經常有香客問我剩失,道長屈尼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任拴孤,我火速辦了婚禮脾歧,結果婚禮上,老公的妹妹穿的比我還像新娘演熟。我一直安慰自己鞭执,他們只是感情好,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布芒粹。 她就那樣靜靜地躺著兄纺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪化漆。 梳的紋絲不亂的頭發(fā)上估脆,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機與錄音座云,去河邊找鬼疙赠。 笑死,一個胖子當著我的面吹牛朦拖,可吹牛的內容都是我干的圃阳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼璧帝,長吁一口氣:“原來是場噩夢啊……” “哼限佩!你這毒婦竟也來了?” 一聲冷哼從身側響起裸弦,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎作喘,沒想到半個月后理疙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡泞坦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年窖贤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡赃梧,死狀恐怖滤蝠,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情授嘀,我是刑警寧澤物咳,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蹄皱,受9級特大地震影響览闰,放射性物質發(fā)生泄漏。R本人自食惡果不足惜巷折,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一压鉴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锻拘,春花似錦油吭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芯丧,卻和暖如春芍阎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缨恒。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工谴咸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骗露。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓岭佳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親萧锉。 傳聞我的和親對象是個殘疾皇子珊随,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內容