問個問題:如果兩個handler共享一個Looper,那麼HandlerA發送的消息,HandlerB會不會收到囁?
如果你不知道,那就看看我這篇文章,如果知道就別看了。。。
Handler大家一定太熟悉了,就是發送Message的,然後在handler關聯的線程裏處理Message。一般用來通知消息或者切換線程工作之類的。
一個Thread對應一個Looper,Looper就是一個循環體,Looper的作用就是給Thread用來循環處理消息的。當然如果你的Thread創建不需要sendMessage什麼的,那當然就不需要創建一個Looper,比如你只是打印個字符串而已。。。
Looper既然是循環處理消息的話,那Looper中肯定有個Message的隊列,沒錯,就是MessageQueue。MessageQueue就是記錄Message隊列的,採用鏈表的方式,並且記錄列表頭部指針(Java沒有指針的概念,就是用個變量記錄第一個Message。。。)。
看下Looper使用的例子:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
這是個很簡單的例子,在創建Thread的時候,先準備好Looper,只需要調用Looper.prepare()就可以了,看下prepare方法:
public static void prepare() {
prepare(true);
}
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));
}
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
大概就是得到當前的Thread,然後new一個Looper關聯到Thread上。接下來就是生命一個Handler,看下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());
}
}
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.myLooper得到,然後記錄Looper和Looper的MessageQueue,當然你可以調用Handler其他的構造函數來指定Looper等。
然後調用Looper.loop。loop函數中有一個死循環,是不斷的讀取MessageQueue中的Message來處理,如果隊列爲空,則該死循環會阻塞,知道有新的Message到來纔會喚起。
講了這麼多,該講講主人公Message了,Message主要有以下的參數:
public final class Message implements Parcelable {
//Message傳遞的一些參數
public int what;
public int arg1;
public int arg2;
public Object obj;
//記錄發送方
public Messenger replyTo;
public int sendingUid = -1;
//記錄當前Message是不是正在被使用,因爲Message可以重複利用,所以需要有變量記錄
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/** If set message is asynchronous */
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
//在MessageQueue中排序時是通過該值來排序
long when;
/*package*/ Bundle data;
//記錄該Message是要發送給哪個Handler來處理
Handler target;
/*package*/ Runnable callback;
//剛剛提到在MessageQueue中是以鏈表的方式記錄,所以next相當於指針。
/*package*/ Message next;
}
從參數中,可以將Message分爲兩類,一種是攜帶參數信息的(what, arg1, arg2等),一種是攜帶runnable的。後者用handler執行的時候,就是直接調用runnable.run方法。對應到handler的話就是兩種發消息的方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
如果調用post的話,會將runnable封裝到Message中。
那如何創建一個Message呢?
可以通過調用Message.obtain(…)或者handler.obtainMessage(…)來調用,都是一樣的,handler.obtainMessage最後也都是調用Message裏面的方法。
在創建Message的時候,要指定Message的target是誰!!!當從MessageQueue中取出來會將該message發給message.target來處理。所以如果兩個handler共享一個Loop,通過HandlerA發送的消息只能由HandlerA來處理,HandlerB不會收到。
下面問個問題:
如何在一個子線程中彈toast?
是這樣麼?
new Thread(new Runnable() {
@Override
public void run() {
Toast.makeText(TestActivity.this,"lall",Toast.LENGTH_LONG).show();
}
}).start();
no,no,no。當你這麼調的時候會發現出錯了。這裏要提醒下Toast中用到了handler。所以如果你直接像上面這麼寫會出錯,因爲在該子線程中Looper還沒prepare好,所以創建handler的時候會出錯。所以這就要引入一個新的Thread,就是handlerThread,該thread在創建的時候就會自己創建一個Looper並綁定,所以你可以這麼做:
HandlerThread t = new HandlerThread("thread");
t.start();
Handler handler = new Handler(t.getLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(TestActivity.this,"ddd",Toast.LENGTH_LONG).show();
}
});