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

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載
源碼:github.com/AnliaLee
首發(fā)地址:Anlia_掘金
大家要是看到有錯(cuò)誤的地方或者有啥好的建議邀摆,歡迎留言評(píng)論

前言

在Android中規(guī)定了修改UI控件,更新視圖這些操作必須在UI線程(主線程)中進(jìn)行未蝌。而一些耗時(shí)的操作例如加載網(wǎng)絡(luò)數(shù)據(jù)煌寇,查詢本地文件、數(shù)據(jù)等疫蔓,則必須放到子線程中。因此我們需要一種通信機(jī)制使得子線程完成任務(wù)后可以通知UI線程更新界面身冬。本章將挑選線程通信機(jī)制中的Handler進(jìn)行講解衅胀,聊一聊它和ThreadLocalMessage酥筝、MessageQueue以及Looper之間的故事

ps:本篇博客主要是起到一個(gè)引導(dǎo)的作用滚躯,幫助大家梳理清楚HandlerLooper嘿歌、MessageQueue等角色的關(guān)系掸掏,以及它們?cè)?strong>Handler消息機(jī)制下所起到的作用,并不會(huì)過多地深入到源碼中宙帝。至于源碼的講解丧凤,網(wǎng)上優(yōu)秀的文章實(shí)在是太多了,這里推薦幾位前輩撰寫的博客步脓,大家可以相互對(duì)照著看看

ps2:看完這篇博客再去了解源碼有助于消化知識(shí)哦~

往期回顧
大話Android多線程(一) Thread和Runnable的聯(lián)系和區(qū)別
大話Android多線程(二) synchronized使用解析


子線程向主線程發(fā)送消息

在遙遠(yuǎn)的Android大陸中,U國(guó)(UI線程蚁廓,即主線程)和T國(guó)(Thread访圃,子線程)之間發(fā)生了戰(zhàn)爭(zhēng)。某日相嵌,U國(guó)部隊(duì)準(zhǔn)備攻打T國(guó)的首都R城腿时,只要收到地下組織(Handler)的特工小h(Handler的實(shí)例)的信號(hào)后,即可采取相應(yīng)的行動(dòng)(更新UI)饭宾。由于R城戒備森嚴(yán)批糟,小h傳達(dá)信號(hào)需要做到格外隱秘,因此制定了如下計(jì)劃看铆,計(jì)劃由小h和他專屬的情報(bào)員小L(Looper徽鼎,Handler在創(chuàng)建時(shí)就會(huì)關(guān)聯(lián)一個(gè)Looper對(duì)象,而Looper存放在ThreadLocal中弹惦,每一個(gè)線程都會(huì)維護(hù)自己的Looper否淤,這里的Looper自然是屬于主線程的)執(zhí)行:

  • 小h在執(zhí)行潛入任務(wù)前留有各種特定信號(hào)的說明,組織可根據(jù)說明采取相應(yīng)的行動(dòng)

創(chuàng)建Handler實(shí)例時(shí)棠隐,重寫handleMessage方法(在其中編寫更新UI的操作)石抡,以便在消息分配后執(zhí)行

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("情報(bào)收集完畢...");
                    break;
                case CODE_TEST_THREE:
                    textShow.setText("發(fā)起總攻!");
                    break;
            }
        }
    };
}
  • 小h潛入城中后助泽,只要時(shí)機(jī)成熟啰扛,就會(huì)將信號(hào)寫在紙條(Message,即消息)上塞到小L在城墻上挖好的小洞(MessageQueue)中嗡贺,見下圖(靈魂畫手)

MessageQueue:通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)消息列表隐解,消息按照先進(jìn)先出的原則進(jìn)行存取,在線程中創(chuàng)建一個(gè)Looper實(shí)例時(shí)诫睬,會(huì)自動(dòng)創(chuàng)建一個(gè)與之配對(duì)的MessageQueue

我們?cè)谧泳€程中使用Handler實(shí)例發(fā)送消息時(shí)厢漩,Handler會(huì)調(diào)用內(nèi)部方法enqueueMessageMessage插入到MessageQueue中(Handler.enqueueMessage方法最后調(diào)用了MessageQueue.enqueueMessage方法存放Message,有關(guān)Handler發(fā)送消息的方法請(qǐng)見下文附錄一

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);

//                你也可以這樣發(fā)送消息
//                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();
            }
        }
    }
}
  • 城外負(fù)責(zé)接應(yīng)的小L會(huì)一直蹲守在小洞旁(Looper.loop)岩臣,一旦發(fā)現(xiàn)洞中出現(xiàn)紙條(MessageQueue.next)溜嗜,就將其取出送回組織(Handler.dispatchMessage),然后返回繼續(xù)蹲守小洞

Looper.loop方法不斷地調(diào)用MessageQueue.next方法讀取消息架谎,若消息不為空則調(diào)用Handler.dispatchMessage方法將消息分發(fā)出去

  • 組織拿到紙條后炸宵,首先確定紙條是不是由小h發(fā)出的,確認(rèn)無誤后谷扣,根據(jù)紙條上的信號(hào)采取相應(yīng)行動(dòng)

之前在Looper中調(diào)用了HandlerdispatchMessage方法土全,而在dispatchMessage方法中又調(diào)用了Handler.handleMessage方法捎琐,這樣就回到了我們第一點(diǎn)重寫的代碼,實(shí)現(xiàn)了從子線程中發(fā)送消息主線程更新UI的操作

最后運(yùn)行效果如圖所示

總結(jié)Handler創(chuàng)建裹匙,發(fā)送消息到處理消息的整個(gè)流程瑞凑,大致如下圖所示


主線程向子線程發(fā)送消息

主線程Looper在應(yīng)用開啟前系統(tǒng)就已經(jīng)幫我們創(chuàng)建好了,如果我們要在主線程中向子線程發(fā)送消息概页,則需要在子線程創(chuàng)建時(shí)手動(dòng)創(chuàng)建Looper并開啟循環(huán)籽御,具體實(shí)現(xiàn)代碼如下:

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已經(jīng)初始化
            if(testThread.looper!=null){
                handler2 = new Handler(testThread.looper){
                    @Override
                    public void handleMessage(Message msg) {//子線程收到消息后執(zhí)行
                        switch (msg.what){
                            case CODE_TEST_FOUR:
                                Log.e(TAG,"收到主線程發(fā)送的消息");
                                break;
                        }
                    }
                };

                handler2.sendEmptyMessage(CODE_TEST_FOUR);//在主線程中發(fā)送消息
                break;
            }
        }

    private class TestThread extends Thread{
        private Looper looper;

        @Override
        public void run() {
            super.run();
            Looper.prepare();//創(chuàng)建該子線程的Looper實(shí)例
            looper = Looper.myLooper();//取出該子線程的Looper實(shí)例
            Looper.loop();//開始循環(huán)
        }
    }
}

當(dāng)然上面的代碼只是簡(jiǎn)單地體驗(yàn)一下手動(dòng)創(chuàng)建Looper的過程,實(shí)際上系統(tǒng)已經(jīng)為我們封裝好了HandlerThread類惰匙,它幫助我們完成了創(chuàng)建Looper技掏、開啟循環(huán)等一系列操作,因此使用HandlerThread會(huì)更加方便和安全项鬼。以上述同樣的操作為例哑梳,這次我們直接繼承HandlerThread創(chuàng)建子線程:

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) {//子線程收到消息后執(zhí)行
                switch (msg.what){
                    case CODE_TEST_FOUR:
                        Log.e(TAG,"收到主線程發(fā)送的消息");
                        break;
                }
            }
        };
        handler2.sendEmptyMessage(CODE_TEST_FOUR);//在主線程中發(fā)送消息
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handlerThread.quit();
    }
}

有關(guān)HandlerThread更詳細(xì)的資料大家可以看這篇博客

Android 進(jìn)階15:HandlerThread 使用場(chǎng)景及源碼解析


子線程向子線程發(fā)送消息

子線程子線程發(fā)送消息的過程和之前講的差不多,就不贅述了

protected void onCreate(Bundle savedInstanceState) {
    //省略部分代碼...
    handlerThread = new HandlerThread("MyHandlerThread");
    handlerThread.start();
    handler2 = new Handler(handlerThread.getLooper()){
        @Override
        public void handleMessage(Message msg) {//子線程收到消息后執(zhí)行
            switch (msg.what){
                case CODE_TEST_FOUR:
                    Log.e(TAG,"收到另一個(gè)子線程發(fā)送的消息");
                    break;
            }
        }
    };

    Thread testThread = new Thread(new Runnable() {
        @Override
        public void run() {
            handler2.sendEmptyMessage(CODE_TEST_FOUR);//在另一個(gè)子線程中發(fā)送消息
        }
    });
    testThread.start();
}

附錄一:Handler發(fā)送消息

Handler發(fā)送消息多種方法绘盟,但無論我們使用哪種方法鸠真,其最終都是利用MessageQueue.enqueueMessage方法將消息插入到消息隊(duì)列中。各種方法內(nèi)部的執(zhí)行順序如下圖所示龄毡,我們可以從紅框內(nèi)任意一步出發(fā)弧哎,只需注意該方法的作用及傳入?yún)?shù)的區(qū)別即可

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市稚虎,隨后出現(xiàn)的幾起案子撤嫩,更是在濱河造成了極大的恐慌,老刑警劉巖蠢终,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件序攘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寻拂,警方通過查閱死者的電腦和手機(jī)程奠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祭钉,“玉大人瞄沙,你說我怎么就攤上這事』藕耍” “怎么了距境?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)垮卓。 經(jīng)常有香客問我垫桂,道長(zhǎng),這世上最難降的妖魔是什么粟按? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任诬滩,我火速辦了婚禮霹粥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疼鸟。我一直安慰自己后控,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布空镜。 她就那樣靜靜地躺著浩淘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姑裂。 梳的紋絲不亂的頭發(fā)上馋袜,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天男旗,我揣著相機(jī)與錄音舶斧,去河邊找鬼。 笑死察皇,一個(gè)胖子當(dāng)著我的面吹牛茴厉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播什荣,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼矾缓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了稻爬?” 一聲冷哼從身側(cè)響起嗜闻,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桅锄,沒想到半個(gè)月后琉雳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友瘤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年翠肘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辫秧。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡束倍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盟戏,到底是詐尸還是另有隱情绪妹,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布柿究,位于F島的核電站喂急,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏笛求。R本人自食惡果不足惜廊移,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一糕簿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡孔,春花似錦懂诗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至辱揭,卻和暖如春离唐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背问窃。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工亥鬓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人域庇。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓嵌戈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親听皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熟呛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容