Handler,Looper,MessageQueue機制

來源:互聯網
上載者:User
線程與線程間的互動協作

線程與線程之間雖然共用記憶體空間,也即可以訪問進程的堆空間,但是線程有自己的棧,運行在一個線程中的方法調用全部都是線上程自己的調用棧中。通俗來講西線程就是一個run()方法及其內部所調用的方法。這裡面的所有方法調用都是獨立於其他線程的,由於方法調用的關係,一個方法調用另外的方法,那麼另外的方法也發生在調用者的線程裡。所以,線程是時序上的概念,本質上是一列方法調用。

那麼線程之間要想協作,或者想改變某個方法所在的線程(為了不阻塞自己線程),就只能是向另外一個線程發送一個訊息,然後return;另外線程收到訊息後就去執行某些操作。如果是簡單的操作可以用一個變數來標識,比如A線程主需要B線程做某些事時,可以把某個對象obj設定值,B則當看到obj != null時就去做事,這種線程互動協作在《Java編程思想》中有大量樣本。


Android中的ITC-Inter Thread Communication

注意:當然Handler也可以用做一個線程內部的訊息迴圈,不必非與另外的線程通訊,但這裡重點討論的是線程與線程之間的事情。

Android當中做了一個特別的限制就是非主線程不能操作UI元素,而一個應用程式是不可能不創衍生線程的,這樣一來主線程與衍生線程之間就必須進行通訊。由於這種通訊很頻繁,所以不可能全用變數來標識,程式將變得十分混亂。這個時候訊息佇列就變得有十分有必要,也就是在每個線程中建立一個訊息佇列。當A需要B時,A向B發一個訊息,此過程實質為把訊息加入到B的訊息佇列中,A就此return,B並不專門等待某個訊息,而是迴圈的查看其訊息佇列,看到有訊息後就去執行。

整套ITC的基本思想是:定義一個訊息對象,把需要的資料放入其中,把訊息的處理的方法也定義好作為回調放到訊息中,然後把這個訊息發送另一個線程上;另外的線程在迴圈處理其隊列裡的訊息,看到訊息時就對訊息調用附在其上的回調來處理訊息。這樣一來可以看出,這僅僅是改變了處理訊息的執行時序:正常是當場處理,這種則是封裝成一個訊息丟給另外的線程,在某個不確定的時間被執行;另外的線程也僅提供CPU時序,對於訊息是什麼和訊息如何處理它完全不干預。簡言之就是把一個方法放到另外一個線程裡去調用,進而這個方法的調用者的調用棧(call stack)結束,這個方法的調用棧轉移到了另外的線程中。

那麼這個機制改變的到底是什麼呢?從上面看它僅是讓一個方法(訊息的處理)安排到了另外一個線程裡去做(非同步處理),不是立刻馬上同步的做,它改變的是CPU的執行時序(execution sequence)。

那麼訊息佇列存放在哪裡呢?不能放在堆空間裡(直接new MessageQueue()),這樣的話對象的引用容易丟失,針對線程來講也不易維護。Java支援線程的本機存放區ThreadLocal,通過ThreadLocal對象可以把對象放到線程的空間上,每個線程都有了屬於自己的對象。因此,可以為每個需要通訊的線程建立一個訊息佇列並放到其本機存放區中。

基於這個模型還可以擴充,比如給訊息定義優先順序等。


MessageQueue

以隊列的方式來儲存訊息,主要是二個操作一個是入列enqueueMessage,一個是出列next(),需要保證的是安全執行緒,因為入列通常是另外的線程在調用。

MessageQueue是一個十分接近底層的機制,所以不方便開發人員直接使用,要想使用此MessageQueue必須做二個方面工作,一個是目標線程端:建立,與線程關聯,運轉起來;另一個就是隊列線程的用戶端:建立訊息,定義回調處理,發送訊息到隊列。Looper和Handler就是對MessageQueue的封裝:Looper是給目標線程用的:用途是建立MessageQueue,將MessageQueue與線程關聯起來,並讓MessageQueue運轉起來,且Looper有保護機制,讓一個線程僅能建立一個MessageQueue對象;而Handler則是給隊列用戶端用的:用來建立訊息,定義回調和發送訊息。

因為Looper對象封裝了目標隊列線程及其隊列,所以對隊列線程的用戶端來講,Looper對象就代表著一個擁有MessageQueue的線程,和這個線程的MessageQueue。也即當你構建Handler對象時用的是Looper對象,而當你檢驗某個線程是否是預期線程時也用Looper對象。


Looper內幕

Looper的任務是建立訊息佇列MessageQueue,放到線程的ThreadLocal中(與線程關聯),並且讓MessageQueue運轉起來,處於Ready的狀態,並要提供供介面以停止訊息迴圈。它主要有四個介面:

  • public static void Looper.prepare()

    這個方法是為線程建立一個Looper對象和MessageQueue對象,並把Looper對象通過ThreadLocal放到線程空間裡去。需要注意的是這個方法每個線程只能調用一次,通常的做法是線上程run()方法的第一句,但只要保證在loop()前面即可。

  • public static void Looper.loop()

    這個方法要在prepare()這後調用,是讓線程的MessageQueue運轉起來,一旦調用此方法,線程便會無限迴圈下去(while (true){...}),無Message時休眠,有Message入隊時喚醒處理,直到quit()調用為止。它的簡化實現就是:

    loop() {     while (true) {        Message msg = mQueue.next();        if msg is a quit message, then           return;        msg.processMessage(msg)     }  }  

  • public void Looper.quit()

    讓線程結束MessageQueue的迴圈,終止迴圈,run()方法會結束,線程也會停止,因此它是對象的方法,意即終止某個Looper對象。一定要記得在不需要線程的時候調用此方法,否則線程是不會終止退出的,進程也就會一直運行,佔用著資源。如果有大量的線程未退出,進程最終會崩掉。

  • public static Looper Looper.myLooper()

    這個是獲得調用者所線上程所擁有的Looper對象的方法。

還有二個介面是與主線程有關的:

  • 一個是專門為主線程準備的

    public static void Looper.prepareMainLooper();

    這個方法只給主線程初始化Looper用的,它僅在ActivityThread.main()方法中調用,其他地方或其他線程不可以調用,如果在主線程中調用會有異常拋出,因為一個線程只能建立一個Looper對象。但是如在其他線程中調用此方法,會改變mainLooper,接下來的getMainLooper就會返回它而非真正的主線程的Looper對象,這不會有異常拋出,也不會有明顯的錯誤,但是程式將不能正常工作,因為原本設計在主線程中啟動並執行方法將轉到這個線程裡面,會產生很詭異的Bug。這裡Looper.prepareMainThread()的方法中應該加上判斷:

    public void prepareMainLooper() {      if (getMainLooper() != null) {           throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks");       }       //...  }  

    以防止其他線程非法調用,光靠文檔約束力遠不夠。

  • 另外一個就是擷取主線程Looper的介面:

    public static Looper Looper.getMainLooper()

    這個主要用在檢查線程合法性,也即保證某些方法只能在主線程裡面調用。但這並不保險,如上面所說,如果一個衍生線程調用了prepareMainLooper()就會把真正的mMainLooper改變,此衍生線程就可以通過上述檢測,導致getMainLooper() != myLooper()的檢測變得不靠譜了。所以ViewRoot的方法是用Thread來檢測:mThread != Thread.currentThread();其mThread是在系統建立ViewRoot時通過Thread.currentThread()獲得的,這樣的方法來檢測是否是主線程更加靠譜一些,因為它沒有依賴外部而是相信自己儲存的Thread的引用。

Message對象

訊息Message是僅是一個資料結構,是資訊的載體,它與隊列機制是無關的,封裝著要執行的動作和執行動作的必要資訊,what, arg1, arg2, obj可以用來傳送資料;而Message的回調則必須通過Handler來定義,為什麼呢?因為Message僅是一個載體,它不能自己跑到目標MessageQueue上面去,它必須由Handler來操作,把Message放到目標隊列上去,既然它需要Handler來統一的放到MessageQueue上,也可以讓Handler來統一定義處理訊息的回調。需要注意的是同一個Message對象只能使用一次,因為在處理完訊息後會把訊息回收掉,所以Message對象僅能使用一次,嘗試再次使用時MessageQueue會拋出異常。


Handler對象

它被設計出來目的就是方便隊列線程用戶端的操作,隱藏直接操作MessageQueue的複雜性。Handler最主要的作用是把訊息發送到與此Handler綁定的線程的MessageQueue上,因此在構建Handler的時候必須指定一個Looper對象,如果不指定則通過Looper擷取調用者線程的Looper對象。它有很多重載的send*Message和post方法,可以以多種方式來向目標隊列發送訊息,廷時發送,或者放到隊列的頭部等等;

它還有二個作用,一個是建立Message對象通過obtain*系統方法,另一個就是定義處理Message的回調mCallback和handleMessage,由於一個Handler可能不止發送一個訊息,而這些訊息通常共用此Handler的回調方法,所以在handleMessage或者mCallback中就要區分這些不同的訊息,通常是以Message.what來區分,當然也可以用其他欄位,只要能區別出不同的Message即可。需要指明的是,訊息佇列中的訊息本身是獨立的,互不相干的,訊息的命名空間是在Handler對象之中的,因為Message是由Handler發送和處理的,所以只有同一個Handler對象需要區別不同的Message對象。廣義上講,如果一個訊息自己定義有處理方法,那麼所有的訊息都是互不相干的,當從隊列取出訊息時就調用其上的回調方法,不會有命名上的衝突,但由Handler發出的訊息的回調處理方法都是Handler.handleMessage或Handler.mCallback,所以就會有影響了,但影響的範圍也令局限在同一個Handler對象。

因為Handler的作用是向目標隊列發送訊息和定義處理訊息的回調(處理訊息),它僅是依賴於線程的MessageQueue,所以Handler可以有任意多個,都綁定到某個MessageQueue上,它並沒有個數限制。而MessageQueue是有個數限制的,每個線程只能有一個,MessageQueue通過Looper建立,Looper儲存線上程的ThreadLocal中,Looper裡作了限制,每個線程只能建立一個。但是Handler無此限制,Handler的建立通過其建構函式,只需要提供一個Looper對象即可,所以它沒有個數限制。


轉自:http://blog.csdn.net/hitlion2008/article/details/8194510

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.