Android源碼學習-----Handler機制

來源:互聯網
上載者:User

標籤:原來   and   指正   local   AC   指定   並且   time()   標記   

Handler

1.為什麼要使用Handler

在Android4.0之後,google公司為從系統使用及使用者體驗方面考慮,如果做一些比較耗時的操作,就不允許直接在主線程中進行,而是要通過handler發送Message對象的方法來修改主線程的UI介面

2.Handler原理簡介

在所有的UI操作介面中,都在執行一個死迴圈(Looper)在不斷接收和監聽使用者發出的指令,一但接受到指令,就立即執行。
  當子線程需要修改UI介面時,調用Handler的sendMessage()方法,向主線程發送訊息(Message)
  Handler把訊息放到死迴圈Looper的訊息佇列中(MessageQueue)
  Looper中還有一個死迴圈的方法,它會不停的從訊息佇列中取出訊息,並將訊息發送給handler
  當handler接收到這個訊息後就會執行修改UI介面的程式 

3 Handler、Message、MessageQuene、Looper何時建立?

3.1 Message的建立

由於在子線程中無法再直接修改主介面,那麼就需要通過建立Handler發送Message的方法來間接修改主介面。所以Message是由我們手動在需要修改主介面的時候建立的。

Message一般有三種建立方式,

(1) 使用new關鍵字建立

  Message msg = new Message();

(2) 使用obtain()方法建立

 Message msg = Message.obtain();

(3) 使用Handler對象通過obtainMessage()方法建立,此方法證明handler是比Message先建立的

Message msg = handler.obtainMessage();

     通過觀察Handler對象中的obtainMessage()的源碼發現,這個方法內部調用的其實就是Message.obtain(Handler h)方法來建立的Message對象。  參數的this表示調用這個方法的Handler對象。

進入obtain(Handler h)的源碼中可以看到,在調用此方法時,將handler對象給了Message中的target,並返回一個Message對象,所以此方法最終還是調用了無參的obtain()方法

在obtain()無參的方法中,內部有一個同步的代碼塊,在這個同步代碼快中,通過new關鍵字建立了Message對象。不過在建立之前,它先做出了判斷,如果在Message池中已經有Message對象了,那麼就返回已經存在的對象,如果沒Message池中為null,才new一個Message對象。(詳解見3.1.2  Message如何儲存訊息對象)

3.2 Message如何儲存訊息對象

Message內部並不是以隊列的方式儲存,而是以訊息池的方式儲存訊息

(1) mPool預設為訊息池中的第一條訊息,在調用obtain()方法儲存訊息對象時,首先用判斷mPool是否為空白,當sPool不為null時,就使用訊息池中已有的Message對象,如果為null,則new一個Message對象

 

(2)當訊息池中的訊息不為null時,即sPool不為null,則將sPool賦值給一個Messagee對象m,這時m對象就為訊息1

(3)Message對象m使用next屬性指向下一條訊息,這時的m.next相當於訊息2,並將自身賦值給sPool,這時的sPool為訊息2.

(4) 在讓m.next 為null,即讓訊息1不再指向下一條訊息,斷開訊息1和訊息2之間的聯絡關係。這時的sPool為訊息2

(5) 讓訊息池的長度減1,並將Message對象return出去,因為訊息m即原來的訊息對象已經取出,所以此時訊息2即sPool就成為了訊息1

簡而言之,obtain()方法中,就是當判斷出訊息池中已經有Message對象中,沒調用一次該方法,就像第一個訊息取出,將第二個訊息變為第一個訊息

3.3 Handler的建立過程

Handler在當我們需要修改UI介面時,需要我們自己通過new關鍵字建立,一般使用無參的構造方法。

  Handler handler = new Handler(){};

3.4 Looper的建立過程

在Handler的無參構造方法中,調用了自身的一個雙參數的構造方法,有一行代碼為Looper調用了一個myLooper()方法,為變數mLooper賦值,這個變數mLooper是一個Looper對象

 

在myLooper()方法中,通過sThreadLocal調用了get()方法返回一個Looper對象,

從此處可以推斷出,一般有get()方法時,大多數會有一個相對應的set()方法,通過尋找sThreadLocal找到set()方法,可以看到是在prepare()方法中,調用的set()方法。

 

在prepare()方法中,僅僅是建立了一個Looper對象,並賦值給sThreadLooper,並沒有調用載入,繼續尋找prepare()被調用的地方。

通過尋找,找到prepare()方法在prepareMainLooper()方法中被調用。那麼,只要找到prepareMainLooper()被調用的地方,就能找到Looper在什麼時候被調用載入。

 

在Android系統中,Android的UI線程,也就是主線程,實際上是ActivityThread.java,通過查看ActivityThread的源碼,找到main()方法,找到了prepareMainLooper()方法被調用

由此可以推斷出,當應用程式的主線程啟動的時候,就調用載入了prepareMainLooper()來建立了Looper。

3.5 MessageQueue的建立過程

在Handler的無參構造方法中,調用了自身的一個雙參數的構造方法,有一行代碼為

Looper對象mLooper調用mQueue,為MessageQueue賦值

 

    所以要在Looper類中找到mQueue,可以找到MessageQueue在Looper的構造方法中被new出來,而此方法在prepare()方法中,通過sThreadLocal調用set()方法中,作為參數被傳入,接著就類似於Looper的建立過程

實際上MessageQueue是由Looper所建立的,由此,可以推斷出來,MessageQueue和Looper一樣,也是在主線程UI在啟動的時候被載入調用。

 

 

4 當Looper發現MessageQueue中沒有訊息了,會怎樣執行?如果又有訊息了,Looper又會怎樣執行?

    當Looper發現MessageQueue中沒有訊息了,就會進入阻塞狀態,直到MessageQueue中再次有訊息時,Looper才會醒來,繼續執行迴圈

    在此需要明白Linux中的處理序間通訊的機制。

4.1 Linux系統中的處理序間通訊機制

    Linux系統中的進程間相互連信,是通過PIPE管道來完成的,Linux中有一個特殊檔案叫做控制代碼,相當於一個變數對象。一個特殊檔案有兩個控制代碼,一個是寫入控制代碼,另一個控制代碼是讀取控制代碼。

   而子進程對應寫入控制代碼,通過寫入控制代碼向這個特殊檔案中寫入資料,主進程對應讀取控制代碼,不斷的讀取特殊檔案中的資料。

    當特殊檔案中沒有資料時,主進程就會進入阻塞狀態,進入睡眠;當子進程開始向特殊檔案中寫入資料時,為寫入一個特殊標記w(write),當特殊檔案接收到這個特殊標記w時,就會去喚醒主進程。

 

4.2 Looper的阻塞和喚醒機制

    Looper的阻塞和喚醒,就類似於Linux中的進程間同信機制,Handler就是子線程,MessageQueue就是特殊檔案,Looper就是主線程。

    當MessageQueue訊息佇列中如果沒有訊息時,Looper就會進入阻塞狀態,而當Handler有資料寫入MessageQueue訊息佇列中時,會寫入特殊標記w(write),當MessageQueue接收到這個特殊標記w時,就會喚醒Looper繼續讀取訊息。

5 MessageQuene如何儲存Handler發送的訊息?

    訊息是由handler通過sendMessage()方法來發送給MessageQueue,當MessageQueue接收到後,將這個訊息儲存起來

在sendMessage()這個方法中,傳入一個Message對象,內部調用sendMessageDelayed()方法並返回。

在sendMessageDelayed()這個方法中,接收兩個參數,一個是Message對象msg,另一個是

在這個方法中,調用sendMessageAtTime()方法,該方法中有兩個參數,第一個參數為Message對象,即調用sendMessage()方法時傳入的Message對象,在此繼續傳遞。

另一個參數為延遲時間加上一個當前的時間,由於在sendMessageDelayed()方法中傳入的延遲時間為0,所以可以認為這個參數就表示發送Message訊息的時間。

 

在sendMessageAtTime()方法中,返回enqueueMessage()方法,此方法接收到了傳入的Message對象和Message的發送時間這兩個參數,該方法表示當Handler發送一個訊息時,通過這個方法將發送Handler發送的訊息插入到MessageQueue中

而enqueueMessage()方法調用的MessageQueue中的一個雙參數的方法,

 

查看MessageQueue的源碼找到enqueueMessage()雙參數的方法,這個方法接收一個Message對象和Message的發送時間。

在這個方法中,通過判斷訊息是否為空白、訊息的發送時間,將接收到的訊息插入到MessageQueue中。

mMessage為訊息佇列中的第一條訊息,並且賦值給一個Message對象p,所以此時mMessage和p都為第一條訊息。

 

 

5.1假設現在p不為null,也就是訊息不為空白時

 假設現在p不為null,也就是訊息不為空白時,那麼代碼判斷執行else中的語句。定義了一個新的Message對象prve

 

 

(3) 在這個else語句中,有一個迴圈,在這個迴圈中將p賦值給了prev,並且p調用next指向第二條訊息,直到p為null,或者這個即將發送的訊息msg的時間(when)小於p的訊息發送的時間(p.when),即when < p.when時,才break跳出迴圈。(假設此時msg的when值大於a小於b)

此時p為第二條訊息,而prev為第一條訊息

(4) 當滿足break的條件跳出迴圈時,將p賦值給msg.next,也就是msg的下一條訊息為p,並將msg賦值給prev.next,也就是prev的下一條訊息為msg

 

此時prev為第一條訊息,msg為第二條訊息,p為第三條訊息

經此過後,傳入的msg參數就插入到了MessageQueue中

5.2 假設訊息p為null時

    mMessage為第一條訊息,並賦值給p,此時p為第一條訊息

    假設訊息p為null(p == null)或者發送時間為0(when == 0)或者msg的發送時間小於p的發送時間時(when < p.when)

    當p為null時,就是第一條訊息為空白,意味著訊息佇列為null,隊列中沒有訊息。讓p賦值給msg.next,就是讓msg的下一條訊息為null,再將msg賦值給訊息佇列中mMessage預設的第一條訊息,這時mMessage就指向了msg,那麼msg就為訊息佇列中的第一條訊息。

當when等於0時,就是當即將要插入的訊息的時間為0時,意味著要立刻處理這條訊息。當when < p.when時,就是將msg插入到p之前

6 如何將訊息發送給指定的message的Handler?

在Looper的loop方法中,當MessageQueue對象queue在調用next方法賦值給Message對象msg時,有可能會阻塞

先通過myLooper()建立一個Looper對象me,再通過me建立一個MessageQueue對象queue,然後再通過queue的next()方法返回一個Message對象msg,並對msg進行判斷,

當msg為null時,證明在訊息佇列中,後面已經沒有訊息,並return返回結束。

 

當msg不為null時,會通過target調用handler的dispatchMessage()方法。

通過看Handler的源碼找到target,可以知道target就是一個Handler對象,當Handler發送訊息給MessageQueue時,MessageQueue就會將這個Handler儲存起來儲存到target中,這樣當Looper從MessageQueue中取出訊息時,就可以將訊息發給所對應的Handler來處理。

 

在Handler中找到dispatchMessage()方法,在這個代碼中,首先判斷callback是否為null,callback其實是一個Runnable對象,在通過obtain()方法建立Message對象時,obtain()有個雙參數的方法,可以傳入一個Handler對象h和一個Runnable對象callback。就是在此回調

假如callback為null時,調用handleMessage()方法,該方法在代碼中由我們自己實現。

假如callback不為null時,執行handleCallback()方法,這個方法內部通過傳入的Message對象調用callback的run()方法

簡單來說,當我們通過obtain()方法穿件Message對象時,如果傳入一個Runnable對象callback,那麼就通過handleCallback()方法來分發訊息;如果不傳入該參數,那麼就通過在代碼中重寫handleMessage()方法由我們自己實現分發訊息

 

如有錯誤, 敬請指正, 感激不盡!

 

Android源碼學習-----Handler機制

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.