版權(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)行講解衅胀,聊一聊它和ThreadLocal、Message酥筝、MessageQueue以及Looper之間的故事
ps:本篇博客主要是起到一個(gè)引導(dǎo)的作用滚躯,幫助大家梳理清楚Handler、Looper嘿歌、MessageQueue等角色的關(guān)系掸掏,以及它們?cè)?strong>Handler消息機(jī)制下所起到的作用,并不會(huì)過多地深入到源碼中宙帝。至于源碼的講解丧凤,網(wǎng)上優(yōu)秀的文章實(shí)在是太多了,這里推薦幾位前輩撰寫的博客步脓,大家可以相互對(duì)照著看看
- Android進(jìn)階——Android消息機(jī)制之Looper愿待、Handler浩螺、MessageQueue
- Android 異步消息處理機(jī)制 讓你深入理解 Looper、Handler呼盆、Message三者關(guān)系
- Android開發(fā)——Android的消息機(jī)制詳解
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)部方法enqueueMessage將Message插入到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)用了Handler的dispatchMessage方法土全,而在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ì)的資料大家可以看這篇博客
子線程向子線程發(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ū)別即可