在《
Android Handler之訊息迴圈的深入解析》中談到了Handler是用於操作線程內部的訊息佇列,所以Handler可以用來線程間通訊ITC,這種方式更加安全和高效,可以大大減少同步的煩惱,甚至都可以不用syncrhonized。
線程間通訊ITC
正常情況下函數調用棧都會生存在同一個線程內,想要把執行邏輯交換到其他線程可以建立一個Thread,然後start()。另外一種方法就是用ITC,也即用訊息佇列來實現,線程需要把執行邏輯交到其他線程時就向另外的線程的訊息佇列發送一個訊息,發送訊息後函數就此結束返回,調用棧也停止。當訊息佇列中有了訊息時,線程會被喚醒來執行處理訊息,從而把執行邏輯從一個線程轉到另外一個線程。這就實現了線程間的通訊ITC,與進行間通訊IPC有十分類似的思想。
通常的做法都是,在主線程建立一個Handler,然後在建立線程中使用此Handler與主線程通訊。因為主線程的訊息佇列已經建好,所以直接建立Handler即可,建立的線程就可以直接使用。
有些情況,需要在多線程之間進行通訊,這就要為每個線程都建立MessageQueue和Handler,只要線程能訪問其他線程的Handler就可以與之通訊。
要正確的建立Handler,因為Handler要與線程綁定,所以在初始化Handler的時候就要注意:
如果給Handler指定Looper對象new Handler(Looper),那麼此Handler便綁定到Looper對象所在的線程中,Handler的訊息處理回調會在那個線程中執行。
如果建立線程時不指定Looper對象,那麼此Handler綁定到建立此Handler的線程內,訊息回調處理會在那個線程中執行,所以像下面的例子,如果這樣寫:複製代碼 代碼如下:private class CookServer extends Thread {
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
....
}
};
那麼,此mHandler會與建立此CookerServer的線程綁定,handleMessage也會運行於其中。顯然,如果是主線程調用new CookServer(),那麼mHandler其實是運行在主線程中的。正確的寫法應該是:複製代碼 代碼如下:private class CookServer extends Thread {
public void run() {
Looper.prepare();
// or new Handler(Looper.myLooper())
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
....
}
};
HandlerThread
如果要在一個線程中使用訊息佇列和Handler,Android API中已經有封裝好了的一個類HandlerThread,這個類已經做好了Looper的初始化工作,你需要做的就是重寫其onLooperPrepared()方法,在其中建立Handler:複製代碼 代碼如下:private class DeliverServer extends HandlerThread {
private Handler mHandler;
public DeliverServer(String name) {
super(name);
}
@Override
public void onLooperPrepared() {
mHandler = new Handler(getLooper()) {
public void handleMessage(Message msg) {
.....
}
};
}
}
執行個體
此執行個體類比了一個網路訂餐系統,客戶點擊“Submit order"來產生一個定單,主線程中負責收集定單,然後交由CookServer來製作,CookServer在製作完成後會交由DeliverServer來把食物運送到客戶,至此一個定單完成,同時CookServer和DeliverServer會更新狀態。
複製代碼 代碼如下:/**
* How to attach an Handler to a Thread:
* If you specify Looper object to Handler, i.e. new Handler(Looper), then the handler is attached to the thread owning
* the Looper object, in which handleMessage() is executed.
* If you do not specify the Looper object, then the handler is attached to the thread calling new Handler(), in which
* handleMessage() is executed.
* In this example, for class CookServer or DeliverServer, if you write this way:
* private class CookServer extends Thread {
private Handler mHandler;
private Looper mLooper;
public CookServer() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
....
}
start();
}
* then mHandler is attached to thread calling new CookServer(), which is the main thread, so mHandler.handleMessage() will
* be executed in main thread.
* To attach mHandler to its own thread, you must put it in run(), or after mLooper is created. For our example, providing
* mLooper or not won't matter, because new Handler() is called in run(), which is in a new thread.
*/
public class HandlerITCDemo extends ListActivity {
private static final int COOKING_STARTED = 1;
private static final int COOKING_DONE = 2;
private static final int DELIVERING_STARTED = 3;
private static final int ORDER_DONE = 4;
private ListView mListView;
private static final String[] mFoods = new String[] {
"Cubake",
"Donut",
"Eclaire",
"Gingerbread",
"Honeycomb",
"Ice Cream Sanwitch",
"Jelly Bean",
};
private ArrayList<String> mOrderList;
private TextView mGeneralStatus;
private Button mSubmitOrder;
private static Random mRandomer = new Random(47);
private int mOrderCount;
private int mCookingCount;
private int mDeliveringCount;
private int mDoneCount;
private Handler mMainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case COOKING_STARTED:
mCookingCount++;
break;
case COOKING_DONE:
mCookingCount--;
break;
case DELIVERING_STARTED:
mDeliveringCount++;
break;
case ORDER_DONE:
mDeliveringCount--;
mDoneCount++;
default:
break;
}
mGeneralStatus.setText(makeStatusLabel());
}
};
private CookServer mCookServer;
private DeliverServer mDeliverServer;
@Override
protected void onDestroy() {
super.onDestroy();
if (mCookServer != null) {
mCookServer.exit();
mCookServer = null;
}
if (mDeliverServer != null) {
mDeliverServer.exit();
mDeliverServer = null;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListView = getListView();
mOrderList = new ArrayList<String>();
mGeneralStatus = new TextView(getApplication());
mGeneralStatus.setText(makeStatusLabel());
mSubmitOrder = new Button(getApplication());
mSubmitOrder.setText("Submit order");
mSubmitOrder.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String order = mFoods[mRandomer.nextInt(mFoods.length)];
mOrderList.add(order);
mOrderCount = mOrderList.size();
mGeneralStatus.setText(makeStatusLabel());
setAdapter();
mCookServer.cook(order);
}
});
mListView.addHeaderView(mGeneralStatus);
mListView.addFooterView(mSubmitOrder);
setAdapter();
mCookServer = new CookServer();
mDeliverServer = new DeliverServer("deliver server");
}
private String makeStatusLabel() {
StringBuilder sb = new StringBuilder();
sb.append("Total: ");
sb.append(mOrderCount);
sb.append(" Cooking: ");
sb.append(mCookingCount);
sb.append(" Delivering: ");
sb.append(mDeliveringCount);
sb.append(" Done: ");
sb.append(mDoneCount);
return sb.toString();
}
private void setAdapter() {
final ListAdapter adapter = new ArrayAdapter<String>(getApplication(), android.R.layout.simple_list_item_1, mOrderList);
setListAdapter(adapter);
}
private class CookServer extends Thread {
private Handler mHandler;
private Looper mLooper;
public CookServer() {
start();
}
@Override
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
mHandler = new Handler(mLooper, new Handler.Callback() {
public boolean handleMessage(Message msg) {
new Cooker((String) msg.obj);
return true;
}
});
Looper.loop();
}
public void cook(String order) {
if (mLooper == null || mHandler == null) {
return;
}
Message msg = Message.obtain();
msg.obj = order;
mHandler.sendMessage(msg);
}
public void exit() {
if (mLooper != null) {
mLooper.quit();
mHandler = null;
mLooper = null;
}
}
}
private class Cooker extends Thread {
private String order;
public Cooker(String order) {
this.order = order;
start();
}
@Override
public void run() {
mMainHandler.sendEmptyMessage(COOKING_STARTED);
SystemClock.sleep(mRandomer.nextInt(50000));
mDeliverServer.deliver(order);
mMainHandler.sendEmptyMessage(COOKING_DONE);
}
}
private class DeliverServer extends HandlerThread {
private Handler mHandler;
public DeliverServer(String name) {
super(name);
start();
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
mHandler = new Handler(getLooper(), new Handler.Callback() {
public boolean handleMessage(Message msg) {
new Deliver((String) msg.obj);
return true;
}
});
}
public void deliver(String order) {
if (mHandler == null || getLooper() == null) {
return;
}
Message msg = Message.obtain();
msg.obj = order;
mHandler.sendMessage(msg);
}
public void exit() {
quit();
mHandler = null;
}
}
private class Deliver extends Thread {
private String order;
public Deliver(String order) {
this.order = order;
start();
}
@Override
public void run() {
mMainHandler.sendEmptyMessage(DELIVERING_STARTED);
SystemClock.sleep(mRandomer.nextInt(50000));
mMainHandler.sendEmptyMessage(ORDER_DONE);
}
}
}