版權宣告:本文為博主原創文章,未經博主允許不得轉載
原始碼:github.com/AnliaLee
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論
前言
在Android中規定了修改UI控制元件,更新檢視這些操作必須在UI執行緒(主執行緒)中進行。而一些耗時的操作例如載入網路資料,查詢本地檔案、資料等,則必須放到子執行緒中。因此我們需要一種通訊機制使得子執行緒完成任務後可以通知UI執行緒更新介面。本章將挑選執行緒通訊機制中的Handler進行講解,聊一聊它和ThreadLocal、Message、MessageQueue以及Looper之間的故事
ps:本篇部落格主要是起到一個引導的作用,幫助大家梳理清楚Handler、Looper、MessageQueue等角色的關係,以及它們在Handler訊息機制下所起到的作用,並不會過多地深入到原始碼中。至於原始碼的講解,網上優秀的文章實在是太多了,這裡推薦幾位前輩撰寫的部落格,大家可以相互對照著看看
- Android進階——Android訊息機制之Looper、Handler、MessageQueue
- Android 非同步訊息處理機制 讓你深入理解 Looper、Handler、Message三者關係
- Android開發——Android的訊息機制詳解
ps2:看完這篇部落格再去了解原始碼有助於消化知識哦~
往期回顧
大話Android多執行緒(一) Thread和Runnable的聯絡和區別
大話Android多執行緒(二) synchronized使用解析
子執行緒向主執行緒傳送訊息
在遙遠的Android大陸中,U國(UI執行緒,即主執行緒)和T國(Thread,子執行緒)之間發生了戰爭。某日,U國部隊準備攻打T國的首都R城,只要收到地下組織(Handler)的特工小h(Handler的例項)的訊號後,即可採取相應的行動(更新UI)。由於R城戒備森嚴,小h傳達訊號需要做到格外隱祕,因此制定瞭如下計劃,計劃由小h和他專屬的情報員小L(Looper,Handler在建立時就會關聯一個Looper物件,而Looper存放在ThreadLocal中,每一個執行緒都會維護自己的Looper,這裡的Looper自然是屬於主執行緒的)執行:
- 小h在執行潛入任務前留有各種特定訊號的說明,組織可根據說明採取相應的行動
建立Handler例項時,重寫handleMessage方法(在其中編寫更新UI的操作),以便在訊息分配後執行
public class HandlerTestActivity extends AppCompatActivity {
TextView textShow;
private static final int CODE_TEST_ONE = 101;
private static final int CODE_TEST_TWO = 102;
private static final int CODE_TEST_THREE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
textShow = (TextView) findViewById(R.id.text_show);
}
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case CODE_TEST_ONE:
textShow.setText("開始刺探軍情...");
break;
case CODE_TEST_TWO:
textShow.setText("情報收集完畢...");
break;
case CODE_TEST_THREE:
textShow.setText("發起總攻!");
break;
}
}
};
}
複製程式碼
- 小h潛入城中後,只要時機成熟,就會將訊號寫在紙條(Message,即訊息)上塞到小L在城牆上挖好的小洞(MessageQueue)中,見下圖(靈魂畫手)
MessageQueue:通過單連結串列的資料結構來儲存訊息列表,訊息按照先進先出的原則進行存取,線上程中建立一個Looper例項時,會自動建立一個與之配對的MessageQueue
我們在子執行緒中使用Handler例項傳送訊息時,Handler會呼叫內部方法enqueueMessage將Message插入到MessageQueue中(Handler.enqueueMessage方法最後呼叫了MessageQueue.enqueueMessage方法存放Message,有關Handler傳送訊息的方法請見下文附錄一)
public class HandlerTestActivity extends AppCompatActivity {
//省略部分程式碼...
public void clickEvent(View view) {
switch (view.getId()) {
case R.id.btn_start:
new Thread(new TestRunnable()).start();
break;
}
}
private class TestRunnable implements Runnable{
@Override
public void run() {
try {
handler.sendEmptyMessage(CODE_TEST_ONE);
// 你也可以這樣傳送訊息
// Message message = Message.obtain();
// message.what = CODE_TEST_ONE;
// handler.sendMessage(message);
// 或者
// message.sendToTarget();
Thread.sleep(2000);
handler.sendEmptyMessage(CODE_TEST_TWO);
Thread.sleep(2000);
handler.sendEmptyMessage(CODE_TEST_THREE);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
複製程式碼
- 城外負責接應的小L會一直蹲守在小洞旁(Looper.loop),一旦發現洞中出現紙條(MessageQueue.next),就將其取出送回組織(Handler.dispatchMessage),然後返回繼續蹲守小洞
Looper.loop方法不斷地呼叫MessageQueue.next方法讀取訊息,若訊息不為空則呼叫Handler.dispatchMessage方法將訊息分發出去
- 組織拿到紙條後,首先確定紙條是不是由小h發出的,確認無誤後,根據紙條上的訊號採取相應行動
之前在Looper中呼叫了Handler的dispatchMessage方法,而在dispatchMessage方法中又呼叫了Handler.handleMessage方法,這樣就回到了我們第一點重寫的程式碼,實現了從子執行緒中傳送訊息到主執行緒更新UI的操作
最後執行效果如圖所示
總結Handler建立,傳送訊息到處理訊息的整個流程,大致如下圖所示
主執行緒向子執行緒傳送訊息
主執行緒的Looper在應用開啟前系統就已經幫我們建立好了,如果我們要在主執行緒中向子執行緒傳送訊息,則需要在子執行緒建立時手動建立Looper並開啟迴圈,具體實現程式碼如下:
public class HandlerTestActivity extends AppCompatActivity {
private Handler handler2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
TestThread testThread = new TestThread();
testThread.start();
while (true){//保證testThread.looper已經初始化
if(testThread.looper!=null){
handler2 = new Handler(testThread.looper){
@Override
public void handleMessage(Message msg) {//子執行緒收到訊息後執行
switch (msg.what){
case CODE_TEST_FOUR:
Log.e(TAG,"收到主執行緒傳送的訊息");
break;
}
}
};
handler2.sendEmptyMessage(CODE_TEST_FOUR);//在主執行緒中傳送訊息
break;
}
}
private class TestThread extends Thread{
private Looper looper;
@Override
public void run() {
super.run();
Looper.prepare();//建立該子執行緒的Looper例項
looper = Looper.myLooper();//取出該子執行緒的Looper例項
Looper.loop();//開始迴圈
}
}
}
複製程式碼
當然上面的程式碼只是簡單地體驗一下手動建立Looper的過程,實際上系統已經為我們封裝好了HandlerThread類,它幫助我們完成了建立Looper、開啟迴圈等一系列操作,因此使用HandlerThread會更加方便和安全。以上述同樣的操作為例,這次我們直接繼承HandlerThread建立子執行緒:
public class HandlerTestActivity extends AppCompatActivity {
private HandlerThread handlerThread;
private Handler handler2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
handler2 = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {//子執行緒收到訊息後執行
switch (msg.what){
case CODE_TEST_FOUR:
Log.e(TAG,"收到主執行緒傳送的訊息");
break;
}
}
};
handler2.sendEmptyMessage(CODE_TEST_FOUR);//在主執行緒中傳送訊息
}
@Override
protected void onDestroy() {
super.onDestroy();
handlerThread.quit();
}
}
複製程式碼
有關HandlerThread更詳細的資料大家可以看這篇部落格
子執行緒向子執行緒傳送訊息
子執行緒向子執行緒傳送訊息的過程和之前講的差不多,就不贅述了
protected void onCreate(Bundle savedInstanceState) {
//省略部分程式碼...
handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
handler2 = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {//子執行緒收到訊息後執行
switch (msg.what){
case CODE_TEST_FOUR:
Log.e(TAG,"收到另一個子執行緒傳送的訊息");
break;
}
}
};
Thread testThread = new Thread(new Runnable() {
@Override
public void run() {
handler2.sendEmptyMessage(CODE_TEST_FOUR);//在另一個子執行緒中傳送訊息
}
});
testThread.start();
}
複製程式碼
附錄一:Handler傳送訊息
Handler傳送訊息多種方法,但無論我們使用哪種方法,其最終都是利用MessageQueue.enqueueMessage方法將訊息插入到訊息佇列中。各種方法內部的執行順序如下圖所示,我們可以從紅框內任意一步出發,只需注意該方法的作用及傳入引數的區別即可