天天看點

Android多線程----異步消息處理機制之Handler詳解

【正文】

雖然是國慶佳節,但也不能停止學習的腳步,我選擇在教研室為祖國母親默默地慶生。

在android當中,提供了異步消息處理機制的兩種方式來解決線程之間的通信問題,一種是今天要講的handler的機制,還有一種就是之前講過的 asynctask 機制。

一、handler的引入:

我們都知道,android ui是線程不安全的,如果在子線程中嘗試進行ui操作,程式就有可能會崩潰。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟于心,即建立一個message對象,然後借助handler發送出去,之後在handler的handlemessage()方法中獲得剛才發送的message對象,然後在這裡進行ui操作就不會再出現崩潰了。具體實作代碼如下:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

上方第45行代碼也可以換成:

上面的代碼中,我們并沒有在子線程中直接進行ui操作,而是建立了一個message對象,并将它的what字段的值指定為了一個整形常量update_text,用于表示更新textview這個動作。然後調用handler的sendmessage()方法将這條message發送出去。很快,handler就會收到這條message,并在handlemessage()方法,在這裡對具體的message進行處理(需要注意的是,此時handlemessage()方法中的代碼是在主線程中運作的)。如果發現message的what字段的值等于update_text,就将textview顯示的内容更新。運作程式後,點選按鈕,textview就會顯示出更新的内容。

 注:如果從源碼的角度了解,粗略的描述是這樣的:

先是調用了handler的obtainmessage()方法得到message對象。在obtainmessage()方法裡做的事情是:調用了message.obtain(this)方法,把handler作為對象傳進來。在message.obtain(this)方法裡做的事情是:生成message對象,把handler作為參數指派給message的target屬性。總的來說,一個handler對應一個looper對象,一個looper對應一個messagequeue對象,使用handler生成message,所生成的message對象的target屬性,就是該對象。而一個handler可以生成多個message,是以說,handler和message是一對多的關系。

二、異步消息處理機制:

handler是android類庫提供的用于接受、傳遞和處理消息或runnable對象的處理類,它結合message、messagequeue和looper類以及目前線程實作了一個消息循環機制,用于實作任務的異步加載和處理。整個異步消息處理流程的示意圖如下圖所示:

Android多線程----異步消息處理機制之Handler詳解

根據上面的圖檔,我們現在來解析一下異步消息處理機制:

message:消息體,用于裝載需要發送的對象。

handler:它直接繼承自object。作用是:在子線程中發送message或者runnable對象到messagequeue中;在ui線程中接收、處理從messagequeue分發出來的message或者runnable對象。發送消息一般使用handler的sendmessage()方法,而發出去的消息經過處理後最終會傳遞到handler的handlermessage()方法中。

messagequeue:用于存放message或runnable對象的消息隊列。它由對應的looper對象建立,并由looper對象管理。每個線程中都隻會有一個messagequeue對象。

looper:是每個線程中的messagequeue的管家,循環不斷地管理messagequeue接收和分發message或runnable的工作。調用looper的loop()方法後,就會進入到一個無限循環中然後每當發現messagequeue中存在一條消息,就會将它取出,并調用handler的handlermessage()方法。每個線程中也隻會有一個looper對象。

了解這些之後,我們在來看一下他們之間的聯系:

首先要明白的是,handler和looper對象是屬于線程内部的資料,不過也提供與外部線程的通路接口,handler就是公開給外部線程的接口,用于線程間的通信。looper是由系統支援的用于建立和管理messagequeue的依附于一個線程的循環處理對象,而handler是用于操作線程内部的消息隊列的,是以handler也必須依附一個線程,而且隻能是一個線程。

我們再來對異步消息處理的整個流程梳理一遍:

當應用程式開啟時,系統會自動為ui線程建立一個messagequeue(消息隊列)和looper循環處理對象。首先需要在主線程中建立一個handler對象,并重寫handlermessage()方法。然後當子線程中需要進行ui操作時,就建立一個message對象,并通過handler将這條消息發送出去。之後這條消息就會被添加到messagequeue的隊列中等待被處理,而looper則會一直嘗試從messagequeue中取出待處理消息,并找到與消息對象對應的handler對象,然後調用handler的handlemessage()方法。由于handler是在主線程中建立的,是以此時handlemessage()方法中的代碼也會在主線程中運作,于是我們在這裡就可以安心地進行ui操作了。

通俗地來講,一般我們在實際的開發過程中用的比較多一種情況的就是主線程的handler将子線程中處理過的耗時操作的結果封裝成message(消息),并将該message(利用主線程裡的messagequeue和looper)傳遞到主線程中,最後主線程再根據傳遞過來的結果進行相關的ui元素的更新,進而實作任務的異步加載和處理,并達到線程間的通信。

通過上一小節對handler的一個初步認識後,我們可以很容易總結出handler的主要用途,下面是android官網總結的關于handler類的兩個主要用途:

(1)執行定時任務:

指定任務時間,在某個具體時間或某個時間段後執行特定的任務操作,例如使用handler提供的postdelayed(runnable r,long delaymillis)方法指定在多久後執行某項操作,比如當當、淘寶、京東和微信等手機用戶端的開啟界面功能,都是通過handler定時任務來完成的。

我們接下來講一下post。 

(2)線程間的通信:

在執行較為耗時的操作時,handler負責将子線程中執行的操作的結果傳遞到ui線程,然後ui線程再根據傳遞過來的結果進行相關ui元素的更新。(上面已有說明)

三、post:

對于handler的post方式來說,它會傳遞一個runnable對象到消息隊列中(這句話稍後會進行詳細解釋),在這個runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在ui線程上的操作。

post允許把一個runnable對象入隊到消息隊列中。它的方法有:post(runnable)、postattime(runnable,long)、postdelayed(runnable,long)。詳細解釋如下:

boolean post(runnable r):把一個runnable入隊到消息隊列中,ui線程從消息隊列中取出這個對象後,立即執行。

boolean postattime(runnable r,long uptimemillis):把一個runnable入隊到消息隊列中,ui線程從消息隊列中取出這個對象後,在特定的時間執行。

boolean postdelayed(runnable r,long delaymillis):把一個runnable入隊到消息隊列中,ui線程從消息隊列中取出這個對象後,延遲delaymills秒執行

void removecallbacks(runnable r):從消息隊列中移除一個runnable對象。

下面通過一個demo,講解如何通過handler的post方式在新啟動的線程中修改ui元件的屬性:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

點選按鈕,運作結果如下:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

有一點值得注意的是:對于post方式而言,它其中runnable對象的run()方法的代碼(37行至39行或者58至61行),均運作在主線程上(雖然看上去是寫在子線程當中的),如果我們在這段代碼裡列印日志輸出線程的名字,會發現輸出的是main thread的名字。是以對于這段代碼而言,不能執行在ui線程上的操作,一樣無法使用post方式執行,比如說通路網絡。 

我們現在來解釋一下上面藍色字型的那句話:

這個runnable對象被放到了消息隊列當中去了,然後主線程中的looper(因為handler是在主線程中生成的,是以looper也在主線程中)将這個runnable對象從消息隊列中取出來,取出來之後,做了些什麼呢?為什麼在執行pos的runnable對象的run()方法時,不是重新開啟一個線程呢?要了解這個過程,隻能求助android的源代碼:

打開源碼的目錄sdk\sources\android-19\android\os,并找到handler.java這個檔案。找到post方法:

上方的代碼中, 可以看到,post方法其實就一行代碼(326行),裡面調用了sendmessagedelayed()這個方法,裡面有兩個參數。先看一下第一個參數getpostmessage(r):(719行)

上方的代碼中,将runnable對象指派給message的callback屬性。注:通過檢視message.java檔案的源代碼發現,callback屬性是一個runnable對象:(91行)

我們再來分析一下上方getpostmessage()這個方法,該方法完成了兩個操作:

一是生成了一個message對象,二是将r對象複制給message對象的callback屬性。傳回的是一個message對象。

再回到326行:

這行代碼相當于:

現在應該好了解了:

第一個問題,如何把一個runnable對象放到消息隊列中:實際上是生成了一個message對象,并将r指派給message對象的callback屬性,然後再将message對象放置到消息隊列當中。

我們再看看一下looper做了什麼。打開looper.java的dispatchmessage的方法:(136行)

這裡面調用了dispatchmessage()方法,打開handler.java的dispatchmessage()方法:(93至104行)

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

上方第5行代碼:因為這次已經給message的callback屬性指派了,是以就不為空,直接執行這行代碼。即執行handlecallback()這個方法。打開handlecallback()方法的源碼:(732至734行) 

看到這個方法,就真相大白了:message的callback屬性直接調用了run()方法,而不是開啟一個新的子線程。

現在可以明白了: 

第二個問題: looper取出了攜帶有r對象的message對象以後,做的事情是:取出message對象之後,調用了dispatchmessage()方法,然後判斷message的callback屬性是否為空,此時的callback屬性是有值的,是以執行了handlecallback(message message),在該方法中執行了 message.callback.run()。根據java的線程知識,我們可以知道,如果直接調用thread對象或者runnable對象的run()方法,是不會開辟新線程的,而是在原有的線程中執行。 

因為looper是在主線程當中的,是以dispatchmessage()方法和handlemessage()方法也都是在主線程當中運作。是以post()裡面的run方法也自然是在主線程當中運作的。 使用post()方法的好處在于:避免了在主線程和子線程中将資料傳來傳去的麻煩。

四、message:

handler如果使用sendmessage的方式把消息入隊到消息隊列中,需要傳遞一個message對象,而在handler中,需要重寫handlemessage()方法,用于擷取工作線程傳遞過來的消息,此方法運作在ui線程上。

對于message對象,一般并不推薦直接使用它的構造方法得到,而是建議通過使用message.obtain()這個靜态的方法或者handler.obtainmessage()擷取。message.obtain()會從消息池中擷取一個message對象,如果消息池中是空的,才會使用構造方法執行個體化一個新message,這樣有利于消息資源的利用。并不需要擔心消息池中的消息過多,它是有上限的,上限為10個。handler.obtainmessage()具有多個重載方法,如果檢視源碼,會發現其實handler.obtainmessage()在内部也是調用的message.obtain()。

handler中,與message發送消息相關的方法有:

message obtainmessage():擷取一個message對象。

boolean sendmessage():發送一個message對象到消息隊列中,并在ui線程取到消息後,立即執行。

boolean sendmessagedelayed():發送一個message對象到消息隊列中,在ui線程取到消息後,延遲執行。

boolean sendemptymessage(int what):發送一個空的message對象到隊列中,并在ui線程取到消息後,立即執行。

boolean sendemptymessagedelayed(int what,long delaymillis):發送一個空message對象到消息隊列中,在ui線程取到消息後,延遲執行。

void removemessage():從消息隊列中移除一個未響應的消息。

五、通過handler實作線程間通信:

1、在worker thread發送消息,在mainthread中接收消息:

【執行個體】點選反扭,将下方的textview的内容修改為“從網絡中擷取的資料”

Android多線程----異步消息處理機制之Handler詳解

【實際意義】點選按鈕時,程式通路伺服器,伺服器接到請求之後,會傳回字元串結果,然後更新到程式。

完整版代碼如下:

xml布局檔案代碼如下:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

mainactivity.java代碼如下:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

 這段代碼的結構,和最上面的第一章節是一樣的。

上方代碼中,我們在子線程中休眠2秒來模拟通路網絡的操作。

65行:用字元串s表示從網絡中擷取的資料;70行:然後我們把這個字元串放在message的obj屬性當中發送出去,并在主線程中接收(36行)。

運作後結果如下:

Android多線程----異步消息處理機制之Handler詳解

點選按鈕後結果如下:

Android多線程----異步消息處理機制之Handler詳解

點選按鈕後,背景輸出結果如下:

Android多線程----異步消息處理機制之Handler詳解

可以看到,子線程的名字是:thread-1118,主線程的名字是:main。

2、在mainthread中發送消息,在worker thread中接收消息:

【執行個體】點選按鈕,在在mainthread中發送消息,在worker thread中接收消息,并在背景列印輸出。

【代碼】完整版代碼如下:

Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解
Android多線程----異步消息處理機制之Handler詳解

上方的第42行至54行代碼:這是mainthread中發送消息,在worker thread中接收消息的固定寫法。上面的三個步驟再重複一下:

準備looper對象

在workerthread當中生成一個handler對象

調用looper的loop()方法之後,looper對象将不斷地從消息隊列當中取出對象,然後調用handler的handlemessage()方法,處理該消息對象;如果消息隊列中沒有對象,則該線程阻塞

注意,此時handlemessage()方法是在worker thread中運作的。

運作程式後,當我們點選按鈕,就會在背景輸出“收到了消息對象”這句話:

Android多線程----異步消息處理機制之Handler詳解

小小地總結一下:

首先執行looper的prepare()方法,這個方法有兩個作用:一是生成looper對象,而是把looper對象和目前線程對象形成鍵值對(線程為鍵),存放在threadlocal當中,然後生成handler對象,調用looper的mylooper()方法,得到與handler所對應的looper對象,這樣的話,handler、looper 、消息隊列就形成了一一對應的關系,然後執行上面的第三個步驟,即looper在消息隊列當中循環的取資料。

繼續閱讀