最近花了幾天研究了一下Android的Handler機制,也閱讀了網上很多的資料,于是在此寫篇文章整理一下,想形成自己的體系,有一些別人總結的很好的干貨我這邊就不再總結了(但是會給出推薦鏈接),我只是把自己認為特別重要的寫在這里,
一.Handler機制是干啥的
我認為可以從兩方面來理解
1 從俠義上來說,handler(Android訊息機制的主要成員)是用來解決子執行緒無法訪問更改UI的問題的
2 從廣義上來說,所有的代碼都是在handler基礎上運行的,handler是Android的app運行的整個框架,Android系統框架內,Activity生命周期的通知等功能也是通過Handler訊息機制來實作的
1好理解,因為這是handler與我們距離最近的使用,但是為什么我認為有2這么一點呢?下面我來解釋下:
關于 2 的解釋
我們知道,安卓應用程式作為一個控制類程式,跟Java程式類似,都是有一個入口的,而這個入口就是ActivityThread,ActiviyThread也有一個main方法,這個main方法其實是安卓應用程式真正的入口,所以
①我們進入ActivityThread的main方法看一下
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
省略無關代碼,我們不難發現,在main函式這里進行了兩個關于Looper的至關重要的操作,第一個是
Looper.prepareMainLooper();
②我們追蹤進入
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
發現這里是對sMainLooper 進行賦值,就是給主執行緒的looper賦值(looper是handler機制里面一個非常重要的角色)也就是創建了主執行緒的Looper
③然后Looper.loop(),我們追蹤進入loop方法
public static void loop() {
...
for (;;) {
...
}
}
發現在loop方法里面,除了開頭幾行的一些賦值等等,后面就會進入一個死回圈,我們整個app的啟動,可以說都是在這個死回圈里面進行的,可以這么理解,就是可以把我們的app比喻成人,這個死回圈比喻成自然環境,只有自然環境能夠一直運行下去,正常運轉,那么人才能有生活的物資,才不至于滅亡,所以app就依賴這個死回圈才能在你不退出的情況下一直運行,所以我說2 不過分吧?
二.Handler機制的幾個角色及其相互關系
上面我們解決了兩個問題,其一是Handler機制是啥(主執行緒中更新UI),其二是Handler機制的重要性(Android的framework層的極端重要的部分),那么下面就來詳細地介紹下Handler機制涉及的幾個角色Handler,Looper,MessageQueue,Message及其相互關系
1.基本介紹
-
Handler: 發送訊息和處理訊息 -
Looper: 從MessageQueue中獲取Message,并將其交給Handler的dispatchMessage(Message)方法 -
MessageQueue: 訊息佇列,存放Handler發送過來的訊息 -
Message: 訊息的載體
2.他們之間的關系
他們之間的關系,我從兩個方面來分析
①其一,從基本功能的角度
首先,看這么一張圖

(1)MessageQueue就像履帶,它上面裝載著一個個Message
(2)Thread就像傳送帶的動力,對應到程式就是我們通信都是基于執行緒而來的,
(3)傳送帶的滾動需要一個開關給電機通電,那么就相當于我們的Looper的loop函式,而這個loop里面的for回圈就會帶著傳送帶不斷的滾動,去輪詢messageQueue上面裝載的每一個Message
(4)Message就是 我們的貨物了,
②其二,從設計思想的角度

其實構成了執行緒模型中的經典問題 生產者-消費者模型,
生產者-消費者模型:生產者和消費者在同一時間段內共用同一個存盤空間,生產者往存盤空間中添加資料,消費者從存盤空間中取走資料,
好處:能夠保證資料生產消費的順序(通過MessageQueue,先進先出) 不管是生產者(子執行緒)還是消費者(主執行緒)都只依賴緩沖區(handler),生產者消費者之間不會相互持有,使他們之間沒有任何耦合
③再總結下,就是下面這張圖

三.關于Handler機制的一些必要知識點
1.Handler有哪些發送訊息的方法
sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)
//下面這些不怎么重要
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)
2.從Handler發送訊息到訊息入隊,其內部是如何實作的?
①首先在子執行緒呼叫sendMessage方法發送訊息
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
②追蹤sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
③發現呼叫了sendMessageAtTime,繼續追蹤
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
④發現呼叫了Handler的enqueueMessage,繼續追蹤
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
//將當前的Handler賦值給Message的target屬性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
⑤發現最終呼叫了MessageQueue的enqueueMessage方法,將訊息入隊
2.MessageQueue的必要知識
(1)基本介紹
MessageQueue維護了一個優先級佇列(以時間為順序進行排列),而且是以鏈表的形式實作的,其他執行緒若想發送訊息到此執行緒,則就要把訊息發送到此執行緒的MessageQueue,
(2)排序依據when的介紹
MessageQueue不僅滿足先進先出,而且還有順序,其順序就是根據每一個訊息的when屬性來排序的,還記得一開始列出來的提交資訊的那些方法嘛,不管用哪個方法去提交訊息,最終一般都會走到sendMessageDelayed方法,而這個方法就可以設定when,我們進去看一下
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到這個when值是 SystemClock.uptimeMillis() 與 delayMillis 之和,代表從系統啟動開始再加上你給它設定的延遲時間數,很好理解的,when就是用于表示 Message 期望被分發的時間,就是啥時候執行,
(3)在哪根據when來排序的
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.when = when;
...
if (p == null || when == 0 || when < p.when) {
...
} else {
...
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//***************************
//***************************
//看這里看這里
//***************************
//***************************
break;
}
if (needWake && p.isAsynchronous()) {
...
}
}
...
}
...
}
...
}
我們把相對無關的代碼都省去了,在代碼標注的位置,我們可以看到這個for死回圈跳出的條件有兩個,其一是一直遍歷走到MessageQueue的隊尾,才退出回圈,其二是發現有符合要求的地方可以插入message了,這個符合要求是啥意思,比如待插入的message的when是2,而MessageQueue佇列中的message分別是1,3,4,5.那么待插入的message就會插入到when為1和3的message之間,新佇列的when就變成了1,2,3,4,5,很好理解吧,
特殊情況,如果
when相同,則按代碼執行時間先后順序插入,
(4)MessageQueue核心演算法enqueueMessage的完整分析
上面我們簡簡單單地分析了下enqueueMessage,下面我們就較為完整地分析下這個演算法
boolean enqueueMessage(Message msg, long when) {
// Hanlder為空則拋例外
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//當前訊息如果已經已經被執行則拋例外
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 對MessageQueue進行加鎖
synchronized (this) {
// 判斷目標thread是否已經死亡
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 標記Message正在被執行,以及需要被執行的時間
msg.markInUse();
msg.when = when;
// p是MessageQueue的鏈表頭
Message p = mMessages;
// 判斷是否需要喚醒MessageQueue
boolean needWake;
// 如果有新的隊頭,同時MessageQueue處于阻塞狀態則需要喚醒佇列
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//...
// 根據時間找到插入的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//...
}
msg.next = p;
prev.next = msg;
}
// 如果需要則喚醒佇列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
需要注意的是:當新插入的Message在鏈表頭時,如果messageQueue是空的或者正在等待下個延遲訊息,換句話說就是處于阻塞狀態的時候,則需要喚醒MessageQueue
3.Looper的必要知識
(1)簡單介紹
Looper是個什么角色呢?從一個簡單的例子來說吧,我們在子執行緒創建Handler實體的時候,必須要先創建一個Lopper,并開啟回圈讀取訊息,在主執行緒中我們ActivityThread已經幫我們創建了(在本文最開始的地方就有介紹),所以我們不需要再次創建,但是在子執行緒中新建Handler的時候就需要創建咯!比如這樣
public static class MyThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(@NonNull Message msg) {
//處理接收的訊息
}
};
Looper.loop();
}
}
一個執行緒對應一個Looper,但是可以有多個Handler,Looper是訊息輪詢的基礎,比如本文最開始的傳送帶圖片,沒有Looper那么整個Handler機制就無法運轉,而且Looper還不能多,否則傳送帶就會崩掉,違背單線操作的概念,造成不合適的并發操作,
- 值得注意的是,我們看下
Looper的構造方法,發現我們在創建Looper的時候,也會捎帶著創建MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
(2)Looper的作業介紹(訊息是怎么被讀取的)
剛剛說了用Loop來輪詢訊息,那么具體是怎么個詢法呢?沒錯,就是Looper的loop方法,我們要想搞懂Looper的訊息輪詢機制,就要從loop方法開始,(本文開始也有一丟丟對loop方法的分析,不過這里才是更全面的)
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
...
}
}
我們可以看到,在開啟死回圈后,獲取每一個訊息的方式是呼叫queue的next方法,而且值得注意的是,在原始碼的注釋中給了兩個單詞:might block,即有可能是阻塞的,好,下面追蹤queue的next方法看看
Message next() {
// Return here if the message loop has already quit and been disposed.
// 原始碼中的注釋表示:如果looper已經退出了,這里就回傳null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//...
// 定義阻塞時間賦值為0
int nextPollTimeoutMillis = 0;
//死回圈
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞對應時間 這個方法最侄訓呼叫到linux的epoll機制
nativePollOnce(ptr, nextPollTimeoutMillis);
// 對MessageQueue進行加鎖,保證執行緒安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//...
if (msg != null) {
if (now < msg.when) {
// 將要獲取的下一個訊息(也就是隊頭)還沒開始,計算等待時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲得訊息且現在要執行,標記MessageQueue為非阻塞
mBlocked = false;
// 鏈表操作
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 沒有訊息,進入阻塞狀態
nextPollTimeoutMillis = -1;
}
//退出
if (mQuitting) {
dispose();
return null;
}
}
}
總結下:先看下Looper是否已經退出(怎么退出后面講),然后進入死回圈,首先判斷是否要進行阻塞(判斷是依據是上一次回圈得到的某個值:nextPollTimeoutMillis ),阻塞最侄訓呼叫到linux的epoll(pipe/epoll)機制,阻塞結束后會進入鎖中,得到隊首的Message物件,然后進入if判斷,如果有訊息,即把隊頭訊息取出,然后進行判斷,決定是當下立即回傳還是delay一下再回傳,如果沒有訊息,則無限期進入阻塞狀態,直到有訊息進入,
nextPollTimeoutMillis表示阻塞的時間,其中,-1表示無限時間,直到有訊息傳入為止,0表示不阻塞,
(3)Looper的退出
剛剛說了Looper的作業原理,那么作業完了怎么退出呢?
Looper提供了quit和quitSafely兩個方法來退出一個Looper,二者的區別是: quit會直接退出Looper,而quitSafely只是設定一個標記,然后把訊息佇列中的已有訊息處理完畢后才安全退出,這個好理解,
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
其實兩個方法最終都是呼叫mQueue的quit方法,讓我們來分析一下這個方法
// 最終都是呼叫到了這個方法
void quit(boolean safe) {
// 如果!mQuitAllowed為true即不能退出則拋出例外,
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
// 執行不同的退出邏輯方法
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// 喚醒MessageQueue
nativeWake(mPtr);
}
}
這個方法首先根據mQuitAllowed引數判斷是否能退出,如果可以退出則進入鎖中執行退出邏輯,如果mQuitting==true,那么這里會直接return掉,而且mQuitting這個變數只有在這里被執行了賦值,所以一旦looper退出,則無法再次運行了,也就是說Looper只能創建一次,一旦quit之后此執行緒就無法再復活了,
- 而且同一個執行緒,只能創建一個
Looper,因為Looper的創建是通過Looper.prepare方法實作的,而在prepare方法中就判斷了,當前執行緒是否存在Looper物件,如果已經存在則拋出例外,所以同一個執行緒,只能創建一個Looper,多次創建會報錯,
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
之后執行不同的退出邏輯,然后喚醒MessageQueue,之后MessageQueue的next方法會退出,Looper的loop方法也會跟著退出,那么執行緒也就停止了,
(4)關于Looper的幾個騷操作
①如何獲取當前執行緒的looper
Looper.myLooper()
內部原理就是通過
ThreadLocal<Looper>的sThreadLocal物件的get方法來獲取Looper
sThreadLocal物件在整個app中只有一個,因為它是由static final修飾的
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
②如果在任意執行緒獲取主執行緒的 Looper
Looper.getMainLooper()
③如何判斷當前執行緒是不是主執行緒
方法一:
Looper.myLooper() == Looper.getMainLooper()
方法二:
Looper.getMainLooper().getThread() == Thread.currentThread()
方法三: 方法二的簡化版
Looper.getMainLooper().isCurrentThread()
4.關于ThreadLocal
ThreadLocal是Java中一個用于執行緒內部存盤資料的工具類,并不是Android所特有的,
值得注意的是,在不同的執行緒中訪問同一個threadLocal物件,但是它們獲取的值卻是不一樣的,這就是ThreadLocal的奇妙之處,那么問題來了,原理是什么?
我們進入其set方法看一下
public void set(T value) {
//得到當前執行緒
Thread t = Thread.currentThread();
//得到當前執行緒的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//將當前的ThreadLocal變數作為key,傳進來的泛型作為value進行存盤
map.set(this, value);
else
//沒有map則創建,并且將當前的ThreadLocal變數作為key,傳進來的泛型作為value進行存盤
createMap(t, value);
}
我們可以發現,每一個執行緒都維護著一個Map集合,其名字為ThreadLocalMap ,我們可以通過
getMap方法來獲得,此集合是以鍵值對的方式來存盤資料的,鍵為ThreadLocal物件,也就是呼叫set方法的那個ThreadLocal物件,這個值就是set()里面傳入的引數了,每一個執行緒都有一個ThreadLocalMap ,雖然鍵相同,但是取出來的值是不相同的,
- 我們可以這樣類比,就是
ThreadLocal是一個人,不同的執行緒即不同的Thread和不同的ThreadLocalMap,是不同的環境,相同的一個人(key),在學校(學校的Thread,里面有學校特有的ThreadLocalMap)里面他的身份(value)就是學生,在家庭里面他的身份(value)可能是父親,在社會上他的身份(value)就是中國公民,即同一個人在不同的環境下有不同的身份,同一個ThreadLocal在不同的執行緒下可能映射著不同的值,就是這個道理, - 前面在
Looper中提到的的ThreadLocal<Looper>的sThreadLocal,它的get方法就是回傳每個執行緒所特有的Looper,其也是從ThreadLocalMap中取值,雖然整個App中sThreadLocal是唯一的,但是每個執行緒ThreadLocalMap是不同的,所以不同的執行緒能得到不同的Looper,
5.同步屏障
在Handler機制中,有三種訊息型別:
①同步訊息,也就是普通的訊息,
②異步訊息,通過setAsynchronous(true)設定的訊息,
③同步屏障訊息,通過postSyncBarrier方法添加的訊息,特點是target為空,也就是沒有對應的handler,
同步屏障是什么意思?可以這么理解,比如救護車,在正常情況下,沒有拉病人的情況下是可以和其他車一樣在公路上正常行駛的,但是如果拉上病人就不一樣了,不僅需要加急,不用等紅燈,而且前面還有一輛警車幫忙帶路,其實這個救護車就可以理解成異步訊息,警車就可以理解成同步屏障訊息,在后方有承載著病人的救護車的時候,其他車輛都會被警車“屏障”,救護車優先通行,這就是同步屏障機制存在的意義,當有要緊的異步訊息需要立馬被執行的時候,同步屏障訊息就來了,具體程序我們可以看一下原始碼
Message next() {
...
for (;;) {
...
synchronized (this) {
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
...
}
...
}
}
我們可以看到,在next方法,每次取出訊息的時候,會先進行一個if判斷,即if (msg != null && msg.target == null) ,就是如果某個msg不為null而且它的target為null,那么它就是一個同步屏障訊息,那我們就要進入這個if,此時會往后逐個遍歷MessageQueue中的訊息,當發現下一個異步訊息的時候,就退出回圈,得到這個異步訊息然后加急進行處理,
- 值得注意的是:同步屏障訊息只是一個標志,就像我一開始說的警車一樣,它上面并沒有承載病人,真正承載病人的是后面的救護車,也就是有重要資訊的異步訊息,
- 而且并不是所有的異步訊息都是需要加急處理的,正常情況下異步訊息和同步訊息都是可以根據
when來決定處理順序的,當然只是正常情況下,畢竟異步訊息設計出來就是要被加急處理的,就像救護車的職能就是緊急救援病人的,
用張圖表示就是這樣

那么有什么加急訊息呢?比如UI的更新,這就是一個加急訊息,相信不用我解釋大家都能理解咯,
6.HandlerThread
HandlerThread是啥?看名字就知道,其實它繼承了Thread,本質上也是一個Thread,但是它的run函式中已經幫我們完成了Looper的創建,不信你看
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
所以它第一個好處就是方便我們使用,也就是我們使用子執行緒創建Handler的時候就不用再顯示地創建Looper了,
但是,光知道這個還是不夠的,萬一在HandlerThread還沒有創建好Looper的時候就呼叫getLooper方法怎么辦呢?谷歌工程師早就想到了這個執行緒安全問題,所以我們看getLooper方法
public Looper getLooper() {
...
// If the thread has been started, wait until the looper has been created.
//意思是如果執行緒已經啟動了,那么等looper創建了之后再回傳
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
從這個方法可以看出,人家getLooper方法并不是無腦給你回傳looper,因為有可能還沒有創建好,所以先wait,等前面run方法創建了looper之后notifyAll,這里getLooper才給你回傳looper,這就是第二個好處,執行緒安全,
7.IntentService
首先看原始碼
public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
首先它是一個服務,可以借助子執行緒HandlerThread ,將訊息一個接一個地執行,而且訊息佇列的特點是前面的訊息執行完后面的訊息才能執行,所以就可以讓子任務在子執行緒中有條不紊地一個接一個地執行,而且訊息執行完畢之后服務自動關閉stopSelf,實作了記憶體釋放,
我們的耗時任務的邏輯就是重寫onHandleIntent方法,在執行耗時任務的時候就會回呼我們重寫的onHandleIntent方法,
簡單來說,這就是一個可以在子執行緒進行耗時任務,并且在任務有序執行后自動停止的Service
8.Handler記憶體泄漏
小題大做 | 記憶體泄漏簡單問,你能答對嗎(強烈建議大家學習此文)
參考文章
溫故而知新 | 打破Handler問到底(這個強烈推薦大家學習)
你真的懂Handler嗎?Handler問答
"一篇就夠"系列: Handler訊息機制完全決議
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/280556.html
標籤:其他
