標籤:原來 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機制