大話Android多執行緒(三) 執行緒間的通訊機制之Handler

Anlia發表於2018-02-06

版權宣告:本文為博主原創文章,未經博主允許不得轉載
原始碼:github.com/AnliaLee
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論

前言

在Android中規定了修改UI控制元件,更新檢視這些操作必須在UI執行緒(主執行緒)中進行。而一些耗時的操作例如載入網路資料,查詢本地檔案、資料等,則必須放到子執行緒中。因此我們需要一種通訊機制使得子執行緒完成任務後可以通知UI執行緒更新介面。本章將挑選執行緒通訊機制中的Handler進行講解,聊一聊它和ThreadLocalMessageMessageQueue以及Looper之間的故事

ps:本篇部落格主要是起到一個引導的作用,幫助大家梳理清楚HandlerLooperMessageQueue等角色的關係,以及它們在Handler訊息機制下所起到的作用,並不會過多地深入到原始碼中。至於原始碼的講解,網上優秀的文章實在是太多了,這裡推薦幾位前輩撰寫的部落格,大家可以相互對照著看看

ps2:看完這篇部落格再去了解原始碼有助於消化知識哦~

往期回顧
大話Android多執行緒(一) Thread和Runnable的聯絡和區別
大話Android多執行緒(二) synchronized使用解析


子執行緒向主執行緒傳送訊息

在遙遠的Android大陸中,U國(UI執行緒,即主執行緒)和T國(Thread,子執行緒)之間發生了戰爭。某日,U國部隊準備攻打T國的首都R城,只要收到地下組織(Handler)的特工小h(Handler的例項)的訊號後,即可採取相應的行動(更新UI)。由於R城戒備森嚴,小h傳達訊號需要做到格外隱祕,因此制定瞭如下計劃,計劃由小h和他專屬的情報員小L(LooperHandler在建立時就會關聯一個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)中,見下圖(靈魂畫手)

大話Android多執行緒(三) 執行緒間的通訊機制之Handler

MessageQueue:通過單連結串列的資料結構來儲存訊息列表,訊息按照先進先出的原則進行存取,線上程中建立一個Looper例項時,會自動建立一個與之配對的MessageQueue

我們在子執行緒中使用Handler例項傳送訊息時,Handler會呼叫內部方法enqueueMessageMessage插入到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中呼叫了HandlerdispatchMessage方法,而在dispatchMessage方法中又呼叫了Handler.handleMessage方法,這樣就回到了我們第一點重寫的程式碼,實現了從子執行緒中傳送訊息主執行緒更新UI的操作

最後執行效果如圖所示

大話Android多執行緒(三) 執行緒間的通訊機制之Handler

總結Handler建立,傳送訊息到處理訊息的整個流程,大致如下圖所示

大話Android多執行緒(三) 執行緒間的通訊機制之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();//開始迴圈
        }
    }
}
複製程式碼

大話Android多執行緒(三) 執行緒間的通訊機制之Handler

當然上面的程式碼只是簡單地體驗一下手動建立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更詳細的資料大家可以看這篇部落格

Android 進階15: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方法將訊息插入到訊息佇列中。各種方法內部的執行順序如下圖所示,我們可以從紅框內任意一步出發,只需注意該方法的作用及傳入引數的區別即可

大話Android多執行緒(三) 執行緒間的通訊機制之Handler

相關文章