Android訊息機制之Looper、Handler、MessageQueen
Android訊息機制之Looper、Handler、MessageQueen
本篇文章包括以下內容:
前言
Android訊息機制可以說是我們android工程師面試題中的必考題,弄懂它的原理是我們避不開的任務,所以長痛不如短痛,花點時間幹掉他,廢話不多說,開車啦
Android訊息機制的簡介
在安卓開發中,常常會遇到獲取資料後更新UI的問題,比如:在獲取網路資訊後,需要彈出一個Toast
HttpUtils.doGet("https://www.so.com/", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Toast.makeText(OkHttpActivity.this ,"",Toast.LENGTH_SHORT).show();
}
});
這個時候程式就會報以下的錯誤
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
這是因為Android規定了只允許UI執行緒修改Activity裡的UI元件,而我們剛才的操作在子執行緒中修改Activity裡的UI元件,才會導致UI操作的執行緒不安全,並報出錯誤。為了保證Android的UI操作是執行緒安全的,Android提供了Handler訊息傳遞機制來解決這個問題
Android訊息機制的使用
在獲取網路資訊後,需要彈出一個Toast,正確做法是
private static final int MAKE_TOAST = 0x01;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MAKE_TOAST:
Toast.makeText(OkHttpActivity.this,"",Toast.LENGTH_SHORT).show();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gallery);
HttpUtils.doGet("https://www.so.com/", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
mHandler.sendEmptyMessageDelayed(MAKE_TOAST,200);
}
});
}
在子執行緒中通過Handler傳送訊息,該訊息會在Hanlder中的handleMessage()中被解析,並進行相對應的UI元件更新
Android訊息機制的相關概念
一、相關概念的解釋
- 主執行緒(UI執行緒)
- 定義:當程式第一次啟動時,Android會同時啟動一條主執行緒(Main Thread)
- 作用:主執行緒主要負責處理與UI相關的事件
- Message(訊息)
- 定義:Handler接收和處理的訊息物件(Bean物件)
- 作用:通訊時相關資訊的存放和傳遞
- ThreadLocal
- 定義:執行緒內部的資料儲存類
- 作用:負責儲存和獲取本執行緒的Looper
- Message Queue(訊息佇列)
- 定義:採用單鏈表的資料結構來儲存訊息列表
- 作用:用來存放通過Handler發過來的Message,按照先進先出執行
- Handler(處理者)
- 定義:Message的主要處理者
- 作用:負責傳送Message到訊息佇列&處理Looper分派過來的Message
- Looper(迴圈器)
- 定義:扮演Message Queue和Handler之間橋樑的角色
- 作用:
訊息迴圈:迴圈取出Message Queue的Message
訊息派發:將取出的Message交付給相應的Handler
二、圖片解讀它們之間的關係
三、文字解讀它們之間的關係
Looper中存放有MessageQueen,MessageQueen中又有很多Message,當我們的Handler傳送訊息的時候,會獲取當前的Looper,並在當前的Looper的MessageQueen當中存放我們傳送的訊息,而我們的MessageQueen也會在Looper的帶動下,一直迴圈的讀取Message資訊,並將Message資訊傳送給Handler,並執行HandlerMessage()方法
其實這是一個迴圈的過程,讀懂這句話和看懂圖解很重要,會給我們下面的原始碼分析帶來很大的幫助,所以建議大家先讀懂前面的內容
Android訊息機制的通訊流程
這裡採用網上的一張圖進行說明,個人感覺圖片概括得很好,就沒必要再去造同樣的輪子了,在新視窗開啟可瀏覽大圖
Looper原始碼分析
一、根據上面的例子,為什麼Handler可以在主執行緒中直接可以使用呢?
因為主執行緒(UI執行緒)的Looper在應用程式開啟時建立好了,即在ActivityThread.main方法中建立的,該函式為Android應用程式的入口
public static void main(String[] args) {
...
Process.setArgV0("<pre-initialized>");
//1. 建立訊息迴圈Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
//2. 執行訊息迴圈
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper中最為重要的兩個方法:
- Looper.prepareMainLooper():該方法是Looper物件的初始化
- Looper.loop():該方法會迴圈取出Message Queue的Message,將取出的Message交付給相應的Handler(Looper的作用就體現在這裡)
二、Looper.prepareMainLooper()
//在主執行緒中初始化Looper
public static void prepareMainLooper() {
//在這裡會呼叫prepare(boolean quitAllowed)方法
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//看下prepare(boolean quitAllowed)方法
private static void prepare(boolean quitAllowed) {
//判斷sThreadLocal是否為null,否則丟擲異常
//即Looper.prepare()方法不能被呼叫兩次
//也就是說,一個執行緒中只能對應一個Looper例項
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//初始化Looper物件設定到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
//看下Looper的構造方法
private Looper(boolean quitAllowed) {
//建立了一個MessageQueue(訊息佇列)
//這說明,當建立一個Looper例項時,會自動建立一個與之配對的MessageQueue(訊息佇列)
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
整個Looper的初始化準備工作就完了,這裡做了哪幾件事:
- Looper的建立會關聯一個MessageQueen的建立
- Looper物件只能被建立一次
- Looper物件建立後被存放在sThreadLocal中
三、Looper.loop()
public static void loop() {
//myLooper()方法作用是返回sThreadLocal儲存的Looper例項,如果me為null,loop()則丟擲異常
//也就是說loop方法的執行必須在prepare方法之後執行
//也就是說,訊息迴圈必須要先線上程當中建立Looper例項
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取looper例項中的mQueue(訊息佇列)
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//進入訊息迴圈
for (;;) {
//next()方法用於取出訊息佇列裡的訊息
//如果取出的訊息為空,則執行緒阻塞
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//訊息派發:把訊息派發給msg的target屬性,然後用dispatchMessage方法去處理
//Msg的target其實就是handler物件,下面會繼續分析
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//釋放訊息佔據的資源
msg.recycleUnchecked();
}
}
整個Looper的迴圈過程就完了,這裡做了哪幾件事:
- 取出Looper和MessageQueen
- 進入訊息迴圈,有訊息則分發出去
- 訊息資源的回收
四、Looper的退出
當然Looper也提供了兩個方法可以退出一個Looper:
- quit():quit會直接退出Looper
- quitSafety():quitSafety只是設定一個退出標記,然後把訊息佇列中的已有訊息處理完畢後退出Looper
MessageQueen原始碼分析
一、由於MessageQueen是用來存放Message的,那麼是如何儲存Message的呢?
由於Handler使用Post()方法將Message傳遞到MessageQueen中,在MessageQueen中會使用enqueueMessage()方法儲存Message,其實現的方式是通過單鏈表的資料結構來儲存訊息列表
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) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
整個進佇列的過程就完了,這裡做了哪幾件事:
- 首先判斷訊息佇列裡有沒有訊息,沒有的話則將當前插入的訊息作為隊頭,並且這時訊息佇列如果處於等待狀態的話則將其喚醒
- 若是在中間插入,則根據Message建立的時間進行插入
二、既然MessageQueen存了訊息之後,是如何提供取出來的方法的呢?
我們知道存訊息是Handler存進來的,那麼取訊息就應該是Looper中取了,從Looper的原始碼可以看出,訊息就是在Looper中取出的,其實現是用MessageQueen裡面的next()方法
Message next() {
......
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,這時候訊息佇列處於等待狀態。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//按照我們設定的時間取出訊息
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
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 {
// 如果訊息佇列中沒有訊息,將nextPollTimeoutMillis設為-1,下次迴圈訊息佇列則處於等待狀態
nextPollTimeoutMillis = -1;
}
//退出訊息佇列,返回null,這時候Looper中的訊息迴圈也會終止。
if (mQuitting) {
dispose();
return null;
}
......
}
.....
}
}
三、在MessageQueen存訊息的媒介當然是通過Message物件啦,那這個Message物件又是什麼呢?
其實這個Message就是用來儲存Message中各種資訊的Bean物件,從原始碼中可以其屬性,這裡例舉我們常用的幾個
public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when;
Bundle data;
Handler target;
Runnable callback;
Handler原始碼分析
一、Handler的建立
Handler的建立會關聯一個Looper物件,而Looper物件是關聯著MessageQueen物件,所以在Handler建立時候,取出Looper和MessageQueen
public Handler(Callback callback, boolean async) {
...
//取出Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//取出Looper中的MessageQueen
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
前面我們也說過了Looper是存放在ThreadLocal裡面的,可以看到下面的原始碼就知道了
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
整個建立的過程就完了,這裡做了哪幾件事:
- 取出Looper
- 取出Looper中的MessageQueen
二、Handler傳送訊息
第一種方式:sendMessage(Message msg)
//從這裡開始
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
//往下追蹤
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
//往下追蹤
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//往下追蹤
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//直接獲取MessageQueue
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);
}
//呼叫sendMessage方法其實最後是呼叫了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//為msg.target賦值為this,也就是把當前的handler作為msg的target屬性
//如果大家還記得Looper的loop()方法會取出每個msg然後執行msg.target.dispatchMessage(msg)去處理訊息,其實就是派發給相應的Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//最終呼叫queue的enqueueMessage的方法,也就是說handler發出的訊息,最終會儲存到訊息佇列中去
return queue.enqueueMessage(msg, uptimeMillis);
}
第二種方式:post(Ruunable r)
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
其實post()方法最終也會儲存到訊息佇列中去,和上面不同的是它傳進來的一個Runnable物件,執行了getPostMessage()方法,我們往下追蹤
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
實質上就是將這個Runnable儲存在Message的變數中,這就導致了我們下面處理訊息的時候有兩種不同方案
三、Handler處理訊息
你還記得前面所說Looper中msg.target.dispatchMessage()方法嗎?這個方法就是呼叫Handler的dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//1. post()方法的處理方法
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//2. sendMessage()方法的處理方法
handleMessage(msg);
}
}
//1. post()方法的最終處理方法
private static void handleCallback(Message message) {
message.callback.run();
}
//2. sendMessage()方法的最終處理方法
public void handleMessage(Message msg) {
}
整個處理的過程就完了,這裡做了哪幾件事:
- post()方法的處理方法就是將傳進來的Runnable執行run()方法
- sendMessage()方法的處理方法就是執行handleMessage()空方法,這也是我們為什麼要在Handler重寫這個方法的原因
面試題
一、請解釋下Android通訊機制中Message、Handler、MessageQueen、Looper的之間的關係?
首先,是這個MessageQueen,MessageQueen是一個訊息佇列,它可以儲存Handler傳送過來的訊息,其內部提供了進隊和出隊的方法來管理這個訊息佇列,其出隊和進隊的原理是採用單鏈表的資料結構進行插入和刪除的,即enqueueMessage()方法和next()方法。這裡提到的Message,其實就是一個Bean物件,裡面的屬性用來記錄Message的各種資訊。
然後,是這個Looper,Looper是一個迴圈器,它可以迴圈的取出MessageQueen中的Message,其內部提供了Looper的初始化和迴圈出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper會關聯一個MessageQueen,而且將Looper存進一個ThreadLocal中,在loop()方法中,通過ThreadLocal取出Looper,使用MessageQueen的next()方法取出Message後,判斷Message是否為空,如果是則Looper阻塞,如果不是,則通過dispatchMessage()方法分發該Message到Handler中,而Handler執行handlerMessage()方法,由於handlerMessage()方法是個空方法,這也是為什麼需要在Handler中重寫handlerMessage()方法的原因。這裡要注意的是Looper只能在一個執行緒中只能存在一個。這裡提到的ThreadLocal,其實就是一個物件,用來在不同執行緒中存放對應執行緒的Looper。
最後,是這個Handler,Handler是Looper和MessageQueen的橋樑,Handler內部提供了傳送Message的一系列方法,最終會通過MessageQueen的enqueueMessage()方法將Message存進MessageQueen中。我們平時可以直接在主執行緒中使用Handler,那是因為在應用程式啟動時,在入口的main方法中已經預設為我們建立好了Looper。
這段面試題答案是自己寫的,如果有什麼不妥,歡迎留言一起學習
結語
學習完了訊息機制,回過頭來看還是挺簡單的。由於每個人都有懼怕困難的天性,一開始我也是看不懂,很怕看原始碼,但是我還是堅持了2天時間,藉助書本知識和網上的知識,將這個訊息機制給克服了。慢慢的,我們會將養成這種習慣,那麼你離大神的腳步就不遠了,加油吧