大話Android多線程(三) 線程間的通訊機制之Handler

版權聲明:本文爲博主原創文章,未經博主容許不得轉載
源碼:github.com/AnliaLee
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論java

前言

在Android中規定了修改UI控件,更新視圖這些操做必須在UI線程(主線程)中進行。而一些耗時的操做例如加載網絡數據,查詢本地文件、數據等,則必須放到子線程中。所以咱們須要一種通訊機制使得子線程完成任務後能夠通知UI線程更新界面。本章將挑選線程通訊機制中的Handler進行講解,聊一聊它和ThreadLocalMessageMessageQueue以及Looper之間的故事android

ps:本篇博客主要是起到一個引導的做用,幫助你們梳理清楚HandlerLooperMessageQueue等角色的關係,以及它們在Handler消息機制下所起到的做用,並不會過多地深刻到源碼中。至於源碼的講解,網上優秀的文章實在是太多了,這裏推薦幾位前輩撰寫的博客,你們能夠相互對照着看看git

ps2:看完這篇博客再去了解源碼有助於消化知識哦~github

往期回顧
大話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)中,見下圖(靈魂畫手)

MessageQueue:經過單鏈表的數據結構來存儲消息列表,消息按照先進先出的原則進行存取,在線程中建立一個Looper實例時,會自動建立一個與之配對的MessageQueue多線程

咱們在子線程中使用Handler實例發送消息時,Handler會調用內部方法enqueueMessageMessage插入到MessageQueue中(Handler.enqueueMessage方法最後調用了MessageQueue.enqueueMessage方法存放Message,有關Handler發送消息的方法請見下文附錄一app

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的操做

最後運行效果如圖所示

總結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更詳細的資料你們能夠看這篇博客

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方法將消息插入到消息隊列中。各類方法內部的執行順序以下圖所示,咱們能夠從紅框內任意一步出發,只需注意該方法的做用及傳入參數的區別便可

相關文章
相關標籤/搜索