轉載請注明連結:https://blog.csdn.net/feather_wch/article/details/79263855
詳解Handler消息機制,包括Handler、MessageQueue、Looper和LocalThread。
本文是我一點點歸納總結的幹貨,但是難免有疏忽和遺漏,希望不吝賜教。
Handler消息機制詳解(29題)
版本:2018/9/7-1(2359)
- Handler消息機制詳解(29題)
- 問題彙總
- 消息機制概述(8)
- ThreadLocal(4)
- MessageQueue(3)
- Looper(8)
- 主線程的消息循環
- Handler(6)
- 記憶體洩漏
問題彙總
以問答形式将本文的所有内容進行彙總。考考你喲。
- Handler是什麼?
- 消息機制是什麼?
- MeesageQueue是什麼?
- Looper是什麼?
- ThreadLocal是什麼?
- 為什麼不能在子線程中通路UI?
- Handler的運作機制概述(Handler的建立、Looper的建立、MessageQueue的建立、以及消息的接收和處理)
- 主線程向子線程發送消息的方法?
- ThreadLocal的作用?
- ThreadLocal的應用場景
- ThreadLocal的使用
- ThreadLocal的set()源碼分析
- ThreadLocal的get()源碼分析
- MessageQueue的主要操作
- MessageQueue的插入和讀取源碼分析
- MessageQueue的next源碼詳解
- Looper的構造中做了什麼?
- 子線程中如何建立Looper?
- 主線程ActivityThread中的Looper是如何建立和擷取的?
- Looper如何退出?
- quit和quitSafely的差別
- Looper的loop()源碼中的4個工作?
- Android如何保證一個線程最多隻能有一個Looper?
- Looper的構造器為什麼是private的?
- Handler消息機制中,一個looper是如何區分多個Handler的?
- 主線程ActivityThread的消息循環
- ActivityThread中Handler H是做什麼的?
- Handler的post/send()邏輯流程
- Handler的postDelayed邏輯流程
- Handler的消息處理的流程?
- Handler的特殊構造方法中,為什麼有Callback接口?
- Handler的記憶體洩漏如何避免?
消息機制概述(8)
1、Handler是什麼?
- Android消息機制的上層接口(從開發角度)
- 能輕松将任務切換到Handler所在的線程中去執行
- 主要目的在于解決在子線程中無法通路UI的沖突
2、消息機制?
主要就是指
Android的消息機制
Handler的運作機制
的運作需要底層
Handler
和
MessageQueue
的支撐
Looper
3、MeesageQueue是什麼?
- 消息隊列
- 内部存儲結構并不是真正的隊列,而是單連結清單的資料結構來存儲消息清單
- 隻能存儲消息,而不能處理
4、Looper是什麼?
- 消息循環
以無限循環的形式去查找是否有新消息,有就處理消息,沒有就一直等待着。
Looper
5、ThreadLocal是什麼?
中一種特殊的概念
Looper
并不是線程,作用是可以在每個線程中互不幹擾的
ThreadLocal
和
存儲資料
。
提供資料
建立時會采用目前線程的
Handler
來構造消息循環系統,
Looper
内部就是通過
Handler
來擷取目前線程的
ThreadLocal
的
Looper
- 線程預設是沒有
的,如果需要使用
Looper
就必須為線程建立
Handler
Looper
就是
UI線程
,被建立時會初始化
ActivityThread
,是以
Looper
中預設是可以使用
UI線程
的
Handler
6、為什麼不能在子線程中通路UI?
ViewRootImpl會對UI操作進行驗證,禁止在子線程中通路UI:
void checkThread(){
if(mThread != Thread.currentThread()){
throw new CalledFromWrongThreadException("Only th original thread that created a view hierarchy can touch its views");
}
}
7、Handler的運作機制概述(Handler的建立、Looper的建立、MessageQueue的建立、以及消息的接收和處理)
- Handler建立時會采用目前線程的Looper
- 如果目前線程沒有
就會報錯,要麼建立
Looper
,要麼在有
Looper
的線程中建立
Looper
Handler
的
Handler
方法會将一個
post
投遞到
Runnable
内部的
Handler
中處理(本質也是通過
Looper
方法完成)
send
的
Handler
方法被調用時,會調用
send
的
MessageQueue
方法将消息放入消息隊列, 然後
enqueueMessage
發現有新消息到來時,就會處理這個消息,最終消息中的
Looper
或者
Runnable
的
Handler
就會被調用
handleMessage
- 因為
是運作在建立
Looper
所在的線程中的,是以通過
Handler
執行的業務邏輯就會被切換到
Handler
所在的線程中執行。
Looper
8、主線程向子線程發送消息的方法?
- 通過在主線程調用子線程中Handler的post方法,完成消息的投遞。
- 通過
實作該需求。
HandlerThread
ThreadLocal(4)
1、ThreadLocal的作用
是線程内部的資料存儲類,可以在指定線程中存儲資料,之後隻有在指定線程中才開業讀取到存儲的資料
ThreadLocal
- 應用場景1:某些資料是以線程為作用域,并且不同線程具有不同的資料副本的時候。
可以輕松實作
ThreadLocal
線上程中的存取。
Looper
- 應用場景2:在複雜邏輯下的對象傳遞,通過
可以讓對象成為線程内的全局對象,線程内部通過
ThreadLocal
就可以擷取。
get
2、ThreadLocal的使用
mBooleanThreadLocal.set(true);
Log.d("ThreadLocal", "[Thread#main]" + mBooleanThreadLocal.get());
new Thread("Thread#1"){
@Override
public void run(){
mBooleanThreadLocal.set(false);
Log.d("ThreadLocal", "[Thread#1]" + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run(){
Log.d("ThreadLocal", "[Thread#2]" + mBooleanThreadLocal.get());
}
}.start();
- 最終
中輸出
main
;
true
中輸出
Thread#1
;
false
中輸出
Thread#2
null
内部會從各自線程中取出數組,再根據目前
ThreadLocal
的索引去查找出對應的
ThreadLocal
值。
value
3、ThreadLocal的set()源碼分析
//ThreadLocal.java
public void set(T value) {
//1. 擷取目前線程
Thread t = Thread.currentThread();
//2. 擷取目前線程對應的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//3. map存在就進行存儲
map.set(this, value);
else
//4. 不存在就建立map并且存儲
createMap(t, value);
}
//ThreadLocal.java内部類: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
//1. table為Entry數組
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-);
//2. 根據目前ThreadLocal擷取到Hash key,并以此從table中查詢出Entry
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//3. 如果Entry的ThreadLocal與目前的ThreadLocal相同,則用新值覆寫e的value
if (k == key) {
e.value = value;
return;
}
//4. Entry沒有ThreadLocal則把目前ThreadLocal置入,并存儲value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//5. 沒有查詢到Entry,則建立Entry并且存儲value
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//ThreadLocal内部類ThreadLocalMap的靜态内部類
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
4、ThreadLocal的get()源碼分析
public T get() {
//1. 擷取目前線程對應的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//2. 取出map中的對應該ThreadLocal的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//3. 擷取到entry後傳回其中的value
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//4. 沒有ThreadLocalMap或者沒有擷取到ThreadLocal對應的Entry,傳回規定數值
return setInitialValue();
}
private T setInitialValue() {
//1. value = null
T value = initialValue();//傳回null
Thread t = Thread.currentThread();
//2. 若不存在則新ThreadLocalMap, 在裡面以threadlocal為key,value為值,存入entry
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
- 目前線程對應了一個
ThreadLocalMap
- 目前線程的
對應一個Map中的
ThreadLocal
(存在table中)
Entry
中
Entry
會擷取其對應的ThreadLocal,
key
就是存儲的數值
value
MessageQueue(3)
1、MessageQueue的主要操作
: 往消息隊列中插入一條消息
enqueueMessage
:取出一條消息,并且從消息隊列中移除
next
- 本質采用
的資料結構來維護消息隊列,而不是采用隊列
單連結清單
2、MessageQueue的插入和讀取源碼分析
//MessageQueue.java:插入資料
boolean enqueueMessage(Message msg, long when) {
//1. 主要就是單連結清單的插入操作
synchronized (this) {
......
}
return true;
}
/**==========================================
* 功能:讀取并且删除資料
* 内部是無限循環,如果消息隊列中沒有消息就會一直阻塞。
* 一旦有新消息到來,next方法就會傳回該消息并且将其從單連結清單中移除
*===========================================*/
Message next() {
for (;;) {
......
}
}
3、MessageQueue的next源碼詳解
Message next() {
int nextPollTimeoutMillis = ;
for (;;) {
/**======================================================================
* 1、精确阻塞指定時間。第一次進入時因為nextPollTimeoutMillis=0,是以不會阻塞。
* 1-如果nextPollTimeoutMillis=-1,一直阻塞不會逾時。
* 2-如果nextPollTimeoutMillis=0,不會阻塞,立即傳回。
* 3-如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(逾時),如果期間有程式喚醒會立即傳回。
*====================================================================*/
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 目前時間
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
/**=======================================================================
* 2、目前Msg為消息屏障
* 1-說明有重要的異步消息需要優先處理
* 2-周遊查找到異步消息并且傳回。
* 3-如果沒查詢到異步消息,會continue,且阻塞在nativePollOnce直到有新消息
*====================================================================*/
if (msg != null && msg.target == null) {
// 周遊尋找到異步消息,或者末尾都沒找到異步消息。
do {
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
/**================================================================
* 3、擷取到消息
* 1-消息時間已到,傳回該消息。
* 2-消息時間沒到,表明有個延時消息,會修正nextPollTimeoutMillis。
* 3-後面continue,精确阻塞在nativePollOnce方法
*===================================================================*/
if (msg != null) {
// 延遲消息的時間還沒到,是以重新計算nativePollOnce需要阻塞的時間
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 傳回擷取到的消息(可以為一般消息、時間到的延遲消息、異步消息)
return msg;
}
} else {
/**=============================
* 4、沒有找到消息或者異步消息
*==============================*/
nextPollTimeoutMillis = -;
}
/**===========================================
* 5、沒有擷取到消息,進行下一次循環。
* (1)此時可能處于的情況:
* 1-沒有擷取到消息-nextPollTimeoutMillis = -1
* 2-沒有擷取到異步消息(接收到同步屏障卻沒找到異步消息)-nextPollTimeoutMillis = -1
* 3-延時消息的時間沒到-nextPollTimeoutMillis = msg.when-now
* (2)根據nextPollTimeoutMillis的數值,最終都會阻塞在nativePollOnce(-1),
* 直到enqueueMessage将消息添加到隊列中。
*===========================================*/
if (pendingIdleHandlerCount <= ) {
// 用于enqueueMessage進行精準喚醒
mBlocked = true;
continue;
}
}
}
}
- 如果是一般消息,會去擷取消息,沒有擷取到就會阻塞(native方法),直到enqueueMessage插入新消息。擷取到直接傳回Msg。
- 如果是同步屏障,會去循環查找異步消息,沒有擷取到會進行阻塞。擷取到直接傳回Msg。
- 如果是延時消息,會計算時間間隔,并進行精準定時阻塞(native方法)。直到時間到達或者被enqueueMessage插入消息而喚醒。時間到後就傳回Msg。
Looper(8)
1、Looper的構造
private Looper(boolean quitAllowed) {
//1. 會建立消息隊列: MessageQueue
mQueue = new MessageQueue(quitAllowed);
//2. 目前線程
mThread = Thread.currentThread();
}
2、為線程建立Looper
//1. 在沒有Looper的線程建立Handler會直接異常
new Thread("Thread#2"){
@Override
public void run(){
Handler handler = new Handler();
}
}.start();
異常:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
//2. 用prepare為目前線程建立一個Looper
new Thread("Thread#2"){
@Override
public void run(){
Looper.prepare();
Handler handler = new Handler();
//3. 開啟消息循環
Looper.loop();
}
}.start();
3、主線程ActivityThread中的Looper
- 主線程中使用
建立
prepareMainLooper()
Looper
能夠在任何地方擷取到主線程的
getMainLooper
Looper
4、Looper的退出
的退出有兩個方法:
Looper
和
quit
quitSafely
會直接退出
quit
Looper
隻會設定退出标記,在已有消息全部處理完畢後才安全退出
quitSafely
退出後,
Looper
的發行的消息會失敗,此時
Handler
傳回
send
false
中如果手動建立了
子線程
,應該在所有事情完成後調用
Looper
方法來終止消息循環
quit
5、Looper的loop()源碼分析
//Looper.java
public static void loop() {
//1. 擷取Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//2. 擷取消息隊列
final MessageQueue queue = me.mQueue;
......
for (; ; ) {
//3. 擷取消息,如果沒有消息則會一直阻塞
Message msg = queue.next();
/**=================================
* 4. 如果消息獲得為null,則退出循環
* -Looper退出後,next就會傳回null
*=================================*/
if (msg == null) {
return;
}
......
/**==========================================================
* 5. 處理消息
* -msg.target:是發送消息的Handler
* -最終在該Looper中執行了Handler的dispatchMessage()
* -成功将代碼邏輯切換到指定的Looper(線程)中執行
*========================================================*/
msg.target.dispatchMessage(msg);
......
}
}
6、Android如何保證一個線程最多隻能有一個Looper?
1-Looper的構造方法是private,不能直接構造。需要通過Looper.prepare()進行建立,
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
2-如果在已有Looper的線程中調用 Looper.prepare()
會抛出RuntimeException異常
public class Looper {
static final HashMap<Long, Looper> looperRegistry = new HashMap<Long, Looper>();
private static void prepare() {
synchronized(Looper.class) {
long currentThreadId = Thread.currentThread().getId();
// 根據線程ID查詢Looper
Looper l = looperRegistry.get(currentThreadId);
if (l != null)
throw new RuntimeException("Only one Looper may be created per thread");
looperRegistry.put(currentThreadId, new Looper(true));
}
}
...
}
7、Handler消息機制中,一個looper是如何區分多個Handler的?
- Looper.loop()會阻塞于MessageQueue.next()
- 取出msg後,msg.target成員變量就是該msg對應的Handler
- 調用msg.target的disptachMessage()進行消息分發。這樣多個Handler是很容易區分的。
主線程的消息循環
8、主線程ActivityThread的消息循環
//ActivityThread.java
public static void main(String[] args) {
//1. 建立主線程的Looper和MessageQueue
Looper.prepareMainLooper();
......
//2. 開啟消息循環
Looper.loop();
}
/**=============================================
* ActivityThread中需要Handler與消息隊列進行互動
* -内部定義一系列消息類型:主要有四大元件等
* //ActivityThread.java
*=============================================*/
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = ;
public static final int PAUSE_ACTIVITY = ;
public static final int PAUSE_ACTIVITY_FINISHING= ;
......
}
通過
ActivityThread
和
ApplicationThread
進行
AMS
IPC通信
完成請求的工作後會回調
AMS
中的
ApplicationThread
方法
Binder
會向
ApplicationThread
發送消息
Handler H
接收到消息後會将
H
的邏輯切換到
ApplicationThread
中去執行
ActivityThread
Handler(6)
1、Handler使用執行個體post/sendMessage
post
handler.post(new Runnable() {
@Override
public void run() {
}
});
sendMessage
// 自定義msg的what
static final int INT_WHAT_MSG = ;
// 0、兩種建立Msg的方法
Message message = new Message();
message = Message.obtain();
// 1、自定義MSG的類型,通過what進行區分
message.what = INT_WHAT_MSG;
// 2、發送Msg
handler.sendMessage(message);
// 3、自定義Handler處理Msg
class MsgHandler extends android.os.Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case INT_WHAT_MSG:
// 識别出了Msg,進行邏輯處理
break;
default:
break;
}
}
}
post内部還是通過sendMessage實作的。
2、Handler的post/send()源碼分析
//Handler.java: post最終是通過send系列方法實作的
//Handler.java
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, );
}
//Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < ) {
delayMillis = ;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//Handler.java
public boolean sendMessageAtTime(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.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//1. 最終是向消息隊列插入一條消息
return queue.enqueueMessage(msg, uptimeMillis);
}
會将消息插入到
sendMessage()
消息隊列中
的
MessageQueue
方法就會傳回這條消息交給
next
Looper
- 最終
會把消息交給
Looper
的
Handler
dispatchMessage
3、Handler的postDelayed源碼分析
//Handler.java---層層傳遞,和一般的post調用的同一個底層方法.
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//xxxxxx
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
...
return queue.enqueueMessage(msg, uptimeMillis);
}
//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
//會直接加進隊列
}
- postDelayed和post調用的底層sendMessage系列方法,隻不過前者有延遲,後者延遲參數=0。
- 最終會直接将Msg加入到隊列中。
- MessageQueue.next()在取出Msg時,如果發現消息A有延遲且時間沒到,會阻塞消息隊列。
- 如果此時有非延遲的新消息B,會将其加入消息隊列, 且處于消息A的前面,并且喚醒阻塞的消息隊列。
- 喚醒後會拿出隊列頭部的消息B,進行處理。然後會繼續因為消息A而阻塞。
- 如果達到了消息A延遲的時間,會取出消息A進行處理。
4、Handler的消息處理源碼
//Handler.java
public void dispatchMessage(Message msg) {
//1. Msg的callback存在時處理消息——Handler的post所傳遞的Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
/**===============================================
*2. mCallback不為null時調用handleMessage
* -Handler handle = new Handler(callback)
* -好處在于不需要派生Handler子類并且也不需要重寫其handleMessage
*=============================================*/
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//3. 如果其他條件都不符合,最後會調用Handler的handleMessage進行處理
handleMessage(msg);
}
}
//Handler.java-調用Handler的post所傳遞的Runnable的run()方法
private static void handleCallback(Message message) {
message.callback.run();
}
//Handler.java-Callback接口用于不需要派生Handler就能完成功能
public interface Callback {
public boolean handleMessage(Message msg);
}
5、Handler的特殊構造方法
-不需要派生Handler
Handler handle = new Handler(callback);
- 通過特定
構造
Looper
Handler
public Handler(Looper looper) {
this(looper, null, false);
}
- 預設構造函數
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
//1. 在沒有Looper的線程中建立Handler
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
記憶體洩漏
6、Handler的記憶體洩漏如何避免?
- 采用靜态内部類:
static handler = xxx
- Activity結束時,調用
、然後handler設定為null
handler.removeCallback()
- 如果使用到Context等引用,要使用
弱引用