Android Handler 訊息機制分析(一)
Handler,Message,MessageQueue,Looper,是android一種訊息處理機制,在android開發中經常會用到,當Handler建立後,會被繫結到它所在的執行緒上,處理訊息的成員及其功能如下:
Handler:傳送一個訊息(Message)去做特定任務
Message:代表一個要處理的任務;
MessageQueue:Message的佇列;
Looper:負責迴圈Message佇列,取出Message又傳給Handler處理。
它們之間關係如下:
每個執行緒中只有一個Looper,由
每個Looper中只有一個MessageQueue,因此一個執行緒中只有一個MessageQueue;
每個MessageQueue中可以有多個Message;
每個執行緒中可以有多個Handler;
四者關係如下圖:
接下來我們以開發中使用時的順序來分析它們之間的關係。
1.Looper
Looper,就是不斷迴圈讀取訊息給Handler。在開發過程中,更多的是在主執行緒中對Handler進行例項化,然後就可以呼叫sendMessage()方法傳送訊息了,不需要對Looper做額外的處理,但是如果我們在子執行緒進行例項化
至於為什麼在子執行緒中必須進行這樣的操作,而在主執行緒就不需要這樣的操作呢?這就先得從Handler說起了。class MyThread extends Thread{ @Override public void run() { //為該執行緒建立一個Looper Looper.prepare(); //例項化Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; //不斷迴圈讀取Message Looper.loop(); } }
1.1.子執行緒中Looper的獲取
Handler的構造方法如下:
public Handler() {
this(null, false);
}
直接看它另一個過載構造方法:
public Handler(Callback callback, boolean async) {
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());
}
}
//獲取當前執行緒的Looper
mLooper = Looper.myLooper();
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;
}
這裡很明顯了,在Handler例項化時,會首先獲取當前執行緒的Looper,如果不存在Looper,則丟擲Runtime異常,注意了,這裡是”當前執行緒”,這是什麼意思?下面進行分析。
再來看看Looper中的兩個靜態方法幹了什麼,prepare()方法如下:
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));
}
在prepare()方法中,獲取了Looper物件,並且維護了一個ThreadLocal存放每個執行緒的Looper物件,通過ThreadLocal將執行緒和Looper進行了繫結,從而解決了多執行緒問題,保證了每個執行緒只有一個Looper物件。因此,如果在子執行緒中不顯示呼叫prepare()方法,則由於該執行緒中沒有Looper物件,從而報出異常:Can't create handler inside thread that has not called Looper.prepare().
好了,再來看看Looper的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在構造方法中,首先例項化了MessageQueue物件,由於每個執行緒只有一個Looper,每個Looper又只會例項化一個MessageQueue,因此,每個執行緒也就只有一個MessageQueue。當在子執行緒中呼叫Looper.prepare()後,該子執行緒中就有Looper了,因此,就可以完成Handler的例項化了,再來看看Looper的loop()方法:
public static void loop() {
final Looper me = myLooper();
................
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);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
}
//回收資源
msg.recycleUnchecked();
}
}
在loop()方法中,設定了一個for死迴圈,不斷的從MessageQueue中讀取MessageQueue,如果Message為空,則return退出該方法。但是這不表示loop時如果沒有訊息,該方法就return了,mQueue.next()是個阻塞方法,也就是說,如果當前執行緒中還沒有訊息,該方法處於阻塞狀態,那麼什麼時候mQueue.next()會返回null呢?在分析MessageQueue的時候進行分析。
那麼為何在主執行緒中,為何可以不使用Looper.prepare()直接獲取Handler呢?這是因為,主執行緒已經做了這些操作了。
1.2.主執行緒中Looper的獲取
Android主執行緒,即UI執行緒,實際上就是ActivityThread,我們看看ActivityThread的main()方法:
public static void main(String[] args) {
.....................
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到,和子執行緒獲取Looper有一個小差別,在這裡,Looper使用了prepareMainLooper()方法例項化。此外,如果Looper.loop()執行退出,則丟擲異常。
Looper的實現不是特別複雜,實現原理就是內部維護一個ThreadLocal保證每一個執行緒只有一個Looper物件,但卻是整個訊息處理機制的基石,每個執行緒只有獲取了Looper後才可以獲取Handler。其主要工作就是不停的迴圈遍歷訊息,然後對訊息進行派遣。派遣給誰呢?是msg.target,也就是Handler了。
2.Handler
Handler作為訊息的發起者和訊息的最終處理者,也是這四者當中最常見的,它提供了許多的介面供我們開發時呼叫,下面開始分析Handler在訊息機制中的作用以及它的常用方法。
2.1.Handler構造方法
Handler中核心成員如下:
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
}
現在我們開始先從構造方法開始分析,Handler提供了七個構造方法,然而它們都的目的是一樣的,在構造方法中,都會初始化以上的核心成員變數,同時它們被final修飾,也就意味著一旦初始化就不能改變,mLooper是當前執行緒的Looper物件;mQueue代表當前執行緒的MessageQueue物件;Callback是Handler內部的一個介面,該介面和處理訊息有關,會在處理訊息時說明;mAsynchronous代表是否需要非同步執行,如果設定為true,則Handler中的操作會非同步進行,預設為false。在分析Looper的時候已經分析了兩個構造方法了,這裡再列舉出我們常用來例項化Handler的構造方法,如在android系統服務中會常常使用一個Looper物件例項化Handler,從而給Handler提供一個Looper,不使用預設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;
}
因此,不管是何種構造方法,其目的都是一樣的,就是初始化核心的成員屬性。
2.2.Handler傳送訊息方式的兩種方式
當獲取了Handler物件後,就可以傳送訊息了,就像我們最常用的sendMessage()方法一樣,Handler同樣提供了多種傳送訊息的方法,它們之間的聯絡和呼叫順序如下圖所示:
上圖中綠色代表HandlerAPI的開放介面,即我們可以呼叫的方法,紅色表示API中沒有開放的方法。從上圖中可以看出,傳送訊息的方法可以分為兩類,一類是sendXXX(Message)傳送訊息,另一種是postXXX(Runnable)傳送訊息,無論是哪種方式傳送訊息,最終都會走到enqueueMessage()方法中。在enqueueMessage()方法中,首先會將msg和當前Handler進行繫結,然後會將Message新增到訊息佇列中去,具體如何加入訊息對列,在MessageQueue中進行分析。該方法如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//當前Handler和當前msg進行繫結
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//呼叫到MessageQueue中加入訊息佇列
return queue.enqueueMessage(msg, uptimeMillis);
}
這兩種傳送訊息的方式有何區別呢?我們從原始碼中看起,先看一個sendXXXMessage()型別的方法:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
//設定Message what屬性
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
再來看看postXXX()型別的方法:
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
這裡呼叫了一個getPostMessage(r),那就再看看這個方法做了什麼:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
//設定Message callback屬性
m.callback = r;
return m;
}
可以看到,在sendXXX()方式傳送的訊息中,給訊息(Message)設定了的what屬性,而在postXXX()方法傳送的訊息中,是給訊息(Message)設定了callback屬性,其他並無區別。現在清楚這兩種方式傳送的訊息的區別了,那麼這樣做的根本目的是什麼呢?這就要在處理訊息的方法中進行分析了。
2.3.Handler處理訊息
當Handler傳送訊息後,會將其加入訊息佇列,我們在Looper中已經分析到,如果Looper在死迴圈中遍歷到Message,會進行如下操作:
msg.target.dispatchMessage(msg);
Msg.target是和當前Message繫結的Handler,因此,Handler中傳送完畢訊息後,最終對訊息進行接受和處理的還是在Handler中。dispatchMessage()方法如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在這個方法中,對訊息的處理方式進行決策:
1.判斷Message的callback屬性是否為空,如果不為空,呼叫handlerCallback()方法處理訊息;
2.如果Message的callback屬性為空,則判斷Handler的mCallback物件是否為空,如果不為空,使用mCallback.handleMessage()進行處理;
3.如果Message的callback屬性為空,同時Handler的mCallback物件也為空,則直接使用Handler的handlerMessage(msg)進行處理。
Message的callback是哪個呢?就是使用post(Runnable)方式傳送訊息傳入的Runnbale;Handler的Callback物件是什麼呢?是就是構造方法Handler(Callback callback)傳入的。
handlerCallback()方法如下:
private static void handleCallback(Message message) {
message.callback.run();
}
到這裡,終於明白了使用sendXXX(Message)和postXXX(Runnable)兩種方式傳送訊息的目的了,如果是send方式傳送訊息,那麼在處理訊息時,會使用handleMessage(msg)進行處理,具體使用Handler的該方法還是Handler.Callback的該方法,要看Handler是通過何種構造方法例項化;如果是post(Runnable)方式傳送訊息,那麼會在Runnable的run()方法中進行處理。
2.3.Handler.post(Runnable r)是執行在主執行緒呢還是在子執行緒?
問題:Handler.post(Runnable r)是執行在主執行緒呢還是在子執行緒?
這個問題經常困擾到我,我們這裡寫個小demo測試一下,首先我們在主執行緒例項化一個Handler,並使用Handler.post(Runnable r)傳送訊息:
public class MainActivity extends AppCompatActivity {
private MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyHandler = new MyHandler();
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMyHandler.post(new Runnable() {
@Override
public void run() {
Log.d("tag","Thread id:"+Thread.currentThread().getId()
+",Thread name:"+Thread.currentThread().getName());
}
});
}
});
}
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
執行列印Log如下:
可以看到,儘管使用了Runnable,但它是執行在主執行緒的。
接下來我們在子執行緒例項化Handler,並使用Handler.post(Runnable)傳送訊息:
public class MainActivity extends AppCompatActivity {
private MyHandler mMyHandler;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMyHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"Thread name:"
+Thread.currentThread().getName(),
Toast.LENGTH_SHORT).show();
Log.d("tag","Thread id:"+Thread.currentThread().getId()
+",Thread name:"
+Thread.currentThread().getName());
}
});
}
});
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
Looper.prepare();
mMyHandler = new MyHandler();
Looper.loop();
}
}
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
執行列印Log如下:
因此,Handler的postXXX(Runnable)方法接受訊息時的執行執行緒和Handler的執行緒有關,如果Handler來源於主執行緒,那麼在接受訊息時,Runnable的run()方法在主執行緒執行,如果Handler來源於子執行緒,則run()方法也在子執行緒執行。
3.Message
比起Looper和Handler,Message就相對簡單了,Message,就是為Handler而封裝的一個實體類,其實現了Parcelable介面,因此可以進行序列化和反序列化,同時還能跨程序傳遞。對於Message,只需要瞭解它常用的屬性和方法即可,接下來我們分析其屬性和常用方法。
3.1.Message物件的獲取
Message只提供了一個構造方法:
public Message() {
}
但是,在開發中並不建議使用構造方法類獲取Message物件,因為Message提供了靜態方法obtain()用於獲取Message物件,他有許多的過載形式,在獲取Message物件的同時,可以給Message設定對應的屬性,如:
public static Message obtain() {
synchronized (sPoolSync) {
//如果存在已回收的Message,複用它即可,否則重新例項化一個Message
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
在呼叫靜態方法obtain()時,首先會判斷是否存在回收的Message sPool,如果存在,則直接使用該物件,不再例項化新的Message物件,否則例項化Message,從而避免的不必要的物件佔用記憶體空間。
還有其他過載的obtain()方法用來獲取Message,但是這些過載方法中都會首先呼叫obtain()方法,比如obtain(Handler,int):
public static Message obtain(Handler h, int what) {
Message m = obtain();
m.target = h;
m.what = what;
return m;
}
可以通過obtain(Handler,int)直接給當前Message物件設定target和what屬性,如果是這種方式獲取的Message,就可通過這種方式來發送訊息:
//獲取Message物件
Message message = Message.obtain(mMyHandler,1);
//傳送訊息
message.sendToTarget();
sendToTarget()方法內部則通過Message的target傳送訊息,原始碼如下:
public void sendToTarget() {
target.sendMessage(this);
}
除了以上obtain()方法之外,還有五中型別的過載方法,它們的原理相同,都是根據傳入的引數提前對Message設定了對應的屬性。因此,在實際開發中使用要獲取Message時,不建議使用new Message()獲取例項,而是通過Message.obtain()獲取。
3.2.Message常用屬性
Message類中提供了一些屬性來攜帶資訊,有些對使用者開放,有些則沒有開放,接下來對這些屬性進行總結:
public int what:訊息碼
public int arg1:可以攜帶int值
public int agr2:可以攜帶int值
public int obj:可以攜帶一個物件,一般用於跨程序
/*package*/ int flags:標識是否需要非同步執行
/*package*/ long when:處理訊息時間點
/*package*/ Handler target:繫結的Handler
/*package*/ Runnable callback:Runnable物件,如果存在則在run()方法中處理訊息
到這裡為止,Handler、Looper、Message分析完畢了,現在就剩餘MessageQueue,由於MessageQueue涉及到JNI層,內容較多,因此在另一篇文中分析。