Android非同步處理三:Handler+Looper+MessageQueue深入詳解
在《Android非同步處理一:使用Thread+Handler實現非UI執行緒更新UI介面》中,我們講到使用Thread+Handler的方式來實現介面的更新,其實是在非UI執行緒傳送訊息到UI執行緒,通知UI執行緒進行介面更新,這一篇我們將深入學習Android執行緒間通訊的實現原理。
概述:Android使用訊息機制實現執行緒間的通訊,執行緒通過Looper建立自己的訊息迴圈,MessageQueue是FIFO的訊息佇列,Looper負責從MessageQueue中取出訊息,並且分發到訊息指定目標Handler物件。Handler物件繫結到執行緒的區域性變數Looper,封裝了傳送訊息和處理訊息的介面。
例子:在介紹原理之前,我們先介紹Android執行緒通訊的一個例子,這個例子實現點選按鈕之後從主執行緒傳送訊息"hello"到另外一個名為” CustomThread”的執行緒。
LooperThreadActivity.java
main.xmlpackage com.zhuozhuo; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; public class LooperThreadActivity extends Activity{ /** Called when the activity is first created. */ private final int MSG_HELLO = 0; private Handler mHandler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); new CustomThread().start();//新建並啟動CustomThread例項 findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {//點選介面時傳送訊息 String str = "hello"; Log.d("Test", "MainThread is ready to send msg:" + str); mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//傳送訊息到CustomThread例項 } }); } class CustomThread extends Thread { @Override public void run() { //建立訊息迴圈的步驟 Looper.prepare();//1、初始化Looper mHandler = new Handler(){//2、繫結handler到CustomThread例項的Looper物件 public void handleMessage (Message msg) {//3、定義處理訊息的方法 switch(msg.what) { case MSG_HELLO: Log.d("Test", "CustomThread receive msg:" + (String) msg.obj); } } }; Looper.loop();//4、啟動訊息迴圈 } } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:text="傳送訊息" android:id="@+id/send_btn" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout>
Log列印結果:
原理:
我們看到,為一個執行緒建立訊息迴圈有四個步驟:
1、 初始化Looper
2、 繫結handler到CustomThread例項的Looper物件
3、 定義處理訊息的方法
4、 啟動訊息迴圈
下面我們以這個例子為線索,深入Android原始碼,說明Android Framework是如何建立訊息迴圈,並對訊息進行分發的。
1、 初始化Looper : Looper.prepare()
Looper.java
private static final ThreadLocal sThreadLocal = new ThreadLocal();
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
一個執行緒在呼叫Looper的靜態方法prepare()時,這個執行緒會新建一個Looper物件,並放入到執行緒的區域性變數中,而這個變數是不和其他執行緒共享的(關於ThreadLocal的介紹)。下面我們看看Looper()這個建構函式:
Looper.java
final MessageQueue mQueue;
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
可以看到在Looper的建構函式中,建立了一個訊息佇列物件mQueue,此時,呼叫Looper. prepare()的執行緒就建立起一個訊息迴圈的物件(此時還沒開始進行訊息迴圈)。
2、 繫結handler到CustomThread例項的Looper物件 : mHandler= new Handler()
Handler.java
final MessageQueue mQueue;
final Looper mLooper;
public Handler() {
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());
}
}
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 = null;
}
Handler通過mLooper = Looper.myLooper();繫結到執行緒的區域性變數Looper上去,同時Handler通過mQueue =mLooper.mQueue;獲得執行緒的訊息佇列。此時,Handler就繫結到建立此Handler物件的執行緒的訊息佇列上了。
3、定義處理訊息的方法:Override public void handleMessage (Message msg){}子類需要覆蓋這個方法,實現接受到訊息後的處理方法。
4、啟動訊息迴圈 : Looper.loop()
所有準備工作都準備好了,是時候啟動訊息迴圈了!Looper的靜態方法loop()實現了訊息迴圈。
Looper.java
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
while (true) {
Message msg = queue.next(); // might block
//if (!me.mRun) {
// break;
//}
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf("Looper", "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.recycle();
}
}
}
while(true)體現了訊息迴圈中的“迴圈“,Looper會在迴圈體中呼叫queue.next()獲取訊息佇列中需要處理的下一條訊息。當msg != null且msg.target != null時,呼叫msg.target.dispatchMessage(msg);分發訊息,當分發完成後,呼叫msg.recycle();回收訊息。
msg.target是一個handler物件,表示需要處理這個訊息的handler物件。Handler的void dispatchMessage(Message msg)方法如下:
Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可見,當msg.callback== null 並且mCallback == null時,這個例子是由handleMessage(msg);處理訊息,上面我們說到子類覆蓋這個方法可以實現訊息的具體處理過程。
總結:從上面的分析過程可知,訊息迴圈的核心是Looper,Looper持有訊息佇列MessageQueue物件,一個執行緒可以把Looper設為該執行緒的區域性變數,這就相當於這個執行緒建立了一個對應的訊息佇列。Handler的作用就是封裝傳送訊息和處理訊息的過程,讓其他執行緒只需要操作Handler就可以發訊息給建立Handler的執行緒。由此可以知道,在上一篇《Android非同步處理一:使用Thread+Handler實現非UI執行緒更新UI介面》中,UI執行緒在建立的時候就建立了訊息迴圈(在ActivityThread的public static final void main(String[] args)方法中實現),因此我們可以在其他執行緒給UI執行緒的handler傳送訊息,達到更新UI的目的。