一均芽、前言:
Android中的消息機(jī)制是通過(guò)Handler來(lái)實(shí)現(xiàn)的党巾。隨著EventBus和RxJava等依托觀察者模式的消息傳遞機(jī)制的出現(xiàn)瘪吏,當(dāng)前在Android開(kāi)發(fā)中Handler的使用已經(jīng)不如之前那么重要蔑祟,但是Android系統(tǒng)所提供的Handler中的各種編程思路和設(shè)計(jì)方案晌区,對(duì)我們?cè)诰幊趟枷氲奶嵘€是有很大益處的寞埠。
gitHub 地址:https://github.com/lyyRunning/ThreadDemo0629.git
1.Handler是什么?
Handler是Android給我們提供用于更新UI的一套機(jī)制屁置,也是一套消息處理機(jī)制。我們用它可以發(fā)送消息仁连,也可以用它處理消息蓝角。在Android開(kāi)發(fā)中有著非常重要的地位。
2.為什么要使用Handler?
當(dāng)一個(gè)應(yīng)用程序運(yùn)行時(shí)饭冬,它會(huì)創(chuàng)建一個(gè)進(jìn)程使鹅。這個(gè)進(jìn)程就是我們的主線程(UI線程&Activity Thread) 。在主線程中昌抠,會(huì)默認(rèn)為我們?cè)谙到y(tǒng)中默認(rèn)創(chuàng)建一個(gè)Looper患朱,這個(gè)Looper會(huì)與我們的Message Queue 和 主線程有一定聯(lián)系。 在main線程中炊苫,主要是運(yùn)行一個(gè)Message Queue裁厅,管理著頂級(jí)的應(yīng)用程序(Activity,Boardcast Receiver…)這些頂級(jí)應(yīng)用程序在默認(rèn)情況下都會(huì)在主線程中創(chuàng)建劝评。這就是為什么我們需要在主線程中更新UI姐直。
Android在設(shè)計(jì)的過(guò)程中,就封裝了一套消息創(chuàng)建蒋畜、傳遞、處理的機(jī)制撞叽。如果不遵循這樣的機(jī)制姻成,是沒(méi)有辦法更新UI信息的插龄,會(huì)拋出異常信息。
非主線程更新UI的后果
我們可以嘗試在一個(gè)新的線程中更新UI科展,會(huì)發(fā)現(xiàn)程序崩潰了均牢。查看Logcat可以看到這樣的一句提示
Only the original thread that created a view hierarchy can touch its views.
這告訴我們,在實(shí)際開(kāi)發(fā)中才睹,我們需要遵循Google為我們?cè)O(shè)定的這樣的機(jī)制徘跪。
那么如何在其他線程達(dá)到更新UI的目的呢?使用Handler就是其中一種辦法琅攘。
3.Handler的作用
根據(jù)Android Developer網(wǎng)站上的描述垮庐,Handler主要有兩個(gè)用途
- 定時(shí)地去發(fā)送一個(gè)Message或Runnable對(duì)象
- 可以跳轉(zhuǎn)到另一個(gè)線程中去執(zhí)行一些操作
4.Handler的使用方式
關(guān)于Message
參數(shù)
public int arg1(arg2):如果只需要存儲(chǔ)幾個(gè)整型數(shù)據(jù),arg1 和 arg2是setData()的低成本替代品坞琴。
public Object obj: 發(fā)送給接收者的任意對(duì)象哨查。當(dāng)使用Message對(duì)象在線程間傳遞消息時(shí),如果它包含一個(gè)Parcelable的結(jié)構(gòu)類(不是由應(yīng)用程序?qū)崿F(xiàn)的類)剧辐,此字段必須為非空(non-null)寒亥。其他的數(shù)據(jù)傳輸則使用setData(Bundle)方法。
public Messenger replyTo:用戶自定義的消息代碼荧关,這樣接受者可以了解這個(gè)消息的信息溉奕。每個(gè)handler各自包含自己的消息代碼,所以不用擔(dān)心自定義的消息跟其他handlers有沖突忍啤。
方法
另外 腐宋,用Message來(lái)傳遞還可通過(guò)Message的setData(Bundle)方法來(lái)傳遞。獲取時(shí)調(diào)用getData()方法檀轨。與sendData相似的還有peekData胸竞。
public void setData(*Bundle data):設(shè)置一個(gè)任意數(shù)據(jù)值的Bundle對(duì)象。如果可以参萄,使用arg1和arg2域發(fā)送一些整型值以減少消耗卫枝。
public Bundle peekData():與getData()相似,但是并不延遲創(chuàng)建Bundle讹挎。如果Bundle對(duì)象不存在返回null校赤。
public Bundle getData():獲取附加在此事件上的任意數(shù)據(jù)的Bundle對(duì)象,需要時(shí)延遲創(chuàng)建筒溃。通過(guò)調(diào)用setData(Bundle)來(lái)設(shè)置Bundle的值马篮。需要注意的是,如果通過(guò)Messenger對(duì)象在進(jìn)程間傳遞數(shù)據(jù)時(shí)怜奖,需要調(diào)用Bundle類的Bundle.setClassLoader()方法來(lái)設(shè)置ClassLoader浑测,這樣當(dāng)接收到消息時(shí)可以實(shí)例化Bundle里的對(duì)象。
public static Message obtain(): 從全局池中返回一個(gè)新的Message實(shí)例。在大多數(shù)情況下這樣可以避免分配新的對(duì)象迁央。
5.handleMessage方法
handleMessage方法用于接收Message對(duì)象并進(jìn)行相應(yīng)的處理掷匠,對(duì)應(yīng)Handler的sendMessage方法。
使用時(shí)應(yīng)在handler中重寫(xiě)此方法岖圈。當(dāng)在其他線程調(diào)用sendMessage方法時(shí)讹语,handleMessage方法便會(huì)被回調(diào),并攜帶sendMessage方法調(diào)用者在Message中存入的信息蜂科。當(dāng)我們想要在其他線程更新UI時(shí)顽决,就可以用主線程中創(chuàng)建的Handler調(diào)用sendMessage方法,然后在該Handler重寫(xiě)的handleMessage方法中做相應(yīng)的處理导匣。
比如此處才菠,我們?cè)趆andleMessage方法中進(jìn)行更新TextView的操作,并把Message的arg1作為文本的內(nèi)容逐抑。
private Handler mHandler = new Handler(){
public void handleMessage(Message msg){
mTextView.setText(""+msg.arg1+"-"+msg.arg2);
};
};
new Thread(){
@Override
public void run() {
try {
Thread.sleep(2000);
Message message = Message.obtain();
message.arg1 = 88;
mHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
6.post方法
剛剛的異常我們已經(jīng)看到鸠儿,那如何才能使用Handler在這個(gè)新建的線程更新UI呢?
我們可以這樣做:在主線程中創(chuàng)建一個(gè)Handler厕氨。然后在子線程中辈讶,我們可以調(diào)用Handler的post方法杀赢,并向其中傳遞一個(gè)Runnable為參數(shù)虫腋,在Runnable中更新UI即可竭业。
private TextView mTextView;
private Handler mHandler = new Handler();
mTextView = findViewById(R.id.tv_text);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("update");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
7.postDelayed方法
postDelayed與post方法非常接近,僅僅是參數(shù)多了一個(gè)long類型的參數(shù)delayMills国葬。它與post的區(qū)別就是它會(huì)在delayMills這段時(shí)間之后再去執(zhí)行Runnable的方法贤徒,也就是延遲執(zhí)行。
有時(shí)候我們需要定時(shí)的完成一些事情(比如定時(shí)更換TextView的文字)時(shí)汇四,就可以利用它延遲執(zhí)行的這一特點(diǎn)來(lái)實(shí)現(xiàn)接奈。做法是分別在主函數(shù)中以及它所執(zhí)行的Runnable中postDelayed一段時(shí)間。這樣就可以達(dá)到定時(shí)地完成某件任務(wù)的工作通孽。
比如下例就簡(jiǎn)單地實(shí)現(xiàn)了每隔一秒切換一次TextView文字的作用序宦。
8.removeCallbacks方法
比如我們這里有個(gè)定時(shí)更新TextView的文本的代碼,如果想要按下按鈕背苦,停止定時(shí)更換文本互捌,就可以通過(guò)removeCallbacks方法,傳入該Runnable來(lái)中止消息行剂。
二秕噪、Handler運(yùn)行機(jī)制
Handler的消息機(jī)制如下圖所示,主要包含兩個(gè)消息隊(duì)列厚宰,一個(gè)是消息的回收隊(duì)列腌巾,另一個(gè)是Message Q隊(duì)列。
1. 消息的回收隊(duì)列:消息回收隊(duì)列是為Handler提供消息對(duì)象的,當(dāng)Handler需要發(fā)送消息時(shí)壤躲,首先從消息回收隊(duì)列中獲取已被清空數(shù)據(jù)的消息對(duì)象城菊,若消息對(duì)隊(duì)列中此時(shí)沒(méi)有消息對(duì)象备燃,則創(chuàng)建新的消息對(duì)象碉克。當(dāng)消息對(duì)象被使用后,不會(huì)直接被當(dāng)做垃圾回收并齐,而是會(huì)進(jìn)入消息的回收隊(duì)列漏麦,在該隊(duì)列中會(huì)將消息對(duì)象上的所有數(shù)據(jù)清空,之后在隊(duì)列中等待被使用况褪。
2. 獲取消息 :直接通過(guò)以下兩種方式中的任一種獲取消息撕贞。
Message message = Message.obtain();
message.what = MAIN_UI;
message.obj = "main發(fā)送消息的線程名稱"
3. 創(chuàng)建Handler對(duì)象: 直接通過(guò)如下方式創(chuàng)建Handler對(duì)象即可。該Handler默認(rèn)使用Android提供的Looper测垛。
Handler handler= new Handler();
4. 發(fā)送消息:通過(guò)如下等方式進(jìn)行消息的發(fā)送捏膨。
handlerUI.sendEmptyMessage(11);//立刻發(fā)送空的消息
handlerUI.sendMessage(message);//立刻發(fā)送攜帶數(shù)據(jù)的消息
handlerUI.sendMessageDelayed(message,1000);//延遲1000ms后發(fā)送攜帶數(shù)據(jù)的消息
5. Message Q隊(duì)列:消息隊(duì)列,用來(lái)管理消息食侮,會(huì)根據(jù)消息的執(zhí)行時(shí)間對(duì)消息進(jìn)行排序号涯。并通過(guò)Looper.loop對(duì)消息進(jìn)行輪詢?nèi)〕觯?dāng)隊(duì)列中有消息時(shí)就將消息取出交給Handler的handlerMessage()回調(diào)進(jìn)行消息的處理锯七,當(dāng)隊(duì)列中沒(méi)有消息時(shí)就進(jìn)行阻塞等待链快,這種機(jī)制又被稱作等待喚醒機(jī)制。Android中的等待喚醒機(jī)制是使用的Linux內(nèi)核的管道流機(jī)制眉尸。
6. 消息的處理:通過(guò)Looper.loop()取出消息隊(duì)列中待處理的消息域蜗,通過(guò)handler的handlerMessage()回調(diào)進(jìn)行消息的處理。處理完成的消息對(duì)象會(huì)進(jìn)入消息的回收隊(duì)列進(jìn)行回收噪猾。
原理解析:
Handler 創(chuàng)建完畢后霉祸,這個(gè)時(shí)候內(nèi)部的 Looper 以及 MessageQueue 就可以和 Handler 一起協(xié)調(diào)工作了,然后通過(guò) Handler 的 post 方法將一個(gè) Runnable 投遞到 Handler 內(nèi)部的 Looper 中去處理袱蜡,也可以通過(guò) Handler 的 send 方法發(fā)送一個(gè)消息,這個(gè)消息同樣會(huì)在 Looper 中去處理迅细。其實(shí) post 方法最終也是通過(guò) send 方法發(fā)送消息的。
接下來(lái)看一下 send 的工作過(guò)程:當(dāng) Handler 的 send 方法被調(diào)用時(shí)帆离,它會(huì)調(diào)用 MessageQueueMessage 方法將這個(gè)消息放入消息隊(duì)列中岸夯,然后 Looper 發(fā)現(xiàn)有新消息到來(lái)時(shí),就會(huì)處理這個(gè)消息,最終消息中的 Runnable 或者是 Handler 的 handlerMessage 方法會(huì)被調(diào)用刮刑。注意 Looper 是運(yùn)行在創(chuàng)建 Handler 所在的線程中的蔽氨,這樣一來(lái) Handler 中的業(yè)務(wù)邏輯就被切換到創(chuàng)建 Handler 所在的線程中去執(zhí)行了。
三柳琢、使用Handler向主線程發(fā)送消息
通過(guò)handler向主線程發(fā)送消息是Handler使用過(guò)程中最常規(guī)的一種方式绍妨,也是最普遍的一種使用方式润脸。當(dāng)創(chuàng)建Handler對(duì)象時(shí),若使用空參構(gòu)造創(chuàng)建他去,則創(chuàng)建出的Handler默認(rèn)使用UI線程中的Looper毙驯,這個(gè)時(shí)候通過(guò)Looper.loop()取出的消息在handlerMessage()中進(jìn)行處理時(shí),是在UI線程中進(jìn)行處理的灾测。這也就是為什么大家常說(shuō):Handler是運(yùn)行在主線程中的爆价。
這又分成兩種情況:
1. 主線程->主線程 發(fā)送消息:在主線程中通過(guò)創(chuàng)建好的handler調(diào)用sendMessage()方法發(fā)送消息即可。
2. 子線程->主線程 發(fā)送消息:在子線程中通過(guò)創(chuàng)建好的handler調(diào)用sendMessage()方法發(fā)送消息即可行施。
本次以主線程和子線程向主線程發(fā)送消息為例:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn1;
@BindView(R.id.btn2)
Button btn2;
@BindView(R.id.tv_message)
TextView tvMessage;
private static final int HANDLER_UI = 1;
private static final int MAIN_UI = 2;
/**
* 運(yùn)行在主線程的Handler:使用Android默認(rèn)的UI線程中的Looper
*/
public Handler handlerUI = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case HANDLER_UI:
String strData = (String) msg.obj;
tvMessage.setText(strData);
break;
case MAIN_UI:
String strData2 = (String) msg.obj;
tvMessage.setText(strData2);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvMessage.setText("主線程發(fā)送 Handler");
}
@OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn1:
//主線程發(fā)送
Message message = Message.obtain();
message.what = MAIN_UI;
message.obj = "main發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
handlerUI.sendMessage(message);
break;
case R.id.btn2:
//子線程發(fā)送
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = HANDLER_UI;
message.obj = "子線程發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
handlerUI.sendMessage(message);
}
}).start();
break;
case R.id.btn3:
ThreadActivity.launch(MainActivity.this);
break;
default:
}
}
}
上述代碼有一定的隱患允坚,因?yàn)槿绱耸褂肏andler會(huì)導(dǎo)致內(nèi)存泄露魂那,此處是為了舉例簡(jiǎn)潔才如此使用蛾号,正常使用Handler時(shí)要避免這種方式,解決這種導(dǎo)致內(nèi)存泄露可通過(guò)——WeakReference(弱引用)涯雅,后面會(huì)詳細(xì)介紹鲜结。
四、使用Handler向子線程發(fā)送消息
上述說(shuō)的是Handler常規(guī)的使用方式活逆,但有時(shí)因?yàn)闃I(yè)務(wù)需求精刷,需要向子線程發(fā)送消息。這時(shí)候就需要尋找突破點(diǎn)了蔗候,仔細(xì)觀察其實(shí)很容易發(fā)現(xiàn)怒允,消息機(jī)制最核心的點(diǎn)就是消息的Looper機(jī)制,UI線程之所以能夠進(jìn)行消息的實(shí)現(xiàn)是因?yàn)锳ndroid默認(rèn)提供了一個(gè)Looper锈遥,含有Looper的線程被稱為UI線程纫事,UI線程和子線程之間唯一的區(qū)別就是一個(gè)有Looper另外的一個(gè)沒(méi)有。既然突破點(diǎn)已經(jīng)找到所灸,那直接創(chuàng)建一個(gè)含有Looper的子線程丽惶,即可實(shí)現(xiàn)向該子線程發(fā)送消息。那如何創(chuàng)建一個(gè)含有Looer的子線程能爬立?需要兩步:
在創(chuàng)建的子線程的run()方法中進(jìn)行Looper相關(guān)的配置钾唬。
在創(chuàng)建Hander時(shí)的構(gòu)造方法中傳入1線程中的Looper。
代碼實(shí)現(xiàn)如下:
public class ThreadActivity extends AppCompatActivity {
@BindView(R.id.tv_message)
TextView tvMessage;
@BindView(R.id.btn1)
Button btn1;
@BindView(R.id.btn2)
Button btn2;
private Handler threadHandler;
private static final int HANDLER_THREAD = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvMessage.setText("子線程處理Handler侠驯,原始用法");
createLooperHandler();
}
@OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn1:
break;
case R.id.btn2:
//子線程發(fā)送
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = HANDLER_THREAD;
message.obj = "子線程發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
threadHandler.sendMessage(message);
}
}).start();
break;
case R.id.btn3:
ThirdActivity.launch(ThreadActivity.this);
break;
default:
}
}
/**
* 創(chuàng)建一個(gè)可以包含looper的子線程抡秆,并開(kāi)啟
*/
private void createLooperHandler() {
MyThread myThread = new MyThread();
myThread.start();
//注釋1
SystemClock.sleep(100);
threadHandler = new Handler(myThread.threadLooper) {
@Override
public void handleMessage(final Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case HANDLER_THREAD:
//添加上當(dāng)前線程名稱
final String thrMsg = (String) msg.obj + "\n 收到的線程:" + Thread
.currentThread().getName();
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessage.setText(thrMsg);
}
});
break;
default:
break;
}
}
};
}
public static void launch(Context context) {
Intent intent = new Intent(context, ThreadActivity.class);
context.startActivity(intent);
}
private class MyThread extends Thread {
private Looper threadLooper;
@Override
public void run() {
Looper.prepare();
threadLooper = Looper.myLooper();
Looper.loop();
}
}
}
通過(guò)上面的方法,確實(shí)可以實(shí)現(xiàn)向子線程發(fā)送消息吟策,但是其實(shí)還是有隱患的儒士,眼尖的人已經(jīng)看到上面81行(注釋1)處的代碼了,為什么要進(jìn)行睡眠100毫秒踊挠,其實(shí)是因?yàn)镸yThread在調(diào)用start方法后乍桂,代碼向下運(yùn)行冲杀,進(jìn)行了創(chuàng)建Handler的操作,在創(chuàng)建Handler時(shí)需要在構(gòu)造方法中傳入MyThread的Looper睹酌,而只有當(dāng)MyThread開(kāi)啟調(diào)用run()方法后才會(huì)創(chuàng)建出Looper权谁,兩者操作是在不同線程中,所以不能確定誰(shuí)會(huì)先被執(zhí)行憋沿。故旺芽,如果不進(jìn)行睡眠的話,就會(huì)概率性出現(xiàn)空指針異常辐啄。那如何才能保證既可以向子線程發(fā)送消息采章,主線程又不用出現(xiàn)睡眠等待呢?其實(shí)android也知道這種情況壶辜,所以向我們提供了一個(gè)類HandlerThread來(lái)解決上述問(wèn)題悯舟。
五、HandlerThread的使用
HnadlerThread是Thread的子類砸民,是專門(mén)向子線程發(fā)送消息使用的抵怎。使用步驟如下:
public class ThirdActivity extends AppCompatActivity {
@BindView(R.id.tv_message)
TextView tvMessage;
@BindView(R.id.btn1)
Button btn1;
@BindView(R.id.btn2)
Button btn2;
private Handler handlerThreadHandler;
private static final int HANDLER_THREAD = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvMessage.setText("子線程處理Handler,正常用法");
createLooperHandler();
}
@OnClick({R.id.btn1, R.id.btn2})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn1:
break;
case R.id.btn2:
//子線程發(fā)送
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = HANDLER_THREAD;
message.obj = "發(fā)送的線程:" + Thread.currentThread().getName();
handlerThreadHandler.sendMessage(message);
}
}).start();
break;
default:
}
}
/**
* 創(chuàng)建一個(gè)可以包含looper的子線程岭参,并開(kāi)啟
*/
private void createLooperHandler() {
HandlerThread handlerThread = new HandlerThread("handler_name1");
handlerThread.start();
handlerThreadHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case HANDLER_THREAD:
//添加上當(dāng)前線程名稱
final String thrMsg = (String) msg.obj + "\n 收到的線程:" + Thread
.currentThread().getName();
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessage.setText(thrMsg);
}
});
break;
default:
break;
}
}
};
}
public static void launch(Context context) {
Intent intent = new Intent(context, ThirdActivity.class);
context.startActivity(intent);
}
}
六反惕、Handler內(nèi)存泄露
Handler使用過(guò)程中,我們需要特別注意一個(gè)問(wèn)題演侯,那就是Handler可能會(huì)導(dǎo)致內(nèi)存泄漏姿染。
具體原因如下:
Handler的生命周期與Activity不同,Handler會(huì)關(guān)聯(lián)Looper來(lái)管理Message Queue秒际。這個(gè)隊(duì)列在整個(gè)Application的生命周期中存在悬赏,因此Handler不會(huì)因Activity的finish()方法而被銷毀。
非靜態(tài)(匿名)內(nèi)部類會(huì)持有外部對(duì)象程癌,當(dāng)我們這樣重寫(xiě)Handler時(shí)它就成為了一個(gè)匿名內(nèi)部類舷嗡,這樣如果調(diào)用finish方法時(shí)Handler有Message未處理的話,就會(huì)導(dǎo)致Activity不能被銷毀嵌莉。
解決方法:
- 可以在外部新建一個(gè)類进萄,這樣便可解決這個(gè)問(wèn)題。但這樣無(wú)疑過(guò)于麻煩了锐峭,內(nèi)部類更方便些中鼠。
- 可以同時(shí)使用靜態(tài)內(nèi)部類和弱引用,當(dāng)一個(gè)對(duì)象只被弱引用依賴時(shí)它便可以被GC回收沿癞。
注意:要static和弱引用要同時(shí)使用援雇,否則由于非靜態(tài)內(nèi)部類隱式持有了外部類Activity的引用,而導(dǎo)致Activity無(wú)法被釋放
public static class MyHandler extends Handler {
WeakReference<Activity> mWeakReference;
public MyHandler(Activity activity) {
mWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = mWeakReference.get();
if (activity == null) {
return;
}
switch (msg.what) {
case 101:
adapter.notifyDataSetChanged();
break;
default:
break;
}
}
}
七椎扬、總結(jié)
Looper 中還有一個(gè)特殊概念惫搏,那就是 ThreadLocal具温,ThreadLocal并不是線程,它的作用是可以在每個(gè)線程中存儲(chǔ)數(shù)據(jù)筐赔。ThreadLocal可以在不同的線程中互不干擾地存儲(chǔ)并提供數(shù)據(jù)铣猩,通過(guò)ThreadLocal可以輕松獲取每個(gè)線程的 Looper茴丰。當(dāng)然需要注意的是,線程是默認(rèn)沒(méi)有 Looper 的贿肩,如果需要使用 Handler 就必須為線程創(chuàng)建 Looper。
主線程之所以可以接收Handler消息汰规,是因?yàn)橹骶€程在啟動(dòng)時(shí),已經(jīng)創(chuàng)建了Looper對(duì)象控轿,這也是在主線程默認(rèn)可以使用 Handler 的原因。
Handler 的工作主要包含消息的發(fā)送和接受過(guò)程茬射。消息的發(fā)布主要post 的一系列方法以及 send 的一系列方法來(lái)實(shí)現(xiàn)的冒签,post 的一系列方法最終通過(guò) send 的一系列方法來(lái)實(shí)現(xiàn)的。
八萧恕、原理總結(jié):
1. ThreadLocal 的工作原理:
ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類刚梭,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后票唆,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)朴读,對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。
對(duì)于 Handler 來(lái)說(shuō)走趋,它需要獲取當(dāng)前線程的 Looper衅金,很顯然 Looper 的作用域就是線程,并且不同的線程具有不同的 Looper簿煌,這個(gè)時(shí)候通過(guò) ThreadLocal 就可以輕松實(shí)現(xiàn) Looper 在線程中的存取氮唯。
2. 消息隊(duì)列的工作原理:
消息隊(duì)列在 Android 中指的是 MessageQueue,MessageQueue主要包含兩個(gè)操作:插入和讀取姨伟。讀取操作本身會(huì)伴隨著刪除操作惩琉,插入和讀取對(duì)應(yīng)的方法分別為 enqueueMessage 和 next,其中
- enqueueMessage的作用是往消息隊(duì)列中插入一條消息夺荒;
- next 的作用是從消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除瞒渠;
盡管良蒸,MessageQueue叫消息隊(duì)列,但是它的內(nèi)部實(shí)現(xiàn)并不是用的隊(duì)列伍玖,實(shí)際上它是通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表诚啃,單鏈表在插入和刪除上比較有優(yōu)勢(shì)。
3. Looper 的工作原理:
Looper在 Android 的消息機(jī)制中扮演著消息循環(huán)的角色私沮,具體來(lái)說(shuō)就是不停地從MessageQueue 中查看是否有新的消息始赎,如果有新的消息就會(huì)立刻處理,否則就一直阻塞在那里仔燕。
Handler 的工作需要 Looper造垛,沒(méi)有 Looper 的線程就會(huì)報(bào)錯(cuò),那么如何為一個(gè)線程創(chuàng)建 Looper 呢晰搀?其實(shí)很簡(jiǎn)單五辽,通過(guò) Looper.prepare()即可為當(dāng)前線程創(chuàng)建一個(gè) Looper,接著通過(guò) Looper.loop()來(lái)開(kāi)啟消息循環(huán)外恕,如下所示:
new Thread(){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
loop方法是一個(gè)死循環(huán)杆逗,唯一跳出循環(huán)的方式 MessageQueue 的 next 方法返回 null。
當(dāng) Looper 的 quit 方法被調(diào)用時(shí)鳞疲,Looper 就會(huì)調(diào)用 MessageQueue 的 quit 或者 quitSafely 方法來(lái)通知消息隊(duì)列退出罪郊,當(dāng)消息對(duì)列被標(biāo)記為退出狀態(tài)是,它的 next 方法就會(huì)返回 null尚洽。
4. Handler 原理:
Handler 的工作主要包含消息的發(fā)送和接收過(guò)程腺毫。消息的發(fā)送可以通過(guò)post 的一系列方法以及 send 的一系列方法來(lái)實(shí)現(xiàn),post 的一系列方法最終是通過(guò) send 方法的一系列方法來(lái)實(shí)現(xiàn)的睛挚。
Handler 發(fā)送消息的過(guò)程僅僅是向消息隊(duì)列中插入了一條消息扎狱,MessageQueue 的 next 方法就會(huì)返回這條消息給 Looper叁熔,Looper 接收到消息后就開(kāi)始處理了荣回,最終消息由 Looper 交 Handler處理,即 Handler 的 dispatchMessage 方法就會(huì)被調(diào)用壕吹,這時(shí) Handler 就進(jìn)入了處理消息的階段。
參考博客
作者:N0tExpectErr0r
地址:https://blog.csdn.net/qq_21556263/article/details/82759061