在 android 中,不可以在子線程中更新 UI 的操作,否則會報錯或者異常資訊。
在這種情況下,我們會使用 Handler(在 UI 線程建立該對象) 接收子線程的訊息更新 UI.
可以看出,這是子線程通知主線程,而主線程沒有直接通知子線程,那麽我們如何做到這一點?這樣有什麼好處?
好處,很明顯。主線程中如果有些耗時的操作,我們可以讓子線程去做,這時,主線程就可以通知子線程了!
實現這種操作的方式有很多,今天主要想說說 HandlerThread 與 Handler 的配合使用方法。
在主線程中,可以建立 HandlerThread 對象:
HandlerThread mHandlerThread = new HandlerThread("sub_thread");
mHandlerThread.start();
這樣,就開啟了一個子線程,該線程的名字就是 sub_thread.
然後,我們實現一個繼承自 Handler 的 MyHandler 類:
class MyHandler extends Handler {
public MyHandler() {
super();
}
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// super.handleMessage(msg);
if(msg.what == MSG_WHAT) {
Bundle bundle = msg.getData();
String info = bundle.getString("main");
Log.d("mark", "我接受任務:" + info);
}
}
}
最後,使用 Message 來傳遞訊息,MyHandler 會根據情況處理 Message.
完整代碼:
package mark.zhang;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
public class HandlerThreadActivity extends Activity {
private static final int MSG_WHAT = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 建立對象,並啟動該線程
HandlerThread mHandlerThread = new HandlerThread("sub_thread");
mHandlerThread.start();
// 擷取 Looper 對象
MyHandler mHandler = new MyHandler(mHandlerThread.getLooper());
// 建立 Bundle 對象,傳遞資料
Bundle bundle = new Bundle();
bundle.putString("main", "我這邊事情太多,麻煩你幫忙處理一下!");
// 發送訊息
Message msg = new Message();
msg.what = MSG_WHAT;
msg.setData(bundle);
msg.setTarget(mHandler);
msg.sendToTarget();
Log.d("mark", "UI----" + "threadName: " + Thread.currentThread().getName() + ",threadId: " + Thread.currentThread().getId());
}
/**
* 該Handler調用 handleMessage方法運行在子線程
*
* @author mark
*/
class MyHandler extends Handler {
public MyHandler() {
super();
}
/* 這個構造方法必須有 */
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_WHAT) {
Bundle bundle = msg.getData();
// 接收訊息
String info = bundle.getString("main");
Log.d("mark", "handleMessage---" + "threadName: " + Thread.currentThread().getName() + ",threadId: " + Thread.currentThread().getId());
Log.d("mark", "我接受任務:" + info);
}
}
}
}
這裡,我們思考一個問題,代碼:
// 擷取 Looper 對象
MyHandler mHandler = new MyHandler(mHandlerThread.getLooper());
擷取 Looper 對象,使用的是 HandlerThread 對象的 getLooper 對象。為什麼不直接這樣:
// 擷取 Looper 對象
MyHandler mHandler = new MyHandler(getMainLooper());
如果這樣做的話,列印資訊如下:
D/mark (21852): UI----threadName: main,threadId: 1
D/mark (21852): handleMessage---threadName: main,threadId: 1
D/mark (21852): 我接受任務:我這邊事情太多,麻煩你幫忙處理一下!
可見,此時的 handleMessage 是在主線程中啟動並執行。每個主線程預設有一個 Looper,也就是說在主線程中,下面代碼是等效的:
MyHandler mHandler = new MyHandler(getMainLooper());
MyHandler mHandler = new MyHandler();
但是子線程需要手動擷取如:
MyHandler mHandler = new MyHandler(mHandlerThread.getLooper());
實現主線程、子線程雙向通訊可以在各自的線程中分別是用 Handler 來傳遞資訊和處理訊息。