今天給大家講解的是在Android開發中如何使用Handler和使用Handler時要注意的一些細節問題。本篇文章是作為我上一篇文章《Android原始碼分析--Handler機制的實現與工作原理》的補充。雖然是補充,但是兩篇文章所講的內容不同:一個是原理的分析,一個是使用的講解。如果你還沒有看過上一篇文章,建議你先去看一看,只有瞭解了Handler的原理,才能更好的使用它。而且我們今天所講的內容也是建立在上一篇文章的基礎上的。
Handler的最大作用就是執行緒的切換,至於Handler切換執行緒的原理和實現,上一篇文章已有詳細的講解,這裡就不多說了。下面我們看如何把一個訊息從一個執行緒傳送到另一個執行緒。
//在主執行緒建立一個Handler物件。
//重寫Handler的handleMessage方法,這個就是接收並處理訊息的方法。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//msg就是子執行緒傳送過來的訊息。
}
};
//開啟一個子執行緒
new Thread(new Runnable() {
@Override
public void run() {
//在子執行緒傳送一個訊息。
Message msg = new Message();
handler.sendMessage(msg);
}
}).start();複製程式碼
上面就是一個簡單的Handler使用例子。我們在主執行緒建立一個Handler,他的handleMessage()方法是執行在主執行緒的,當我們在子執行緒傳送一個訊息的時候,handleMessage()會接收到訊息,這樣我們就把訊息由子執行緒傳送到了主執行緒。上面的程式碼中,Handler傳送的訊息是一個空訊息,什麼資料也沒有,如果我們只是單純的傳送一個空訊息,可以使用Handler自己的傳送空訊息的方法:
handler.sendEmptyMessage(what);複製程式碼
這兩種傳送空訊息的效果是一樣的。至於這裡的what是什麼我們後面會說到。
Handler也可以傳送帶有資料的訊息。Message物件有一個Object型別的obj屬性,就是用來攜帶訊息資料的。我們只需要把要傳送的資料賦值給obj就可以了,然後在處理訊息的時候再把資料取出來。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.i(TAG,"Handler 傳送過來的訊息是:" + msg.obj);
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.obj = "我是訊息資料";
handler.sendMessage(msg);
}
}).start();複製程式碼
上面的程式碼中,msg攜帶一個字串資料:"我是訊息資料",在handleMessage()方法中把這個資料取出來。除了obj以外,Message還有兩個int型別的屬性:arg1、arg2可以用來傳送一些簡單的資料。
現在我們看到的所有的訊息都是在Handler的handleMessage()方法中處理,如果我要傳送很多個訊息,每個訊息的資料都不一樣,訊息的處理邏輯也不一樣,那麼在handleMessage()方法中如何去判斷哪個訊息是哪個呢?這時Message的what屬性就派上用場了,what屬性就是上面傳送空訊息時我們看到的what,它是一int型別,它是用來標識訊息的。
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i(TAG, "第一個訊息是:" + msg.obj);
break;
case 2:
Log.i(TAG, "第二個訊息是:" + msg.obj);
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg1 = new Message();
msg1.obj = "第一個訊息";
msg1.what = 1;
handler.sendMessage(msg1);
Message msg2 = new Message();
msg2.obj = "第二個訊息";
msg2.what = 2;
handler.sendMessage(msg2);
}
}).start();複製程式碼
上面我們在子執行緒中傳送了兩個訊息,並且給兩個訊息設定了它的what,在處理訊息的時候就可以通過msg的what來判斷是哪個訊息了。
除了用sendMessage傳送Message訊息以外,Handler還可以post一個Runnable。
new Thread(new Runnable() {
@Override
public void run() {
//在子執行緒post一個Runnable物件
handler.post(new Runnable() {
@Override
public void run() {
//這裡是訊息處理的方法
//這裡執行在主執行緒。
}
});
}
}).start();複製程式碼
其實post()方法和sendMessage()方法的邏輯是一樣的,post()方法中的Runnable會被封裝成Message,然後傳送出去。下面看它的原始碼:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}複製程式碼
從原始碼中我們看到,Runnable會被封裝成Message,然後還是用sendMessage的方式傳送出去。而且封裝的邏輯也很簡單,直接把Runnable賦值給Message的callback就可以了。在上一篇文章中我介紹了訊息處理的三個方法,Message自己的callback優先順序是最高的,所以這個訊息是由自己的callback也就是Runnable的run()方法處理的,而不再是Handler的handleMessage()方法。
前面我們所舉的例子中,都是訊息的處理是在主執行緒的。其實不然,訊息的處理事實上是執行在負責管理訊息佇列(MessageQueue)的Looper所在的執行緒的,而不一定是主執行緒。這一點我在上一篇文章也反覆的提到了,如果還不瞭解這一點的請認真閱讀一下我的上一篇文章。
如果我們建立一個Handler物件而沒有給它指定它的Looper,那麼它預設會使用當前執行緒的Looper。前面我們所舉的例子都是在主執行緒直接建立Handler物件的,所以它的Looper就是主執行緒的Looper,它的訊息自然也就是在主執行緒處理了。那麼我們也可以在子執行緒使用Handler,下面可一個例子:
//宣告Handler;
Handler handler;
new Thread(new Runnable() {
@Override
public void run() {
//建立當前執行緒的Looper
Looper.prepare();
//在子執行緒建立handler物件
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//這裡是訊息處理,它是執行在子執行緒的
}
};
//開啟Looper的訊息輪詢
Looper.loop();
}
}).start();
mBanner.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在主執行緒傳送一個訊息到子執行緒
Message msg = new Message();
handler.sendMessage(msg);
}
});複製程式碼
在上面的例子中,我們在子執行緒建立handler物件,handler的Looper就是子執行緒的Looper,所以訊息的處理也就是在子執行緒處理的。這就是在子執行緒使用Handler的方式。在這裡有幾個需要注意的點:(很重要)
1、在子執行緒使用Handler前一定要先為子執行緒建立Looper,建立的方式是直接呼叫Looper.prepare()方法。前面我們說過建立Handler物件時如果沒有給它指定Looper,那麼它預設會使用當前執行緒的Looper,而執行緒預設是沒有Looper的,所以使用前一定要先建立Looper。
2、在同一個執行緒裡,Looper.prepare()方法不能被呼叫兩次。因為同一個執行緒裡,最多隻能有一個Looper物件。
3、只有呼叫了Looper.loop()方法,Handler機制才能正常工作。 Looper負責管理MessageQueue,它的loop()方法負責從MessageQueue裡取出訊息並交給Handler處理,所以如果沒有呼叫Looper.loop()方法,訊息就不會被取出和處理。
4、Looper.loop()方法一定要在呼叫了Looper.prepare()方法之後呼叫。那是因為如果當前執行緒還沒有Looper,是不能呼叫Looper.loop()方法開啟訊息輪詢的,否則會報錯。
5、不要在主執行緒呼叫Looper.prepare()方法。這是因為在Android系統建立主執行緒的時候就已經呼叫了Looper.prepare()方法和Looper.loop()方法,這也是為什麼我們在主執行緒使用Handler時不需要呼叫這兩個方法的原因。
6、當我們在子執行緒使用Handler時,如果Handler不再需要傳送和處理訊息,那麼一定要退出子執行緒的訊息輪詢。因為Looper.loop()方法裡是一個死迴圈,如果我們不主動結束它,那麼它就會一直執行,子執行緒也會一直執行而不會結束。退出訊息輪詢的方法是:
Looper.myLooper().quit();
Looper.myLooper().quitSafely();複製程式碼
上面的例子都是用執行緒自己的Looper來建立Handler,我們也可以用指定的Looper來建立Handler。
new Thread(new Runnable() {
@Override
public void run() {
//獲取主執行緒的Looper
Looper looper = Looper.getMainLooper();
//用主執行緒的Looper建立Handler
handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
//這裡是執行在主執行緒的
}
};
}
}).start();複製程式碼
上面的例子中,我們雖然是在子線建立Handler,但因為用的是主執行緒的Looper,所以訊息的處理是在主執行緒的,這跟在主執行緒建立Handler是一樣的。因為這裡並沒有使用到子執行緒的Looper,所以不要呼叫Looper.prepare()和Looper.loop()方法。
以上我們所說的是Handler切換執行緒的使用。Handler除了提供post()方法和sendMessage()方法以外,還提供了一系列的傳送訊息的方法。比如延時傳送訊息和定時傳送訊息:
//延時傳送訊息
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean postDelayed(Runnable r, long delayMillis);
//定時傳送訊息
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);複製程式碼
通過這些方法,可以實現延時執行方法和定時執行方法的功能。如下面的例子:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "延時1000毫秒列印");
}
},1000);複製程式碼
上面的例子並沒有涉及到執行緒的切換,只是利用了Handler延時傳送訊息的功能達到延時列印。所以Handler的使用不僅僅是切換執行緒。更多的方法使用就不一一舉例了。
小知識點:
1、使用Message.obtain()來獲取一個訊息。前面我們的例子中獲取一個訊息都是用new的方式直接建立,我這樣做只是為了方便大家的理解而已。在使用中不推薦用這種方式來獲取一個訊息,而是使用Message.obtain()方法。
Message msg = Message.obtain();複製程式碼
Android會為Message提供一個快取池,把使用過的訊息快取起來,方便下次使用。我們用Message.obtain()方法獲取一個訊息時,會先從快取池獲取,如果快取池沒有訊息,才會去建立訊息。這樣做可以優化記憶體。
2、同一個Message不要傳送兩次。如下面的程式碼是有問題的:
//同一個Message傳送了兩次
Message msg = Message.obtain();
handler.sendMessage(msg);
handler.sendMessage(msg);複製程式碼
這是因為所以的訊息都是傳送到MessageQueue存放,然後等待處理的。如果一個Message物件在MessageQueue裡,再次把它存到MessageQueue時就會報錯。
3、Android已經提供了很多實現了Handler的類和方法,方便我們使用。如Activity類的runOnUiThread()方法,View的post()方法,HandlerThread類等,關於這些知識,大家可以查閱相關資料,這裡就不做講解了,因為他們的實現其實跟我們前面說的是一樣的。