Android訊息迴圈機制淺析
Android訊息機制概略
從根本上來說Android系統同windows系統一樣,也屬於訊息驅動型系統 訊息驅動型系統會有以下4大要素
- 接收訊息的“訊息佇列”
- 阻塞式地從訊息佇列中接收訊息並進行處理的“執行緒”
- 可傳送的“訊息的格式”
- 訊息傳送函式
Android系統與之對應的實現就是 (MessageQueue,Looper,Handler)
-
接收訊息的“訊息佇列” ——【MessageQueue】主要功能是投遞訊息(
MessageQueue.enqueueMessage
)和取走訊息池的訊息(MessageQueue.next
); - 阻塞式地從訊息佇列中接收訊息並進行處理的“執行緒” ——【Thread+Looper】
- 可傳送的“訊息的格式” ——【Message】
- “訊息傳送函式”——【Handler的post和sendMessage】
一個Looper
類似一個訊息泵。它本身是一個死迴圈,不斷地從MessageQueue
中提取Message
或者Runnable。而Handler
可以看做是一個Looper
的暴露介面,向外部暴露一些事件,並暴露sendMessage()
和post()
函式
Android訊息機制的大致流程
因為Android的訊息模型是貫穿整個App的生命週期的,所以主執行緒的訊息模型在App啟動時就被建立。
Android app的入口類是ActivityThread
,這個類中有個main
方法,這個方法是整個app的入口,
其中有下面一段程式碼
//建立一個訊息迴圈 public static void main(String[] args) { ...... Looper.prepareMainLooper();//主執行緒的Looper(),也是app應用主主要的訊息機制的訊息管理者 ...... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
這段程式碼最後是一個異常,如果程式碼執行到哪裡,就會導致app崩潰,既然我們的app沒有崩潰,說明程式碼中以 hi沒有執行到那裡,怎麼才能使那段程式碼不被執行呢?通過死迴圈
其中Looper.loop()
public static void loop() { ...... for(;;){ ...... } ...... }
looper不停的去輪詢訊息佇列,當有訊息時更新,沒有訊息時休眠.這是我們理想中的狀態,
那麼如果來控制這個迴圈狀態呢?
因為Android系統是基於Linux系統的,自然會汲取Linux成熟的一些設計。其中訊息迴圈就是藉助Linux的epoll機制。
訊息迴圈中有兩個重要的native方法nativePollOnce
訊息掛起和nativeWake
訊息喚醒。
Android訊息迴圈機制的主流程簡略概括如下
主執行緒
nativePollOnce
其他執行緒
nativeWake
主執行緒
- 主執行緒nativePollOnce掛起取消
- 處理訊息
- 訊息處理完訊息,nativePollOnce繼續掛起等待新訊息
....
細說Android訊息機制的流程
上面對Android的訊息機制做了一個簡單的介紹,接下來跟著原始碼再來仔細的分析一下訊息機制
先展示一個典型的關於Handler/Looper的執行緒
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { //TODO 定義訊息處理邏輯. } }; Looper.loop(); } } //只有在子執行緒需要Looper.prepare()和 Looper.loop(); //因為主執行緒在初始化的時候已經建立了一個Looper物件了
Looper
prepare()
對於無參的情況,預設呼叫prepare(true)
,表示的是這個Looper允許退出,而對於false的情況則表示當前Looper不允許退出,主執行緒的Looper就是不允許退出的。
private static void prepare(boolean quitAllowed) { //每個執行緒只允許執行一次該方法,第二次執行時執行緒的TLS已有資料,則會丟擲異常。 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //建立Looper物件,並儲存到當前執行緒的TLS區域 sThreadLocal.set(new Looper(quitAllowed)); } //這個方法是主執行緒Looper初始化的程式碼,入口在ActivityThread中,即App初始化時就會被呼叫 public static void prepareMainLooper() { prepare(false); //設定不允許退出的Looper synchronized (Looper.class) { //將當前的Looper儲存為主Looper,每個執行緒只允許執行一次。 if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
每個執行緒只能有一個Looper物件,並且與執行緒(Thread)繫結,在prepare
方法中Looper
會新建了Looper
物件和MessageQueue
物件
Looper
中有一個ThreadLocal
型別的sThreadLocal
靜態欄位,Looper
通過它的get
和set
方法來賦值和取值。
由於ThreadLocal
是與執行緒繫結的,所以我們只要把Looper
與ThreadLocal
綁定了,那Looper
和Thread
也就關聯上了.ThreadLocal
的分析可以看文章下面的知識補充
loop()
public static void loop() { final Looper me = myLooper();//獲取TLS儲存的Looper物件 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue;//獲取Looper物件中的訊息佇列 Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { //死迴圈 Message msg = queue.next(); //可能會阻塞 if (msg == null) { //沒有訊息,則退出迴圈 return; } //預設為null,可通過setMessageLogging()方法來指定輸出,用於debug功能 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } //msg.target是一個Handler物件,回撥Handler.dispatchMessage(msg) msg.target.dispatchMessage(msg); ... msg.recycleUnchecked();//將Message放入訊息池,用以複用 } }
- 讀取MessageQueue的下一條Message;
- 把Message分發給相應的Handler
- 再把分發後的Message回收到訊息池,以便重複利用
quit()
public void quit() { mQueue.quit(false); //訊息移除 } public void quitSafely() { mQueue.quit(true); //安全地訊息移除 } ----------------------------------------MessageQueue#quit();----------------------------- void quit(boolean safe) { // 主執行緒的MessageQueue.quit()行為會丟擲異常, if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { //防止多次執行退出操作 return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); //移除尚未觸發的所有訊息 } else { removeAllMessagesLocked(); //移除所有的訊息 } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
其他方法
Looper.myLooper()//獲取當前執行緒 public static @Nullable Looper myLooper() { return sThreadLocal.get(); } Looper.getMainLooper() //獲取主執行緒 //判斷當前執行緒是不是主執行緒 Looper.myLooper() == Looper.getMainLooper() Looper.getMainLooper().getThread() == Thread.currentThread() Looper.getMainLooper().isCurrentThread()
Handler
Handler的主要作用是什麼呢?
- 跨程序通訊
- 跨程序更新UI
-
與
looper
messageQueue
一起構建Android的訊息迴圈模型
首先建立Handler
無參構造,
public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { //匿名類、內部類或本地類都必須申明為static,否則會警告可能出現記憶體洩露, //這個警告Android studio會提示給開發者 if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur:" +klass.getCanonicalName()); } } //Handler與Looper的繫結過程,如果在繫結之前沒有呼叫Looper.prepare()方法的話就會報錯 mLooper = Looper.myLooper();//從當前執行緒的TLS中獲取Looper物件 if (mLooper == null) { throw new RuntimeException(""); } mQueue = mLooper.mQueue; //訊息佇列,來自Looper物件 mCallback = callback;//回撥方法 mAsynchronous = async; //設定訊息是否為非同步處理方式 }
對於Handler的無參構造方法,預設採用當前執行緒TLS中的Looper物件,並且callback回撥方法為null,且訊息為同步處理方式。只要執行的Looper.prepare()方法,那麼便可以獲取有效的Looper物件。
有參構造
//Handler與傳入的Looper進行繫結 public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
Handler類在構造方法中,可指定Looper,Callback回撥方法以及訊息的處理方式(同步或非同步)
訊息分發
在Looper.loop()中,當發現有訊息時,呼叫訊息的目標handler,執行dispatchMessage()方法來分發訊息。
public void dispatchMessage(Message msg) { //呼叫Handler.post系列方法時,會給Message的callback賦值, //所以在分發訊息的時候會呼叫handleCallback(msg) if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { //當Handler設定了回撥物件Callback變數時,回撥方法handleMessage(); if (mCallback.handleMessage(msg)) { return; } } //Handler自身的回撥方法handleMessage() 需子類實現相應邏輯 handleMessage(msg); } }
訊息傳送
檢視Handler的所有的訊息傳送入口,包括post()
系列方法和sendMessage()
系列方法,你會發送最終都會到MessageQueue.enqueueMessage()
;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //設定Message的消費Handler為當前Handler msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } //uptimeMillis = SystemClock.uptimeMillis() + delayMillis //SystemClock.uptimeMillis() 代表的是自系統啟動開始從0開始的到呼叫該方法時相差的毫秒數 //比起System.currentTimeMillis()更加可靠,因為System.currentTimeMillis()是跟系統時間強關聯的, //修改了系統時間,System.currentTimeMillis()就會發生變化.
訊息回收
removeMessages
public final void removeMessages(int what) { mQueue.removeMessages(this, what, null); }
removeCallbacksAndMessages
public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); }
實質上都是呼叫的MessageQueue
的方法來達到目的的
MessageQueue
MessageQueue是訊息機制的Java層和C++層的連線紐帶,大部分核心方法都交給native層來處理,其中比較重要兩個方法
private native void nativePollOnce(long ptr, int timeoutMillis);//阻塞loop迴圈 private native static void nativeWake(long ptr);//喚醒loop迴圈
next()
提取下一條message,如果有
enqueueMessage
按照引數when
來新增訊息刀佇列中
removeMessages
移除訊息
篇幅有限,MessageQueue的各個方法就不詳細說明
訊息池
在程式碼中,可能經常看到recycle()方法,咋一看,可能是在做虛擬機器的gc()相關的工作,其實不然,這是用於把訊息加入到訊息池的作用。這樣的好處是,當訊息池不為空時,可以直接從訊息池中獲取Message物件,而不是直接建立,提高效率。
靜態變數sPool
的資料型別為Message,通過next成員變數,維護一個訊息池;靜態變數MAX_POOL_SIZE
代表訊息池的可用大小;訊息池的預設大小為50。
其他補充
一個執行緒中可以有多個Handler
,但是隻能有一個Looper
MessageQueue是有序的
子執行緒可以建立Handler物件
不可以在子執行緒中直接呼叫 Handler 的無參構造方法,因為Handler
在建立時必須要繫結一個Looper
物件,有兩種方法繫結
- 先呼叫 Looper.prepare() 在當前執行緒初始化一個 Looper
Looper.prepare(); Handler handler = new Handler(); // .... // 這一步必須 Looper.loop();
- 通過構造方法傳入一個 Looper
Looper looper = .....;//這裡可以傳入主執行緒的looper Handler handler = new Handler(looper);
知識補充
ThreadLocal 淺析
ThreadLocal
是一個執行緒內部的資料儲存類Thread Local Storage,簡稱為TLS),作用域限制線上程之內.即A執行緒
儲存的資料,不能被B執行緒
使用。
ThreadLocal
在Android原始碼中的表現主要在Looper
、ActivityThread
以及AMS
.
使用示例
public class ThreadLocalTest extends AppCompatActivity { private static final String TAG = "ThreadLocalTest"; private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBooleanThreadLocal.set(true); Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); new Thread("Thread#1") { @Override public void run() { mBooleanThreadLocal.set(false); Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); }; }.start(); new Thread("Thread#2") { @Override public void run() { Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); }; }.start(); } } //輸出結果 //[Thread#main]mBooleanThreadLocal=true //[Thread#1]mBooleanThreadLocal=false //[Thread#2]mBooleanThreadLocal=null
從結果來看,都是呼叫的mBooleanThreadLocal.get()
方法,得到的值卻不一樣,這時因為ThreadLocal
會在各自的執行緒分別建立一個數據儲存空間(ThreadLocalMap
),分別儲存各個執行緒中的資料.
接下里跟著原始碼看下ThreadLocal的實現
ThreadLocal的public方法,只有三個 set, get ,remove
ThreadLocal#set
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap ThreadLocalMap
至於ThreadLocalMap
如何儲存資料,這裡就不展開說了。
ThreadLocal#get
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
這段程式碼也十分簡單,無外乎將之前set進去的資料拿出來。