基於Android9.0,瞭解Android訊息機制
基於Android9.0,瞭解Android訊息機制
還是那句話:點成線,線成面,切勿貪心,否則一臉懵逼
由於Android的主執行緒(UI執行緒)是非安全的,而且Android開發規範的限制,不能在UI子執行緒中訪問UI控制元件,否則就會出發程式異常。這個時候,就可以通過Handler來將更新UI的操作切換到UI執行緒中執行。因此,Handler被大家經常用來更新UI,但是從本質上說,Handler並不是專門用於更新UI的。
Android的訊息機制主要是指Handler的執行機制,然後得到MessageQueue、Looper的支撐。
接下來,我們來了解這三個類
- Handler
- ThreadLocal
- MessageQueue
- Looper
還是從原始碼出發。
Handler有兩種傳送訊息的方式
第一種
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) {//重寫Handler的handlerMessage方法,因為Handler的handlerMessage是一個空實現 super.handleMessage(msg); } }; Message msg = Message.obtain();//或者new Message(); msg.what = 1; msg.obj = "fat"; handler.sendMessage(msg); Handler的handleMessage方法 /** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) {//空實現 }
先看Handler原始碼
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) { ... mLooper = Looper.myLooper();//注意這裡,獲取當前執行緒的Looper。至於什麼時候建立的,這條路走通後,會來說明 if (mLooper == null) {//如果沒有獲取到Looper,會異常。這裡證明:執行緒沒有Looper物件,無法建立Handler throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue;//把執行緒的MessageQueue與Handler繫結。MessageQueue原理,下面說介紹 mCallback = callback; mAsynchronous = async; }
看原始碼發現,該構造方法,就是獲取當前執行緒的Looper,然後與Handler繫結。
下面看Message msg = Message.obtain();//或者new Message();
public static Message obtain() {//從Message池裡獲取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;//Message池是連結串列結構,獲取效率高與陣列。
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();//池裡沒有,還是會new Message()
}
handler.sendMessage(msg);
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
...
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//得到構造方法時的訊息佇列
if (queue == null) {//為空拋異常
...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
接下來,看MessageQueue的enqueueMessage
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
//這裡鏈式插入頭部
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
//這裡鏈式插入。msg插入到p的後面
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
這裡,就完成了Handler的sendMessage,最後enqueueMessage到MessageQueue中。
第二種
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
}
});
或
Handler handler = new Handler();//Lambda表示式
handler.post(()->{
});
建立Handler和第一種方式一樣,直接看post
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
sendMessageDelayed後的流程,和第一方式一樣,我們來看不同點,看getPostMessage
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;//這裡把我們建立的Runnable,賦值給callback
return m;// 返回帶有callback的Message
}
到這裡,Handler基本結束。
接下來,我們先來看看ThreadLocal,為什麼需要先看ThreadLocal呢?
因為涉及到如何保證一個執行緒對應一個Looper,並且各個執行緒之間的Looper互不干擾,所以我們先來了解ThreadLocal。
ThreadLocal
ThreadLocal是一個泛型類
public class ThreadLocal<T> {
然後,看看類結構(基於9.0),發現,有get、set、remove等方法,應該能感覺到,這個類大方向的作用。
我們先來看看get
public T get() {
Thread t = Thread.currentThread();//獲取當前執行緒
ThreadLocalMap map = getMap(t);//注意這裡
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//注意這裡
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
我們先看看getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
繼續看threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
到這裡,發現getMap返回的就是當前執行緒的ThreadLocal.ThreadLocalMap。然後,我們去看ThreadLocalMap的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//注意這裡
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);//注意這裡
}
threadLocalHashCode是一個雜湊值,防止相同的執行緒獲取同一個物件。
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
這裡的table是一個Entry陣列
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
Entry是ThreadLocalMap一個內部類
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
到這裡,我們可以得出結論,ThreadLocal的get方法,獲取的是:當前執行緒下的ThreadLocalMap裡面的table中的item,至於這個item裡面具體是什麼,我們去看看set()方法,繼續
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//這裡上面分析過了,就是獲取當前執行緒下的threadLocals
if (map != null)
map.set(this, value);//注意這裡
else
createMap(t, value);//注意這裡
}
繼續createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
這裡我們發現,當前執行緒下threadLocals為null的話,會構造一個ThreadLocalMap,且初始化table。我們再看看map的set
private void set(ThreadLocal<?> key, Object value) {
...
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
這裡,我們得出結論:ThreadLocal主要是維護當前執行緒下的ThreadLocal.ThreadLocalMap,每次get或set時,都會先通過當前執行緒的雜湊&內部table陣列長度拿到table的index值,而當前執行緒就為這個table item的key。
至於table如何確定位置、如何防止重複、如何擴容,有時間再慢慢學習,還是那句話:先點成線,再線成面,切勿貪心,否則一臉懵逼。
好了,現在來看Looper
Looper
在Handler構造中,有這麼一句話
mLooper = Looper.myLooper();//注意這裡
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//這裡關聯handler和looper的MessageQueue
看myLooper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//Looper類中申明sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
這裡發現一個問題,在整個過程中,沒發現sThreadLocal呼叫set程式碼,why?
這裡2個點,注意:
1.看胖子之前的文章Android啟動流程,可知。在呼叫ActivityThread的main方法時,會呼叫Looper.prepareMainLooper(),這裡就建立了主執行緒的Looper。
2.子執行緒,需要手動呼叫Looper的prepare方法來建立Looper,否則會丟擲異常,看Handler原始碼可知。
這裡,我們以主執行緒為主,來看看ActivityThread的main方法
public static void main(String[] args) {
...
Looper.prepareMainLooper();//注意這裡
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();//注意這裡,開啟訊息迴圈
throw new RuntimeException("Main thread loop unexpectedly exited");
}
來看看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();//上面已經介紹過了,會呼叫sThreadLocal.get()
}
}
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));
}
到這裡,真相大白了。現在剩下最後二個問題:MessageQueue以及三者如何貫穿起來的,分析原始碼雖然很枯燥,不過貌似看完受益匪淺,內功會提升很多。
MessageQueue
在new Looper時,會建立MessageQueue,我們來看看Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//建立MessageQueue
mThread = Thread.currentThread();//關聯當前執行緒
}
看看構造方法MessageQueue
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;//用於標示訊息佇列是否可以被關閉,主執行緒的訊息佇列不可關閉
mPtr = nativeInit();//native方法
}
這裡很簡單,就兩行程式碼,這裡還是先看這條線,至於MessageQueue的內部實現,有時間,再來和大家一起研究、學習,這裡提一句,MessageQueue內部是以連結串列的形式。
接下來,來看看幾個類,如何貫穿、運作起來的。
全面貫穿
記得ActivityThread,main方法嗎?裡面呼叫了Looper.loop(),來看看。
public static void loop() {
final Looper me = myLooper();//獲取當前執行緒的Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//獲取對應的訊息佇列
...
for (;;) {//訊息迴圈
//取出訊息
Message msg = queue.next(); // might block 注意這裡,可能會阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);//注意這裡,msg.target為對應的Handler
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
...
}
...
msg.recycleUnchecked();
}
}
先看queue.next()
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// nextPollTimeoutMillis該引數用於確定訊息佇列中是否還有訊息,從而決定訊息佇列應處於出隊訊息狀態 or 等待狀態。
//nextPollTimeoutMillis為-1,訊息佇列處於等待狀態
for (;;) {//死迴圈獲取message
...
//阻塞操作,當等待超時或者訊息佇列被喚醒,才會繼續
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
Message prevMsg = null;
Message msg = mMessages;
...
if (msg != null) {
if (now < msg.when) {
...
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
}
記住:點成線,線成面,切勿貪心,否則一臉懵逼。先不要去管為什麼死迴圈不會造成執行緒阻塞,後續再走這條線。
接著再來看dispatchMessage
//如果你記得handler的兩種方式(忘記了,看上面),就知道callback代表什麼了
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//使用post方式
handleCallback(msg);
} else {//sengMessage方式
if (mCallback != null) {//我們在構成handler時,mCallback傳入的null
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//注意這裡
}
}
先看msg.callback == null的時候,呼叫的handleMessage
/**
* Subclasses must implement this to receive messages.子類必須實現它來接收訊息。
*/
public void handleMessage(Message msg) {
}
這裡是一個空實現,子類需要覆蓋,這裡其實就是我們構造Handler時的
![image-20200717182455681](/Users/King/Library/Application Support/typora-user-images/image-20200717182455681.png)
再來看看msg.callback != null的時候,handleCallback方法
private static void handleCallback(Message message) {
message.callback.run();
}
message.callback為我們傳入的Runnable,所以這裡最後呼叫傳入的Runnable的run方法。
至此,handler機制差不多完結了。
關於總結:還是不借鑑各路大神的blog總結了。胖子覺得這樣會造成部分朋友只看總結,不看內容,最後變成知其然不知其所以然(胖子就吃過很多這樣的虧),還是交給朋友們自行總結吧。
胖子總結
- 先搞清楚Handler、Looper、ThreadLocal、MessageQueue的基本實現
- 自己用工具畫一畫它們之間的關係
- 溫馨提示:點成線,線成面,切勿貪心,否則一臉懵逼
- 胖子有什麼理解錯誤的,歡迎大家指出來,一起討論、學習、進步
- 期待胖子的第三篇《Android事件分發(一)》