Android非同步訊息處理
阿新 • • 發佈:2019-02-03
非同步訊息簡介
非同步訊息和執行緒的區別在於,執行緒執行完run()方法後,執行緒就結束了,而非同步訊息是線上程內部有一個訊息佇列,寫一個死迴圈,
一直去訊息佇列裡去取訊息,然後根據訊息型別處理相應的操作,如果取不到訊息就一直在等待。
非同步認為一般用於:任務需要常駐,比如處理使用者互動的螢幕觸控事件處理;根據不同的訊息型別處理不同的操作。
實現上就是:
1.每個非同步任務要有一個訊息佇列;
2使用while(true)無限迴圈,讀取訊息,處理訊息,執行回撥函式等;
3,外部可以向佇列發訊息,訊息佇列注意執行緒安全。
Android中的非同步訊息
看下圖,這個就是android中的實現圖,
可以這樣描述:
(1)線上程內部有一個或者多個Handler物件,外部程式通過Handler物件向執行緒傳送非同步訊息,
訊息由Handler物件傳遞到MessageQueue物件中。
(2)執行緒的主執行程式中從MessageQueue中讀取訊息,並回調Handler物件中的回到函式handleMessage()
注意:MessageQueue線上程內部只能包含一個;每個訊息對應一個Handler物件
Looper,MessageQueue,Handler的原始碼介紹
先看下使用
----------------------------------------------------------- Looper.java Looper的作用有兩點: 第一個是建立訊息佇列; 第二個就是無限迴圈讀取訊息佇列裡的訊息。 建立訊息佇列 當建立訊息佇列的時候,需要首先呼叫Looper.prepare()靜態函式。
第73行程式碼,在sThreadLocal裡面檢查一下當前執行緒是否已經呼叫過prepare()方法了,即檢查一下和當前執行緒相關的Looper物件是否 已經建立。這裡就是保證一個執行緒只能產生一個Looper物件,如果多次呼叫會報錯。Looper物件被儲存到了執行緒本地儲存裡(ThreadLocal),只存和當前執行緒相關的內容。
對於不瞭解ThreadLocal的,自行到網上查詢一下,簡單說下,就是儲存執行緒範圍的資料。
接著看下Looper的建構函式
Looper的建構函式時私有的,同事在建構函式裡,建立了一個訊息佇列mQueue(第182行),並且把呼叫次Looper的執行緒賦值給mThread(第184行),代表這個Looper從屬的執行緒。
這樣佇列就準備好了。
讀取訊息佇列
從呼叫Looper.loop();的時候開始讀取訊息。loop()方法的原始碼如下:
108行:獲得Looper物件,就是簡單的從sThreadLocal裡面把和當前執行緒相關的Looper物件找出來,如下:
109行:從Looper物件裡找出當前執行緒的訊息佇列MessageQueue物件。
113和114行先別管,以後再講解。
直接進入while迴圈,開始讀取訊息了
117行:從訊息佇列裡讀取訊息,返回的是Message,Message代表一個訊息。如果沒有訊息,阻塞在這裡,直到有訊息返回為止。
121行:只有訊息不為null才去處理訊息
122行:如果訊息的target為null的話,就結束這次迴圈,繼續迴圈下次,target就是訊息對應的Handler物件。
130行:msg.target.dispatchMessage(msg),把訊息通過Handler分發下去,就是把訊息傳遞給Handler的回撥函式
146行:msg.recycle();當處理完一次訊息之後,要對訊息進行回收處理,因為在Message內部有一個訊息池,用來避免不停的
建立刪除訊息物件,內部只是把訊息設定成空閒狀態,以便重複利用。
MessageQueue
其實上面介紹Looper的時候,非同步訊息基本上大體就是這樣處理的。
這裡分析下MessageQueue的原始碼
訊息佇列採用排隊的方式進行訊息處理的,先到的訊息最先得到處理(還有一種情況,訊息被指定某個時刻進行處理,如果時刻不到也不會
去處理)。訊息佇列中的訊息用Message類表示,訊息是以連結串列的形式儲存,Message物件內部包含一個next變數,該變數指向下一個訊息。
訊息佇列的主要功能有兩個:
1.取出訊息
2.放進訊息
取出訊息
從Looper的原始碼中可以知道,是呼叫的訊息佇列的next()方法取出的訊息。看MessageQueue的原始碼:
從115行看起,這裡是迴圈取訊息。其實再MessageQueue.java的物件是沒有這個訊息佇列,真正的訊息佇列在C程式碼中的,
119行:private native void nativePollOnce(int ptr, int timeoutMillis)是個native函式,從C的訊息佇列中取訊息,C中有個
MessageQueue.cpp檔案,裡面定義了一個MessageQueue的C++類。呼叫C++程式碼,取出訊息,把取出的訊息賦值古java中
mMessages變數,如果沒有就會掛起等待。
121行開始,就是讀取訊息
123行:獲得當前時間
124行:把取出的訊息mMessages賦值為final修飾的msg變數,便於後面的處理。
125行:判斷取出的訊息為空
127-133行:如果到了執行時間,就會把訊息返回給Looper。
134行,如果沒到執行時間,則繼續迴圈,去取訊息。訊息佇列的讀寫不能同時進行,所以用synchronized關鍵字包圍起來。
從上面的程式碼可以看出,當時間未到或者訊息為空的時候,佇列會處於空閒狀態,如果沒有空閒時的任務的話,會繼續到佇列裡
去取訊息。其中mIdleHandlers儲存的是空閒任務,如果有空閒任務的話,空閒的時候會去執行。
看下面的程式碼:158-174行
放進訊息佇列
對應該的方法是
主要看方法被synchronized包圍的地方
205行之前都是做一些資料檢查
206-221行主要是看看這個訊息是不是佇列的頭部,如果不是頭部,needWake為false如果不是的話,就需要把這個訊息
放在隊尾,如果是隊頭或者將會變成 對頭,那麼needWake就會是true,會呼叫navtiveWake,把訊息放到C++的那個訊息佇列裡去,
最後函式返回true,表示插入佇列成功。
MesageQueue的建構函式,呼叫了nativeInit()函式,呼叫的是native的,在C程式碼中初始化一個訊息佇列。
Handler
先看下建構函式
119行:獲得當前執行緒的Looper物件,賦值給mLooper
120-123:檢查Looper物件是否為空,為空的話,肯定就沒有訊息隊列了
124行:獲得當前執行緒對應的訊息佇列
125行:mCallback是個回撥物件
再一個就是大家比較關注用handler傳送訊息
接著呼叫:
第二個引數delayMillis代表訊息延遲的時間,沒有延遲就是0
接著呼叫sendMessageAtTime
第二個引數是訊息的傳送時間
456行:把msg的target設定成當前的Handler物件,這樣每個訊息就可以關聯一個自己的Handler了,所以最後就能回撥到這個Handler的回撥函式。
457行:把訊息放進訊息佇列,然後返回成功放進去與否。
回撥Handler的函式:
其實是在Looper的loop()方法中執行的,請看
呼叫的handler物件的dispatchMessage(msg)方法,方法體如下:
handleCallback(msg)先不講。
99行:呼叫了handleMesssage(msg)函式
handleMessage(msg)函式如下,就是空函式,需要程式設計師去實現:
解釋一個callback,這個是定義在Handler裡的一個內部介面,如果這個介面物件存在的話,就不會回撥handler的handleMessage(msg)方法,從94-98行程式碼可以看出。
同理當msg的callback存在的時候,也不呼叫handler的handleMessage(msg)回撥方法了,而是呼叫handleCallback(msg)方法。
回撥的Message中的callback.run()方法。其實Message中的callback就是一個Runable實現
Message解釋
Handler中有一個obtainMessage()方法,來獲取可以回收利用的Message物件。
其實呼叫的是Message.obtain(handler)方法
下面是Message類中的方法呼叫
138行呼叫了obtain()方法返回一個Message
139行:返回的Message物件的target賦值為傳過來的Handler
mPoolSync就是物件鎖,防止同時被訪問而設定的
mPool就是Message物件,Message的next變數也是Message物件,是連結串列形式的,可以組成一個訊息鏈條,只要有訊息空閒就可以取出來使用。
前面的Looper執行完成之後,會有一個Message回收的呼叫,就是這個方法
clearForRecycle()把訊息還原到初始狀態,程式碼入下:
原始碼分析完了,如果感覺不錯,給個好評吧,哈哈
----------------------------------------------------------- Looper.java Looper的作用有兩點: 第一個是建立訊息佇列; 第二個就是無限迴圈讀取訊息佇列裡的訊息。 建立訊息佇列 當建立訊息佇列的時候,需要首先呼叫Looper.prepare()靜態函式。
第73行程式碼,在sThreadLocal裡面檢查一下當前執行緒是否已經呼叫過prepare()方法了,即檢查一下和當前執行緒相關的Looper物件是否 已經建立。這裡就是保證一個執行緒只能產生一個Looper物件,如果多次呼叫會報錯。Looper物件被儲存到了執行緒本地儲存裡(ThreadLocal),只存和當前執行緒相關的內容。