今天給大家講解的是在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類等,關于這些知識,大家可以查閱相關資料,這里就不做講解了,因為他們的實現其實跟我們前面說的是一樣的。
文章已同步到我的CSDN博客:http://blog.csdn.net/u010177022/article/details/63278070