Android 消息機(jī)制源碼解析

異步消息處理

一、Looper

Looper負(fù)責(zé)的就是創(chuàng)建一個(gè)MessageQueue蛤迎,然后進(jìn)入一個(gè)無(wú)限循環(huán)體不斷從該MessageQueue中讀取消息,而消息的創(chuàng)建者就是一個(gè)或多個(gè)Handler 替裆。

在構(gòu)造方法中寸谜,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。

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

方法1. static void prepare() 靜態(tài)的方法,準(zhǔn)備
ThreadLocal中添加進(jìn)一個(gè)新的對(duì)象庭砍,sThreadLocal是一個(gè)ThreadLocal對(duì)象怠缸,可以在一個(gè)線程中存儲(chǔ)變量钳宪。prepare()中判斷了當(dāng)前線程的 Looper 對(duì)象是否為null,不為 null則拋出異常恨樟。這也就說(shuō)明了一個(gè)線程Looper.prepare()方法不能被調(diào)用兩次厌杜,同時(shí)也保證了一個(gè)線程中只有一個(gè)Looper實(shí)例

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();
    
    // 無(wú)限循環(huán)階段
    for (;;) {
        Message msg = queue.next(); // 可能阻塞夯尽,只有當(dāng) MessageQueue 調(diào)用 quit 方法時(shí)匙握,next 方法才會(huì)返回 null陈轿,否則都會(huì)阻塞等待
        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);
        }
        
        //使用調(diào)用 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() 中新建了一個(gè) Looper 對(duì)象蛔琅,Looper 的構(gòu)造方法中初始化了 MessageQueue(新建) 峻呛、
且為 Thread 對(duì)象賦值(當(dāng)前線程)。這兩個(gè)對(duì)象是 Looper 對(duì)象持有的寨躁,每個(gè) Looper 對(duì)象中都有一個(gè) MessageQueue 和 當(dāng)前線程對(duì)象职恳,

prepare() 中將新建的 Looper 對(duì)象添加到了 ThreadLocal<Looper> 對(duì)象中方面,這個(gè)對(duì)象是
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal<Looper> 在類的最初就會(huì)實(shí)例化葡幸,在存儲(chǔ) Looper 的時(shí)候會(huì)以當(dāng)前線程的對(duì)象作為一個(gè)參數(shù)蔚叨,在取Looper
時(shí)也會(huì)通過(guò)當(dāng)前線程來(lái)取

prepare() 方法中會(huì)判斷當(dāng)前線程是否已經(jīng)綁定了 Looper 對(duì)象,如果綁定了則會(huì)拋出
**throw new RuntimeException("Only one Looper may be created per thread"); **

在初始化 Handler 時(shí)也會(huì)根據(jù)當(dāng)前線程判斷是否綁定了 Looper 扬蕊,如果沒(méi)有綁定則會(huì)拋出
throw new IllegalStateException("No Looper; Looper.prepare() wasn't called on this thread.");
通過(guò)這個(gè)過(guò)程丹擎,保證了每個(gè)線程只能綁定一個(gè) Looper 蒂培,并且每個(gè) Looper 都有一個(gè) MessageQueue 對(duì)象

Looper.myLooper()獲取了當(dāng)前線程保存的 Looper 實(shí)例护戳,然后在又獲取了這個(gè) Looper 實(shí)例中保存的
MessageQueue(消息隊(duì)列),這樣就保證了 handler 的實(shí)例與我們 Looper 實(shí)例中 MessageQueue 關(guān)聯(lián)上了抗悍。

Looger.getMainLooper(); 獲取主線程的 Looper 對(duì)象缴渊,主線程會(huì)自動(dòng)調(diào)用 Looper.prepareMainLooper() 方法
完成 Looper 對(duì)象的綁定衔沼,并將該 Looper 對(duì)象賦值給一個(gè)靜態(tài)的 Looper 變量田柔,在調(diào)用 getMainLooper() 方法
時(shí)將該 Looper 對(duì)象返回硬爆。

Looper.myLooper() 方法是獲取當(dāng)前線程的 Looper 對(duì)象。

MessageQueue 的 next() 方法

MessageQueue 的 next() 方法中擎鸠,除了取出 Java 層需要處理的 Message 對(duì)象缀磕,還會(huì)調(diào)用 nativePollOnce() 方法處理 Native 層的消息循環(huán)

native 層的消息循環(huán)在 MessageQueue 構(gòu)造函數(shù)執(zhí)行時(shí)啟動(dòng),其構(gòu)造函數(shù)會(huì)在 Native 層創(chuàng)建 Native.Looper 和 NativeMessageQueue,通過(guò) Linux 的管道和 epoll 機(jī)制來(lái)實(shí)現(xiàn)

Linux 的管道可以看做是一個(gè)文件劣光,并且包含讀和寫端袜蚕,epoll 機(jī)制是如果一個(gè)線程讀該文件時(shí)該文件沒(méi)有內(nèi)容,則該線程會(huì)進(jìn)入等待绢涡,當(dāng)另一個(gè)線程往該文件寫內(nèi)容后會(huì)喚醒正在讀端等待的線程牲剃。這個(gè)等待和喚醒的機(jī)制就使用 Linux 的 epoll 機(jī)制

當(dāng) Java 層的 MessageQueue 獲取消息的時(shí)候,也會(huì)調(diào)用 natice 層的 MessageQueue 從管道中讀消息并且處理消息

創(chuàng)建 Native 層循環(huán)模型的原因:
Android 是支持純 Native 開發(fā)的凿傅,并且 Android 系統(tǒng)的核心組件也是運(yùn)行在 native 層的缠犀,各組件直接需要通信,所以建立兩套循環(huán)機(jī)制就很重要

Looper主要作用:

  1. 與當(dāng)前線程綁定聪舒,保證一個(gè)線程只會(huì)有一個(gè) Looper 實(shí)例辨液,同時(shí)一個(gè) Looper 實(shí)例也只有一個(gè) MessageQueue

  2. loop() 方法,不斷從 MessageQueue 中去取消息箱残,交給消息的 target 屬性的 dispatchMessage 去處理

好了滔迈,我們的異步消息處理線程已經(jīng)有了消息隊(duì)列(MessageQueue),也有了 Looper 在無(wú)限循環(huán)體中取出消息被辑,
現(xiàn)在缺的就是發(fā)送消息的對(duì)象了燎悍,發(fā)送消息的對(duì)象是 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 對(duì)象中有一個(gè) Looper 對(duì)象和一個(gè) MessageQueue 對(duì)象盼理,這兩個(gè)屬性都是final 的在創(chuàng)建一個(gè) Handler 對(duì)象的時(shí)候谈山,如果當(dāng)前線程沒(méi)有綁定 Looper 對(duì)象,則會(huì)拋出異常 throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");所以在一個(gè)線程中創(chuàng)建 Handler 的時(shí)候當(dāng)前線程必須調(diào)用 Looper.prepare(); 方法

然后看我們最常用的sendMessage方法,方法最后調(diào)用了sendMessageAtTime榜揖,在此方法內(nèi)部有直接
獲取 MessageQueue 然后調(diào)用了 enqueueMessage 方法勾哩,我們?cè)賮?lái)看看此方法: enqueue : 入隊(duì)

    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方法會(huì)取出每個(gè)msg然后交給 msg.target.dispatchMessage(msg) 去處理消息举哟,也就是把當(dāng)前的handler作為msg的 target 屬性思劳。

最終會(huì)調(diào)用 queue 的 enqueueMessage 的方法,也就是說(shuō) handler 發(fā)出的消息妨猩,最終會(huì)保存到消息隊(duì)列中去潜叛。enqueueMessage 方法中使用 synchronize 鎖 MessageQueue 對(duì)象,保證不同線程插入數(shù)據(jù)時(shí)的同步問(wèn)題壶硅。

Looper 會(huì)調(diào)用 prepare() 和 loop() 方法威兜,在當(dāng)前執(zhí)行的線程中保存一個(gè) Looper 實(shí)例,這個(gè)實(shí)例會(huì)保存一個(gè) MessageQueue 對(duì)象庐椒,然后當(dāng)前線程進(jìn)入一個(gè)無(wú)限循環(huán)中去椒舵,不斷從 MessageQueue 中讀取 Handler 發(fā)來(lái)的消息。然后再回調(diào)創(chuàng)建這個(gè)消息的 handler 中的 dispathMessage 方法

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

這個(gè)流程已經(jīng)解釋完畢约谈,讓我們首先總結(jié)一下

  1. 首先Looper.prepare()在本線程中保存一個(gè)Looper實(shí)例笔宿,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象; 因?yàn)長(zhǎng)ooper.prepare()在一個(gè)線程中只能調(diào)用一次棱诱,所以MessageQueue在一個(gè)線程中只會(huì)存在一個(gè)泼橘。

  2. Looper.loop()會(huì)讓當(dāng)前線程進(jìn)入一個(gè)無(wú)限循環(huán),不斷從 MessageQueue 的實(shí)例中讀取消息迈勋,然后回調(diào) msg.target.dispatchMessage(msg) 方法炬灭。

  3. Handler 的構(gòu)造方法,會(huì)首先得到當(dāng)前線程中保存的 Looper 實(shí)例靡菇,進(jìn)而與 Looper 實(shí)例中的 MessageQueue 相關(guān)聯(lián)重归。

  4. Handler的 sendMessage 方法米愿,會(huì)給 msg 的 target 賦值為 handler 自身,然后加入 MessageQueue 中提前。

  5. 在構(gòu)造 Handler 實(shí)例時(shí)吗货,我們會(huì)重寫 handleMessage 方法,也就是 msg.target.dispatchMessage(msg) 最終調(diào)用的方法狈网。

好了宙搬,總結(jié)完成,大家可能還會(huì)問(wèn)拓哺,那么在Activity中勇垛,我們并沒(méi)有顯示的調(diào)用 Looper.prepare() 和 Looper.loop() 方法,
為啥 Handler 可以成功創(chuàng)建呢士鸥,這是因?yàn)樵?ActivityTherad 的 main() 方法中闲孤,已經(jīng)為主線程調(diào)用了Looper.prepareMainLooper() 和 Looper.loop() 方法。

關(guān)于Handler處理消息的方式

  1. 創(chuàng)建 Message 對(duì)象時(shí)烤礁,指定 Runnable 對(duì)象讼积,
    例如 Message.obtain(Handler handler, Runnable callback)

  2. 創(chuàng)建 Handler.Callback 實(shí)現(xiàn)類對(duì)象,并作為創(chuàng)建 Handler 的構(gòu)造方法的參數(shù)

  3. 自定義類繼承自 Handler脚仔,并重寫 handlerMessage() 方法

以上3種方法的執(zhí)行順序:如果存在方式1勤众,則由方式1直接處理;如果存在方式2鲤脏,則方式2處理消息们颜,且,如果方式2返回true猎醇,則處理完畢窥突,否則,方式3也會(huì)處理消息硫嘶。

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

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

然后 run 方法中可以寫更新UI的代碼阻问,其實(shí)這個(gè) Runnable 并沒(méi)有創(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 中,得到了一個(gè) Message 對(duì)象曹鸠,然后將我們創(chuàng)建的 Runable 對(duì)象作為 callback 屬性,賦值給了此 message.

  • 注:產(chǎn)生一個(gè)Message對(duì)象斥铺,可以new 彻桃,也可以使用Message.obtain()方法;兩者都可以晾蜘,但是更建議使用obtain方法邻眷,
    因?yàn)镸essage內(nèi)部維護(hù)了一個(gè)Message池用于Message的復(fù)用眠屎,避免使用new 重新分配內(nèi)存。
    最終和handler.sendMessage一樣肆饶,調(diào)用了sendMessageAtTime改衩,然后調(diào)用了enqueueMessage方法,給msg.target賦值為 handler驯镊,最終加入MessagQueue.

  • msg的 callback 和target都有值葫督,那么會(huì)執(zhí)行哪個(gè)呢?
    如果 callback 不為null板惑,則執(zhí)行callback回調(diào)橄镜,也就是我們的Runnable對(duì)象

  • Handler 對(duì)象創(chuàng)建之后可以在任何線程中發(fā)消息,最終消息的處理都將回到 創(chuàng)建 Handler 時(shí)使用的 Looper 所在的線程處理冯乘。所以在子線程中可以使用主線程中創(chuàng)建的 Handler 對(duì)象發(fā)消息洽胶,在主線程中處理消息。

每個(gè)線程中可以創(chuàng)建多個(gè) Handler 裆馒,處理消息時(shí)就調(diào)用 msg.targe 也就是 Handler 來(lái)處理姊氓,Looper 將消息發(fā)送給發(fā)送消息時(shí)使用的 Handler 對(duì)象來(lái)處理。子線程不可以直接創(chuàng)建 Handler喷好, 必須在子線程先調(diào)用 Looper.prepare();線程中調(diào)用 Looper.loop(); 方法后才會(huì)開啟輪循

一個(gè)線程中只能有一個(gè) Looper 翔横,也就是只能有一個(gè) MessageQueue ,可以有多個(gè) Handler绒窑。一個(gè) Handler 可以在不同的線程中發(fā)送消息棕孙,但是消息的處理都是在創(chuàng)建 Handler 時(shí)使用的 Looper 所在的線程

總結(jié)

每個(gè)線程在綁定 Looper 之后都有一個(gè) MessageQueue ,MessageQueue 中存著需要處理的消息 Message些膨,Message 中存著處理消息的 Handler蟀俊,Handler 在發(fā)消息時(shí)會(huì)將消息發(fā)送到自己所在線程的 MessageQueue,Looper 的 loop 方法就是開啟 MessageQueue 的遍歷订雾。這樣肢预,Handler 所發(fā)的消息處理就會(huì)在 Handler 所在的線程中。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洼哎,一起剝皮案震驚了整個(gè)濱河市烫映,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌噩峦,老刑警劉巖锭沟,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異识补,居然都是意外死亡族淮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祝辣,“玉大人贴妻,你說(shuō)我怎么就攤上這事◎保” “怎么了名惩?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孕荠。 經(jīng)常有香客問(wèn)我娩鹉,道長(zhǎng),這世上最難降的妖魔是什么岛琼? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任底循,我火速辦了婚禮,結(jié)果婚禮上槐瑞,老公的妹妹穿的比我還像新娘熙涤。我一直安慰自己,他們只是感情好困檩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布祠挫。 她就那樣靜靜地躺著,像睡著了一般悼沿。 火紅的嫁衣襯著肌膚如雪等舔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天糟趾,我揣著相機(jī)與錄音慌植,去河邊找鬼。 笑死义郑,一個(gè)胖子當(dāng)著我的面吹牛蝶柿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播非驮,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼交汤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了劫笙?” 一聲冷哼從身側(cè)響起芙扎,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎填大,沒(méi)想到半個(gè)月后戒洼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡允华,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年施逾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敷矫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汉额,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出榨汤,到底是詐尸還是另有隱情蠕搜,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布收壕,位于F島的核電站妓灌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蜜宪。R本人自食惡果不足惜虫埂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圃验。 院中可真熱鬧掉伏,春花似錦、人聲如沸澳窑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)摊聋。三九已至鸡捐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麻裁,已是汗流浹背箍镜。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煎源,地道東北人色迂。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像薪夕,于是被迫代替她去往敵國(guó)和親脚草。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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