本期主要內(nèi)容
- 1: Handler是什么穗酥?
- 2:為什么要使用Handler?
- 3: Handler /Looper/ MessageQueue/Message究竟是做什么的?
- 4:Handler如何去實(shí)現(xiàn)發(fā)送和處理消息
1、Handler是什么?
- Handler 是一個(gè)消息分發(fā)對(duì)象后专。handler是Android給我們提供用來更新UI的一套機(jī)制准颓,也是一套消息處理機(jī)制,我們可以發(fā)消息圃郊,也可以通過它處理消息。
- 1:To schedule messages and runnables to be executed as some point in the future 定時(shí)任務(wù)
- 2: To enqueue an action to be performed on a different thread than your own 在不同線程中執(zhí)行任務(wù)
2女蜈、為什么要使用Handler?
- 最根本的目的就是為了解決多線程并發(fā)的問題持舆!打個(gè)比方色瘩,如果在一個(gè)activity中有多個(gè)線程,并且沒有加鎖逸寓,就會(huì)出現(xiàn)界面錯(cuò)亂的問題居兆。但是如果對(duì)這些更新UI的操作都加鎖處理,又會(huì)導(dǎo)致性能下降竹伸。處于對(duì)性能的問題考慮泥栖,Android給我們提供這一套更新UI的機(jī)制我們只需要遵循這種機(jī)制就行了。不用再去關(guān)系多線程的問題勋篓,所有的更新UI的操作吧享,都是在主線程的消息隊(duì)列中去輪訓(xùn)的。
- 它把消息發(fā)送給Looper管理的MessageQueue譬嚣,并負(fù)責(zé)處理Looper分發(fā)給他的消息钢颂。
3、Handler 拜银、 Looper 殊鞭、Message 這三者都與Android異步消息處理線程相關(guān)的概念。那么什么叫異步消息處理線程呢尼桶?
異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無限的循環(huán)體之中疯汁,每循環(huán)一次牲尺,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息處理函數(shù)幌蚊,執(zhí)行完成一個(gè)消息后則繼續(xù)循環(huán)谤碳。若消息隊(duì)列為空,線程則會(huì)阻塞等待溢豆。
那么Android消息機(jī)制主要是指Handler的運(yùn)行機(jī)制蜒简,Handler運(yùn)行需要底層的MessageQueue和Looper支撐。其中MessageQueue采用的是單鏈表的結(jié)構(gòu)漩仙,Looper可以叫做消息循環(huán)搓茬。由于MessageQueue只是一個(gè)消息存儲(chǔ)單元,不能去處理消息队他,而Looper就是專門來處理消息的卷仑,Looper會(huì)以無限循環(huán)的形式去查找是否有新消息,如果有的話麸折,就處理锡凝,否則就一直等待著。
我們知道垢啼,Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)窜锯,需要注意的是张肾,線程默認(rèn)是沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper锚扎,因?yàn)槟J(rèn)的UI主線程吞瞪,也就是ActivityThread,ActivityThread被創(chuàng)建的時(shí)候就會(huì)初始化Looper驾孔,這也是在主線程中默認(rèn)可以使用Handler的原因芍秆。
Handler類包含如下方法用于發(fā)送、處理消息:
void handleMessage(Message msg):處理消息的方法助币。該方法通常用于被重寫浪听。
final boolean hasMessages(int what):檢查消息隊(duì)列中是否包含what屬性為指定值的消息螟碎。
final boolean hasMessages(int what, Object object):檢查消息隊(duì)列中是否包含what屬性為指定值且object屬性為指定對(duì)象的消息眉菱。
多個(gè)重載的Message obtainMessage():獲取消息。
sendEmptyMessage(int what):發(fā)送空消息掉分。
final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后發(fā)送空消息俭缓。
final boolean sendMessage(Message msg):立即發(fā)送消息。
final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后發(fā)送消息酥郭。
-
Message:Handler接收和處理的消息對(duì)象华坦。
- 2個(gè)整型數(shù)值:輕量級(jí)存儲(chǔ)int類型的數(shù)據(jù)。
- 1個(gè)Object:任意對(duì)象不从。
- replyTo:線程通信時(shí)使用惜姐。
- what:用戶自定義的消息碼,讓接收者識(shí)別消息椿息。
-
MessageQueue:Message的隊(duì)列歹袁。
- 采用先進(jìn)先出的方式管理Message。
- 每一個(gè)線程最多可以擁有一個(gè)寝优。
-
Looper:消息泵条舔,是MessageQueue的管理者,會(huì)不斷從MessageQueue中取出消息乏矾,并將消息分給對(duì)應(yīng)的Handler處理孟抗。
- 每個(gè)線程只有一個(gè)Looper。
- Looper.prepare():為當(dāng)前線程創(chuàng)建Looper對(duì)象钻心。
- Looper.myLooper():可以獲得當(dāng)前線程的Looper對(duì)象凄硼。
- Handler:能把消息發(fā)送給MessageQueue,并負(fù)責(zé)處理Looper分給它的消息捷沸。
4:Handler如何去實(shí)現(xiàn)發(fā)送和處理消息
1: 使用Thread發(fā)送消息
2: 使用Runnable發(fā)送消息
3: 使用Handler下載文件并更新進(jìn)度條
1: 首先配置權(quán)限信息 讀寫權(quán)限問題
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />
2: 在Android 6.0之后必須動(dòng)態(tài)檢測(cè)權(quán)限摊沉。所以在下載操作開始之前需要進(jìn)行權(quán)限判斷
權(quán)限問題處理方法
4: 使用 handler實(shí)現(xiàn)倒計(jì)時(shí)功能
首先強(qiáng)調(diào)一下需要注意的問題吧
Handler在進(jìn)行異步操作并處理返回結(jié)果時(shí)經(jīng)常被使用亿胸。通常我們的代碼會(huì)這樣實(shí)現(xiàn)坯钦。
但是预皇,其實(shí)上面的代碼可能導(dǎo)致內(nèi)存泄露,當(dāng)你使用Android lint工具的話婉刀,會(huì)得到這樣的警告:
以下幾種情況都可能存在內(nèi)存泄漏的情況
1.當(dāng)一個(gè)Android應(yīng)用啟動(dòng)的時(shí)候吟温,會(huì)自動(dòng)創(chuàng)建一個(gè)供應(yīng)用主線程使用的Looper實(shí)例。Looper的主要工作就是一個(gè)一個(gè)處理消息隊(duì)列中的消息對(duì)象突颊。在Android中鲁豪,所有Android框架的事件(比如Activity的生命周期方法調(diào)用和按鈕點(diǎn)擊等)都是放入到消息中,然后加入到Looper要處理的消息隊(duì)列中律秃,由Looper負(fù)責(zé)一條一條地進(jìn)行處理爬橡。主線程中的Looper生命周期和當(dāng)前應(yīng)用一樣長(zhǎng)。
2.當(dāng)一個(gè)Handler在主線程進(jìn)行了初始化之后棒动,我們發(fā)送一個(gè)target為這個(gè)Handler的消息到Looper處理的消息隊(duì)列時(shí)糙申,實(shí)際上已經(jīng)發(fā)送的消息已經(jīng)包含了一個(gè)Handler實(shí)例的引用,只有這樣Looper在處理到這條消息時(shí)才可以調(diào)用Handler#handleMessage(Message)完成消息的正確處理船惨。
3.在Java中柜裸,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會(huì)隱式地持有其外部類的引用。靜態(tài)的內(nèi)部類不會(huì)持有外部類的引用粱锐。
解決方式
- 要解決這種問題疙挺,思路就是不適用非靜態(tài)內(nèi)部類,繼承Handler時(shí)怜浅,要么是放在單獨(dú)的類文件中铐然,要么就是使用靜態(tài)內(nèi)部類。因?yàn)殪o態(tài)的內(nèi)部類不會(huì)持有外部類的引用恶座,所以不會(huì)導(dǎo)致外部類實(shí)例的內(nèi)存泄露搀暑。當(dāng)你需要在靜態(tài)內(nèi)部類中調(diào)用外部的Activity時(shí),我們可以使用弱引用來處理奥裸。另外關(guān)于同樣也需要將Runnable設(shè)置為靜態(tài)的成員屬性险掀。注意:一個(gè)靜態(tài)的匿名內(nèi)部類實(shí)例不會(huì)持有外部類的引用。
面試要點(diǎn)
- 這里 我們可以逐步分析:
public Handler(Callback callback, boolean async) {
//得到主線程(已經(jīng)被ActivityThread初始化好)的looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//得到主線程(已經(jīng)被ActivityThread初始化好)的looper的MessageQueue湾宙,注釋:①
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
注意樟氢,看到了嗎? 這里是核心:mLooper = Looper.myLooper();侠鳄,
public static Looper myLooper() {
return sThreadLocal.get();
}
而這個(gè)Looper對(duì)象埠啃,是在我們啟動(dòng)程序的時(shí)候,也就是ActivityThread 中的main方法幫我們初始化的伟恶,也就是我們主線程的Looper碴开。
public static void prepareMainLooper() {
···
prepare(false);
···
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
我們?cè)谧泳€程 使用 handler的實(shí)例進(jìn)行 sendMessage(Message msg) 的時(shí)候:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//這里的代碼和上面的注釋①對(duì)應(yīng)。
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
最終走向enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最終把我們的Message,放進(jìn)了主線程的MessageQueue里面進(jìn)行循環(huán)潦牛!