Android Handler機(jī)制系列文章整體內(nèi)容如下:
- Android Handler機(jī)制1之Thread
- Android Handler機(jī)制2之ThreadLocal
- Android Handler機(jī)制3之SystemClock類(lèi)
- Android Handler機(jī)制4之Looper與Handler簡(jiǎn)介
- Android Handler機(jī)制5之Message簡(jiǎn)介與消息對(duì)象對(duì)象池
- Android Handler機(jī)制6之MessageQueue簡(jiǎn)介
- Android Handler機(jī)制7之消息發(fā)送
- Android Handler機(jī)制8之消息的取出與消息的其他操作
- Android Handler機(jī)制9之Handler的Native實(shí)現(xiàn)前奏之Linux IO多路復(fù)用
- Android Handler機(jī)制10之Handdler的Native實(shí)現(xiàn)Native的實(shí)現(xiàn)
- Android Handler機(jī)制11之Handler機(jī)制總結(jié)
- Android Handler機(jī)制12之Callable、Future和FutureTask
- Android Handler機(jī)制13之AsyncTask源碼解析
本片文章的主要內(nèi)容如下:
- 1、Handler機(jī)制的思考
- 2、Handler消息機(jī)制
- 3、享元模式
- 4、HandlerThread
- 5、Handler的內(nèi)存泄露
- 6、Handler的面試題
一、Handler機(jī)制的思考
- 先提一個(gè)問(wèn)題哈,如果讓你設(shè)計(jì)一個(gè)操作系統(tǒng),你會(huì)怎么設(shè)計(jì)?
我們一般操作系統(tǒng)都會(huì)有一個(gè)消息系統(tǒng),里面有個(gè)死循環(huán),不斷的輪訓(xùn)處理其他各種輸入設(shè)備輸入的信息,比如你在在鍵盤(pán)的輸入,鼠標(biāo)的移動(dòng)等。這些輸入信息最終都會(huì)進(jìn)入你的操作系統(tǒng),然后由操作系統(tǒng)的內(nèi)部輪詢(xún)機(jī)制挨個(gè)處理這些信息。
- 那Android系統(tǒng)那?它內(nèi)部是怎么實(shí)現(xiàn)的? 如果讓你設(shè)計(jì),你會(huì)怎么設(shè)計(jì)?
答:如果讓我設(shè)計(jì),肯定和上面一樣:
- 1 設(shè)計(jì)一個(gè)類(lèi),里面有一個(gè)死循環(huán)去做循環(huán)操作;
- 2 用一個(gè)類(lèi)來(lái)抽象代表各種輸入信息/消息;這個(gè)信息/消息應(yīng)該還有一個(gè)唯一標(biāo)識(shí)符字段;如果這個(gè)信息里面有個(gè)對(duì)象來(lái)保存對(duì)應(yīng)的鍵值對(duì);方便其他人往這個(gè)信息/消息 存放信息;這個(gè)信息/消息應(yīng)該有個(gè)字段標(biāo)明消息產(chǎn)生的時(shí)間;
- 3 而上面的這些 信息/消息 又組成一個(gè)集合。常用集合很多,那是用ArrayList好還是LinkedList或者M(jìn)ap好那?因?yàn)榍懊嬲f(shuō)了是一個(gè)死循環(huán)去處理,所以這個(gè)集合最好是"線(xiàn)性和排序的"比較好,因?yàn)檩斎胗邢群螅话愣际前凑蛰斎氲臅r(shí)間先后來(lái)構(gòu)成。既然這樣就排除了Map,那么就剩下來(lái)了ArrayList和LinkedList。我們知道一個(gè)操作系統(tǒng)的事件是很多的,也就是說(shuō)對(duì)應(yīng)的信息/消息很多,所以這個(gè)集合肯定會(huì)面臨大量的"插入"操作,而在"插入"效能這塊,LinkedList有著明顯的優(yōu)勢(shì),所以這個(gè)集合應(yīng)該是一個(gè)鏈表,但是鏈表又可以分為很多種,因?yàn)槭蔷€(xiàn)性排序的,所以只剩下"雙向鏈表"和"單向鏈表”,但是由于考慮下手機(jī)的性能問(wèn)題,大部分人肯定會(huì)傾向于選擇"單向鏈表",因?yàn)?單項(xiàng)鏈表"在增加和刪除上面的復(fù)雜度明顯低于"雙向鏈表"。
- 4、最后還應(yīng)該有兩個(gè)類(lèi),一個(gè)負(fù)責(zé)生產(chǎn)這個(gè)輸入信息,一個(gè)負(fù)責(zé)消費(fèi)這些信息。因?yàn)樯婕暗较M(fèi)端,所以上面2中說(shuō)的信息/消息應(yīng)該有一個(gè)字段負(fù)責(zé)指向消費(fèi)端。
經(jīng)過(guò)上面的思考,大家是不是發(fā)現(xiàn)和其實(shí)我們Handler的機(jī)制基本上一致。Looper負(fù)責(zé)輪詢(xún);Message代表消息,為了區(qū)別對(duì)待,用what來(lái)做為標(biāo)識(shí)符,when表示時(shí)間,data負(fù)責(zé)存放鍵值對(duì);MessageQueue則代表Message的集合,Message內(nèi)部同時(shí)也是單項(xiàng)鏈表的。通過(guò)上面的分析,希望大家對(duì)Handler機(jī)制的總體設(shè)計(jì)有不一樣的感悟。
二、Handler消息機(jī)制
如果你想要讓一個(gè)Android的應(yīng)用程序反應(yīng)靈敏,那么你必須防止它的UI線(xiàn)程被阻塞。同樣地,將這些阻塞的或者計(jì)算密集型的任務(wù)轉(zhuǎn)到工作線(xiàn)程去執(zhí)行也會(huì)提高程序的響應(yīng)靈敏性。然而,這些任務(wù)的執(zhí)行結(jié)果通常需要重新更新UI組件的顯示,但該操作只能在UI線(xiàn)程中去執(zhí)行。有一些方法解決了UI線(xiàn)程的阻塞問(wèn)題,例如阻塞對(duì)象,共享內(nèi)存以及管道技術(shù)。Android為了解決這個(gè)問(wèn)題,提供了一種自有的消息傳遞機(jī)制——Handler。Handler是Android Framework架構(gòu)中的一個(gè)基礎(chǔ)組件,它實(shí)現(xiàn)了一種非阻塞的消息傳遞機(jī)制,在消息轉(zhuǎn)換的過(guò)程中,消息的生產(chǎn)者和消費(fèi)者都不會(huì)阻塞。
Handler由以下部分組成:
- Handler
- Message
- MessageQueue
- Looper
下面我們來(lái)了解下它們及它們之間的交互。
(一)、Handler
Handler 是線(xiàn)程間傳遞消息的即時(shí)接口,生產(chǎn)線(xiàn)程和消費(fèi)線(xiàn)程用以下操作來(lái)使用Handler
- 生產(chǎn)線(xiàn)程:在消息隊(duì)列中創(chuàng)建、插入或移除消息
- 消費(fèi)線(xiàn)程:處理消息

每個(gè)Handler都有一個(gè)與之關(guān)聯(lián)的Looper和消息隊(duì)列。有兩種創(chuàng)建Handler方式(這里不是說(shuō)只有兩個(gè)構(gòu)造函數(shù),而是說(shuō)把它的構(gòu)造函數(shù)分為兩類(lèi))
- 通過(guò)默認(rèn)的構(gòu)造方法,使用當(dāng)前線(xiàn)程中關(guān)聯(lián)的Looper
- 顯式地指定使用Looper
如果沒(méi)有指定Looper的Handler是無(wú)法工作的,因?yàn)樗鼰o(wú)法將消息放到消息隊(duì)列中。同樣地,它無(wú)法獲取要處理的消息。
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
如果是使用上面Handler的構(gòu)造函數(shù),它會(huì)檢查當(dāng)前線(xiàn)程有沒(méi)有可用的Looper對(duì)象,如果沒(méi)有,它會(huì)拋出一個(gè)運(yùn)行時(shí)的異常,如果正常的話(huà),Handler會(huì)持有Looper中的消息隊(duì)列對(duì)象的引用。
PS:同一個(gè)線(xiàn)程中多的Handler分享一個(gè)同樣的消息隊(duì)列,因?yàn)樗麄兎窒淼氖峭粋€(gè)Looper對(duì)象
Callback參數(shù)是一個(gè)可選的參數(shù),如果提供的話(huà),它將會(huì)處理由Looper分發(fā)的過(guò)來(lái)的消息。
(二)、Message
Message 是容納任意數(shù)據(jù)的容器。生產(chǎn)線(xiàn)程發(fā)送消息給Handler,Handler將消息加入到消息隊(duì)列中。消息提供了三種額外的信息,以供Handler和消息隊(duì)列處理時(shí)使用:
- what:一種標(biāo)識(shí)符,Handler能使用它來(lái)區(qū)分不同的消息,從而采取不同的處理方法
- time:告訴消息隊(duì)列合適處理消息
- target:表示那一個(gè)Handler應(yīng)該處理消息
android.os.Message 消息一般是通過(guò)Handler中以下方法來(lái)創(chuàng)建的
public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
消息從消息池中獲取得到,方法中提供的參數(shù)會(huì)放到消息體對(duì)應(yīng)的字段中。Handler同樣可以設(shè)置消息的目標(biāo)為其自身,這允許我們進(jìn)行鏈?zhǔn)秸{(diào)用,比如:
mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();
消息池是一個(gè)消息對(duì)象的單項(xiàng)鏈表集合,它的最大長(zhǎng)度是50。在Handler處理完這條消息之后,消息隊(duì)列把這個(gè)對(duì)象返回到消息池中,并且重置其所有字段。
當(dāng)使用Handler調(diào)用post方法來(lái)執(zhí)行一個(gè)Runnable時(shí),Handler隱式地創(chuàng)建了一個(gè)新的消息,并且設(shè)置callback參數(shù)來(lái)存儲(chǔ)這個(gè)Runnable。
Message m = Message.obtain();
m.callback = r;

生產(chǎn)線(xiàn)程發(fā)送消息給 Handler 的交互
在上圖中,我們能看到生產(chǎn)線(xiàn)程和 Handler 的交互。生產(chǎn)者創(chuàng)建了一個(gè)消息,并且發(fā)送給Handler,隨后Handler 將這個(gè)消息加入消息隊(duì)列中,在未來(lái)某個(gè)時(shí)間,Handler 會(huì)在消費(fèi)小城中處理這個(gè)消息。
(三)、MessageQueue
MessageQueue是一個(gè)消息體對(duì)象的無(wú)界的單向鏈表集合,它按照時(shí)序?qū)⑾⒉迦腙?duì)列,最小的時(shí)間戳將會(huì)被首先處理。

消息隊(duì)列也通過(guò)SystemClock.uptimeMillis獲取當(dāng)前時(shí)間,維護(hù)一個(gè)阻塞閥值(dispatch barrier)。當(dāng)一個(gè)消息體的時(shí)間戳低于這個(gè)值的時(shí)候,消息就會(huì)分發(fā)給Handler進(jìn)行處理
Handler 提供了三種方式來(lái)發(fā)送消息:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
以延遲的方式發(fā)送消息,是設(shè)置了消息體的time字段為SystemClock.uptimeMillis()+delayMillis。然而,通過(guò)sendMessageAtFontOfQueue方法是把消息插入到隊(duì)首,會(huì)將其時(shí)間字段設(shè)置為0,消息會(huì)在下一次輪訓(xùn)時(shí)被處理。需要謹(jǐn)慎使用這個(gè)方法,因?yàn)樗赡軙?huì)英系那個(gè)消息隊(duì)列,造成順序問(wèn)題,或是其他不可預(yù)料的副作用。
(四)、消息隊(duì)列、Handler、生產(chǎn)線(xiàn)程的交互
現(xiàn)在我們可以概括消息隊(duì)列、Handler、生產(chǎn)線(xiàn)程的交互:

上圖中,多個(gè)生產(chǎn)線(xiàn)程提交消息到不同的Handler中,然而,不同的Handler都與同一個(gè)Looper對(duì)象關(guān)聯(lián),因此所有的消息都加入到同一個(gè)消息隊(duì)列中。這一點(diǎn)非常重要,Android中創(chuàng)建的許多不同的Handler都關(guān)聯(lián)到主線(xiàn)程的Looper。
比如:
- The Choreographer:處理垂直同步與幀更新
- The ViewRoot:處理輸入和窗口時(shí)間,配置修改等等
- The InputMethodManager不會(huì)大量生成消息,因?yàn)檫@可能會(huì)抑制處理系統(tǒng)
(五)、Looper
Looper 從消息隊(duì)列中讀取消息,然后分發(fā)給對(duì)應(yīng)的Handler處理。一旦消息超過(guò)阻塞閥,那么Looper就會(huì)在下一輪讀取過(guò)程中讀取到它。Looper在沒(méi)有消息分發(fā)的時(shí)候變成阻塞狀態(tài),當(dāng)有消息可用時(shí)會(huì)繼續(xù)輪詢(xún)。
每個(gè)線(xiàn)程只能關(guān)聯(lián)一個(gè)Looper,給線(xiàn)程附加的另外的Looper會(huì)導(dǎo)致運(yùn)行時(shí)的異常。通過(guò)使用Looper的Threadlocal對(duì)象可以保證線(xiàn)程只關(guān)聯(lián)一個(gè)Looper對(duì)象。
調(diào)用Looper.quit()方法會(huì)立即終止Looper,并且丟棄消息隊(duì)列中的已經(jīng)通過(guò)阻塞閥的所有消息。調(diào)用Looper.quitSafely()方法能夠保證所有待分發(fā)的消息在隊(duì)列中等待的消息被丟棄前得到處理。

Handler 與消息隊(duì)列和Looper 直接交互的整體流程
Looper 應(yīng)在線(xiàn)程的run方法中初始化。調(diào)用靜態(tài)方法Looper.prepare()會(huì)檢查線(xiàn)程是否與一個(gè)已存在的Looper關(guān)聯(lián)。這個(gè)過(guò)程的實(shí)現(xiàn)是通過(guò)Looper類(lèi)中的ThreadLocal對(duì)象來(lái)檢查L(zhǎng)ooper對(duì)象是否存在。如果Looper不存在,將會(huì)創(chuàng)建一個(gè)新的Looper對(duì)象和一個(gè)新的消息隊(duì)列。如下代碼展示了這個(gè)過(guò)程
PS: 公有的prepare方法會(huì)默認(rèn)調(diào)用prepare(true)
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));
}
Handler 現(xiàn)在能接收到消息并加入到消息隊(duì)列中,執(zhí)行靜態(tài)方法Looper.loop()方法會(huì)開(kāi)始將消息從消息隊(duì)列中出隊(duì)。每次輪訓(xùn)迭代器指向下一條消息,接著分發(fā)消息對(duì)應(yīng)目標(biāo)地的Handler,然后回收消息到消息池中。Looper.looper()方法循環(huán)執(zhí)行這個(gè)過(guò)程,直到Looper終止。下面代碼片段展示這個(gè)過(guò)程:
public static void loop() {
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
(六)、整體流程圖

三、享元模式
享元模式是對(duì)象池的一種實(shí)現(xiàn),盡可能減少內(nèi)存的使用,使用緩存來(lái)共享可用的對(duì)象,避免創(chuàng)建過(guò)多的對(duì)象。Android中Message使用的設(shè)計(jì)模式就是享元模式,將獲取Message通過(guò)obtain方法從對(duì)象池獲取,Message使用結(jié)束通過(guò)recyle將Message歸還給對(duì)象池,達(dá)到循環(huán)利用對(duì)象,避免重復(fù)創(chuàng)建的目的
(一)概念
享元模式(Flywight Pattern) 是一種軟件設(shè)計(jì)模式。它使用共享物件,用來(lái)盡可能減少內(nèi)存使用量以及分享咨詢(xún)給盡可能多的相似物件;它適用于只是因重復(fù)而導(dǎo)致無(wú)法使用無(wú)法令人接受的大量?jī)?nèi)存的大量物件。通常物件中的部分狀態(tài)時(shí)可以分享的。常見(jiàn)的做法是把他們放到外部數(shù)據(jù)結(jié)構(gòu),當(dāng)需要使用時(shí)將他們傳遞給享元。
(二) 為什么要用享元模式
- 當(dāng)一個(gè)軟件系統(tǒng)在運(yùn)行時(shí)產(chǎn)生的對(duì)象數(shù)量太多,將導(dǎo)致運(yùn)行代價(jià)過(guò)高,帶來(lái)系統(tǒng)性能下降的問(wèn)題。所以需要采用一個(gè)共享來(lái)避免大量擁有相同內(nèi)容對(duì)象的開(kāi)銷(xiāo)。在Java中,String類(lèi)型就是使用享元模式。String對(duì)象是final類(lèi)型,對(duì)象一旦創(chuàng)建就不可改變。在Java字符串常量都是存在常量池中的,Java會(huì)確保一個(gè)字符串常量在常量池只有一個(gè)拷貝。
- 是對(duì)對(duì)象池的一種實(shí)現(xiàn),共享對(duì)象,避免重復(fù)的創(chuàng)建,采用一個(gè)共享來(lái)避免大量擁有相同內(nèi)容對(duì)象的開(kāi)銷(xiāo)。使用享元模式可以有效支持大量的細(xì)粒度對(duì)象。
Flyweight,如果很多很小的對(duì)象它們有很多相同的東西,并且在很多地方用到,那就可以把它們抽取成一個(gè)對(duì)象,把不同的東西變成外部屬性,作為方法的參數(shù)傳入。
String類(lèi)型的對(duì)象創(chuàng)建后就不可改變,如果兩個(gè)String對(duì)象所包含的內(nèi)容相同時(shí),JVM只創(chuàng)建一個(gè)String對(duì)象對(duì)應(yīng)這兩個(gè)不同的對(duì)象引用。字符串常量池。
(三) 核心思想
1、概念
運(yùn)行共享技術(shù)有效地支持大量細(xì)粒度對(duì)象的復(fù)用。系統(tǒng)只使用少量的對(duì)象,而這些對(duì)象都很相似,狀態(tài)變化很小,可以實(shí)現(xiàn)對(duì)象的多次復(fù)用。由于享元模式要求能夠共享對(duì)象必須是細(xì)顆粒對(duì)象,因此它又稱(chēng)為輕量級(jí)模式,它是一種對(duì)象結(jié)構(gòu)模式。
享元對(duì)象共享的關(guān)鍵是區(qū)分了內(nèi)部狀態(tài)(Intrinsic State)和外部狀態(tài)(Extrinsic State)。
2、內(nèi)部狀態(tài)(Intrinsic State)
存儲(chǔ)在享元對(duì)象內(nèi)部并且不會(huì)隨環(huán)境改變而改變的狀態(tài),內(nèi)部狀態(tài)可以共享。
3、外部狀態(tài)(Extrinsic State)
享元對(duì)象的外部狀態(tài)通常由客戶(hù)端保存,并在享元對(duì)象被創(chuàng)建之后,需要使用的時(shí)候再傳入到享元對(duì)象內(nèi)部。隨環(huán)境改變而改變的、不可以共享的狀態(tài)。一個(gè)外部狀態(tài)與另一個(gè)外狀態(tài)是相互獨(dú)立的。
由于區(qū)分了內(nèi)部狀態(tài)和外部狀態(tài),我們可以將具有相同內(nèi)部狀態(tài)的對(duì)象存儲(chǔ)在享元池中,享元池中的對(duì)象是可以實(shí)現(xiàn)共享的,需要的時(shí)候?qū)?duì)象從享元池中取出,實(shí)現(xiàn)對(duì)象的復(fù)用。通過(guò)向取出的對(duì)象注入不同的外部狀態(tài),可以得到一系列相似的對(duì)象,而這些對(duì)象在內(nèi)存中實(shí)際上只存儲(chǔ)一份。
(四) 享元模式分類(lèi)
- 單純享元模式
- 復(fù)合享元模式
1、單純享元模式結(jié)構(gòu)重要核心模塊
抽象享元角色:為具體享元角色規(guī)定了必須實(shí)現(xiàn)的方法,而外部狀態(tài)時(shí)以參數(shù)的行賄通過(guò)此方法傳入。在Java中可以由抽象類(lèi)、接口擔(dān)當(dāng)
具體享元角色:實(shí)現(xiàn)抽象橘色規(guī)定的方法。如果存在內(nèi)部狀態(tài),就負(fù)責(zé)為內(nèi)部狀態(tài)提供存儲(chǔ)空間。
享元端角色*:負(fù)責(zé)創(chuàng)建和管理享元角色。想要達(dá)到共享目的,這個(gè)角色的實(shí)現(xiàn)是關(guān)鍵。
客戶(hù)端角色:維護(hù)對(duì)所有享元對(duì)象的引用,而且還需要存儲(chǔ)對(duì)應(yīng)的外部狀態(tài)。
單純享元模式和創(chuàng)新型的簡(jiǎn)單工廠(chǎng)模式實(shí)際上非常相似,但是它的重點(diǎn)或者用意卻和工廠(chǎng)模式截然不同。工廠(chǎng)模式的使用主要是為了使用系統(tǒng)不依賴(lài)于實(shí)現(xiàn)的細(xì)節(jié);而在享元模式的主要目的是避免大量擁有相同內(nèi)容對(duì)象的開(kāi)銷(xiāo)。
2、復(fù)合享元模式
抽象享元角色:為了具體享元角色規(guī)定了必須實(shí)現(xiàn)的方法,而外部狀態(tài)就是以參數(shù)的形式聽(tīng)過(guò)此方法傳入。在Java中可以由抽象類(lèi)、接口來(lái)?yè)?dān)當(dāng)。
具體享元角色:實(shí)現(xiàn)抽象角色規(guī)定的方法。如果存在內(nèi)部狀態(tài),就負(fù)責(zé)為內(nèi)部狀態(tài)提供存儲(chǔ)空間。
復(fù)合享元角色:它所代表的對(duì)象是不可以共享的,并且可以分解為多個(gè)單純享元對(duì)象的組合。
享元工廠(chǎng)角色:負(fù)責(zé)創(chuàng)建和管理享元角色。想要達(dá)到共享的目的,這個(gè)角色的實(shí)現(xiàn)是關(guān)鍵!
客戶(hù)端角色:維護(hù)對(duì)所有享元對(duì)象的引用,而且還需要存儲(chǔ)對(duì)應(yīng)的外部狀態(tài)。
(五) 享元模式的使用場(chǎng)景
一般在如下場(chǎng)景中使用享元模式
- 1 一個(gè)系統(tǒng)有大量相同或相似的對(duì)象,造成內(nèi)存大量耗費(fèi)。
- 2 對(duì)象大部分狀態(tài)都可以外部化,可以將這些外部狀態(tài)傳入對(duì)象中。
- 3 再使用享元模式時(shí)需要維護(hù)一個(gè)存儲(chǔ)享元對(duì)象的享元池,而這需要耗費(fèi)一定的系統(tǒng)資源,因此,應(yīng)該在需要多次重復(fù)使用享元對(duì)象時(shí)才值得使用享元模式。
四、HandlerThread
(一)、HandlerThread 簡(jiǎn)介
我們看到HandlerThread很快就會(huì)聯(lián)想到Handler。Android中Handler的使用,一般都在UI線(xiàn)程中執(zhí)行,因此在Handler接受消息后,處理消息時(shí),不能做一些很耗時(shí)的操作,否則將出現(xiàn)ANR錯(cuò)誤。Android中專(zhuān)門(mén)提供了HandlerThread類(lèi),來(lái)解決該類(lèi)問(wèn)題。HandlerThread是一個(gè)線(xiàn)程專(zhuān)門(mén)處理Handler的消息,依次從Handler的隊(duì)列中獲取信息,逐個(gè)進(jìn)行處理,保證安全,不會(huì)出現(xiàn)混亂引發(fā)的異常。HandlerThread繼承于Thread,所以它卑職就是一個(gè)Thread。與普通Thread的差別就在于,它有個(gè)Looper成員變量。
我們看下官方對(duì)它的簡(jiǎn)介:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
翻譯一下:
HandlerThread可以創(chuàng)建一個(gè)帶有l(wèi)ooper的線(xiàn)程。Looper對(duì)象可以用于創(chuàng)建Handler類(lèi)來(lái)進(jìn)行調(diào)度。
(二)、類(lèi)源碼解析
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
// 線(xiàn)程的優(yōu)先級(jí)
int mPriority;
// 線(xiàn)程id
int mTid = -1;
// Looper對(duì)象,消息對(duì)象以及循環(huán)
Looper mLooper;
public HandlerThread(String name) {
super(name);
//設(shè)置默認(rèn)的線(xiàn)程優(yōu)先級(jí)
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
// 自定義設(shè)置線(xiàn)程優(yōu)先級(jí)
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
//如果有需要這個(gè)方法可以重寫(xiě),例如可以在這里聲明這個(gè)Handler關(guān)聯(lián)此線(xiàn)程
protected void onLooperPrepared() {
}
//Thread線(xiàn)程的run方法
@Override
public void run() {
//獲取當(dāng)前線(xiàn)程的id
mTid = Process.myTid();
// 一旦調(diào)用這句代碼,就在此線(xiàn)程中創(chuàng)建了Looper對(duì)象,這就是為什么我們要在調(diào)用線(xiàn)程start()方法后,才能得到Looper對(duì)象,即當(dāng)調(diào)用Looper.myLooper()時(shí)不為null
Looper.prepare();
// 同步代碼塊,意思就是當(dāng)獲取mLooper對(duì)象后對(duì)象后,喚醒所有線(xiàn)程
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
// 設(shè)置線(xiàn)程優(yōu)先級(jí)
Process.setThreadPriority(mPriority);
//調(diào)用上面的方法,需要用戶(hù)重寫(xiě)
onLooperPrepared();
// 開(kāi)啟消息循環(huán)
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
// 如果線(xiàn)程已經(jīng)死了,所以返回null
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
// 同步代碼塊,正好和上面(run()方法里面的)形成對(duì)應(yīng),就是說(shuō),只要線(xiàn)程活著并且我的looper為null,那么我就讓你一直等
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
// 退出消息循環(huán)
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
// 返回線(xiàn)程ID
public int getThreadId() {
return mTid;
}
}
整體來(lái)說(shuō)上面代碼還是比較淺顯易懂的。主要作用是建立了一個(gè)線(xiàn)程,并且創(chuàng)建了消息隊(duì)列,有自己的Looper,可以讓我們?cè)谧约壕€(xiàn)程中分發(fā)和處理消息。
quit和quitSafely都是退出HandlerThread的消息循環(huán),其分別調(diào)用Looper的quit和quitSafely方法。我們?cè)谶@里簡(jiǎn)單說(shuō)下區(qū)別:
- 1 quit方法會(huì)將消息隊(duì)列中的所有消息移除(延遲消息和非延遲消息)。
- 2 quitSafely 會(huì)將消息隊(duì)列所有延遲消息移除。非延遲消息則派發(fā)出去讓Handler去處理。
- quitSafely相比于quit方法安全之處在于清空消息之前會(huì)派發(fā)所有的非延遲消息。
(三)、HandlerThread的使用
通過(guò)上面的源碼,我們大概也能推測(cè)出HandlerThread的使用步驟,它的使用步驟如下:
- 第一步:創(chuàng)建一個(gè)HandlerThread實(shí)例,本質(zhì)是創(chuàng)建一個(gè)包含Looper的線(xiàn)程。比如
HandlerThread handlerThread=new HandlerThread("name")
- 第二步:開(kāi)啟線(xiàn)程
handlerThread.start()
- 第三步:獲得線(xiàn)程Looper
Looper looper=handlerThread.getLooper();
- 第四步:創(chuàng)建Handler,并用looper初始化
Handler handler=new Handler(looper);
- 第五步:利用handle進(jìn)行一些操作
- 第六步:調(diào)用quit()或者quitSafely()來(lái)終止它的循環(huán)
所以說(shuō)整體流程如下:
當(dāng)我們使用HandlerThrad創(chuàng)建一個(gè)線(xiàn)程,它start()之后會(huì)在它的線(xiàn)程創(chuàng)建一個(gè)Looper對(duì)象且初始化一個(gè)MessageQueue,通過(guò)Looper對(duì)象在他的線(xiàn)程構(gòu)建一個(gè)Handler對(duì)象,然后我們通過(guò)Handler發(fā)送消息的形式將任務(wù)發(fā)送到MessaegQueue中,因?yàn)長(zhǎng)ooper是順序處理消息的,所以當(dāng)有多個(gè)任務(wù)存在時(shí)會(huì)順序的排隊(duì)執(zhí)行。但我們不使用的時(shí)候我們應(yīng)該調(diào)用它的quit()或者quitSafely()來(lái)終止它的循環(huán)。
(四)、HandlerThread和普通Thread的比較
HandlerThread繼承自Thread,當(dāng)線(xiàn)程開(kāi)啟時(shí),也就是它run方法運(yùn)行起來(lái)后,線(xiàn)程同時(shí)創(chuàng)建了了一個(gè)含有消息隊(duì)列的Looper,并對(duì)外提供自己這個(gè)Looper對(duì)象的get方法。
(五)、HandlerThread的使用場(chǎng)景
- 1、開(kāi)發(fā)中如果多次使用類(lèi)似new Thread(){...}.start()。這種方式開(kāi)啟一個(gè)子線(xiàn)程,會(huì)創(chuàng)建多個(gè)匿名線(xiàn)程,使得程序運(yùn)行起來(lái)越來(lái)越慢,而HandlerThread自帶Looper使他可以通過(guò)消息來(lái)多次重復(fù)使用當(dāng)前線(xiàn)程,節(jié)省開(kāi)支。
- 2、android系統(tǒng)提供的Handler類(lèi)內(nèi)部的Looper默認(rèn)綁定的是UI線(xiàn)程的消息隊(duì)列,對(duì)于非UI線(xiàn)程又想使用消息機(jī)制,那么HandlerThread內(nèi)部的Looper是最合適的,它不會(huì)干擾或阻塞UI線(xiàn)程。
- 3、HandlerThread適合處理本地I/O讀寫(xiě)操作(比如數(shù)據(jù)庫(kù)),因?yàn)楸镜豂/O操作大多數(shù)的耗時(shí)屬于毫秒級(jí)別的,對(duì)于單線(xiàn)程+異步隊(duì)列的形式不會(huì)產(chǎn)生較大的阻塞。而網(wǎng)絡(luò)操作相對(duì)于比較耗時(shí),容易阻塞后面的請(qǐng)求,因此在這個(gè)HandlerThread中不合適加入網(wǎng)絡(luò)操作。
(五) 小結(jié):
- 1、HandlerThread將loop轉(zhuǎn)到子線(xiàn)程中去處理,說(shuō)白了就是分擔(dān)MainLooper的工作量,降低了主線(xiàn)程壓力,使主界面更流程。
- 2、開(kāi)啟一個(gè)線(xiàn)程起到多個(gè)線(xiàn)程的作用。處理任務(wù)是串行執(zhí)行,按消息發(fā)送順序進(jìn)行處理。HandlerThread本質(zhì)是一個(gè)線(xiàn)程,在線(xiàn)程內(nèi)部,代碼是串行處理的。但是由于每一個(gè)任務(wù)都將以隊(duì)列的方式逐個(gè)被執(zhí)行到,一旦隊(duì)列中某個(gè)任務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),那么就會(huì)導(dǎo)致后續(xù)的任務(wù)都會(huì)被延遲處理。HandlerThread擁有自己的消息隊(duì)列,它不會(huì)干擾或阻塞UI線(xiàn)程。
- 3、對(duì)于網(wǎng)絡(luò)I/O操作,HandlerThread并不合適,因?yàn)樗挥幸粋€(gè)線(xiàn)程,還得排隊(duì)一個(gè)一個(gè)等著。
五、Handler的內(nèi)存泄露
(一)、概述
android使用Java作為開(kāi)發(fā)環(huán)境,Java的跨平臺(tái)和垃圾回收機(jī)制已經(jīng)幫助我們解決了底層的一些問(wèn)題。但是盡管有了垃圾回收機(jī)制,在開(kāi)發(fā)android的時(shí)候仍然時(shí)不時(shí)遇到out of memory的問(wèn)題,這個(gè)時(shí)候我們不禁要問(wèn),垃圾回收器去哪里了?這里我們主要講解handler引起的泄露,并且給出了幾種解決方案,并且最后提供一個(gè)第三方庫(kù)WeakHandler庫(kù)。
可能導(dǎo)致泄漏問(wèn)題的handler一般會(huì)被提示 Lint警告如下:
This Handler class should be static or leaks might occur
意思是Handler class應(yīng)該使用靜態(tài)聲明,否則可能會(huì)出現(xiàn)內(nèi)存泄露
下面是更詳細(xì)的說(shuō)明(Android Studio,現(xiàn)在應(yīng)該沒(méi)人用Eclipse了吧)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
大概意思是:
一旦Handler 被聲明為內(nèi)部類(lèi),那么可能導(dǎo)致它的外部類(lèi)不能夠被垃圾回收,如果Handler在其他線(xiàn)程(我們通常稱(chēng)為工作線(xiàn)程(worker thread))使用Looper或MessageQueue(消息隊(duì)列),而不是main線(xiàn)程(UI線(xiàn)程),那么久沒(méi)有有這個(gè)問(wèn)題。如果Handler使用Looper或MessageQueue在主線(xiàn)程(main thread),你需要對(duì)Handler的聲明做如下修改:
聲明Handler為static類(lèi);在外部類(lèi)實(shí)例化一個(gè)外部類(lèi)的WeakReferernce(弱引用)并且在Handler初始化時(shí)傳入這個(gè)對(duì)象給你的Handler;將所有引用的外部類(lèi)成員使用WeakReference對(duì)象。
(二)、什么是內(nèi)存泄露
Java使用有向圖機(jī)制,通過(guò)GC自動(dòng)檢查內(nèi)存中的對(duì)象(什么時(shí)候檢查由虛擬機(jī)決定),如果GC發(fā)現(xiàn)一個(gè)或一組對(duì)象為不可到達(dá)狀態(tài),則將該對(duì)象從內(nèi)存中回收。也就是說(shuō),一個(gè)對(duì)象不被任何應(yīng)用所指向,則該對(duì)象會(huì)在被GC發(fā)現(xiàn)的時(shí)候被回收;另外,如果一組對(duì)象只包含相互的引用,沒(méi)沒(méi)有來(lái)自他們外部的引用(例如有兩個(gè)對(duì)象A和B相互持有引用,但沒(méi)有任何外部對(duì)象持有指向A或B的引用),這讓然屬于不可叨叨,同樣會(huì)被GC回收。
(三)、為什么會(huì)內(nèi)存泄露
原因:
- 當(dāng)Android應(yīng)用啟動(dòng)的時(shí)候,會(huì)先創(chuàng)建一個(gè)應(yīng)用主線(xiàn)程的Looper對(duì)象,Looper實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的消息隊(duì)列,一個(gè)一個(gè)的處理里面的Message對(duì)象。主線(xiàn)程Looper對(duì)象在整個(gè)應(yīng)用生命周期中存在。
- 當(dāng)在主線(xiàn)程中初始化Handler時(shí),該Handler和Looper的消息隊(duì)列關(guān)聯(lián)。發(fā)送到消息的隊(duì)列的Message會(huì)應(yīng)用發(fā)送該消息的Handler對(duì)象,這樣系統(tǒng)可以調(diào)用Handler.handleMessage(Message)來(lái)分發(fā)處理該消息。
- 在Java中,非靜態(tài)(匿名)內(nèi)部類(lèi)會(huì)引用外部類(lèi)對(duì)象。而靜態(tài)內(nèi)部類(lèi)不會(huì)引用外部類(lèi)對(duì)象。
-垃圾回收機(jī)制中約定,當(dāng)內(nèi)存中的一個(gè)對(duì)象的引用計(jì)數(shù)為0時(shí),將會(huì)被回收。- 如果外部類(lèi)是Activity,則會(huì)引起Activity泄露。當(dāng)Acitivity finish后,延時(shí)消息會(huì)繼續(xù)存在主線(xiàn)程消息隊(duì)列中1分鐘,然后處理消息。而該消息引用了Activity的Handler對(duì)象,然后這個(gè)Handler又引用了這個(gè)Activity。這些引用對(duì)象會(huì)保持到該消息被處理完,這樣就導(dǎo)致了該Activity對(duì)象無(wú)法被回收,從而導(dǎo)致了上面所說(shuō)的Activity泄露。
所以說(shuō)如果要修改該問(wèn)題,只需要按照Lint提示那樣,把Handler類(lèi)定義為靜態(tài)即可,然后通過(guò)WeakReference拉埃保持外部的Activity對(duì)象。
(四)、什么是WeakReference?
WeakReference弱引用,與強(qiáng)引用(即我們常說(shuō)的引用)相對(duì),它的特點(diǎn)是,GC在回收時(shí)會(huì)忽略掉弱引用,即就算有弱引用指向某對(duì)象,但只要該對(duì)象沒(méi)有被強(qiáng)引用所指向(實(shí)際上多數(shù)時(shí)候還要求沒(méi)有軟引用,但此處軟件用的概念可以忽略),該對(duì)象就會(huì)在被GC檢查到時(shí)回收掉。對(duì)于上面的代碼,用戶(hù)在關(guān)閉Activity之后,就算后臺(tái)線(xiàn)程還沒(méi)有結(jié)束,但由于僅有一條來(lái)自Handler的弱引用指向Activity,所以GC仍然會(huì)在檢查的時(shí)候把Activity回收掉。這樣內(nèi)存泄露的問(wèn)題就不會(huì)出現(xiàn)了。
(五)、Handler內(nèi)存泄露使用弱引用的補(bǔ)充
一般將Handler聲明為static就不必造成內(nèi)存泄露,聲明成弱引用Activity的話(huà),雖然也不會(huì)造成內(nèi)存泄露,但是需要等到handler中沒(méi)有執(zhí)行任務(wù)后才會(huì)回收,因此性能不高。
所以說(shuō)使用弱引用可以解決內(nèi)存泄露,但是需要等到Handler中任務(wù)都執(zhí)行完,才會(huì)釋放activity內(nèi)存,不如直接static釋放的快。所以說(shuō)單獨(dú)使用弱引用性能不是太高。
(六)、WeakHandler
WeakHandler使用起來(lái)和handler一樣,但它是線(xiàn)程安全的,WeakHandler使用如下:
1、WeakHandler的使用
public class ExampleActivity extends Activity {
private WeakHandler mHandler; // We still need at least one hard reference to WeakHandler
protected void onCreate(Bundle savedInstanceState) {
mHandler = new WeakHandler();
...
}
private void onClick(View view) {
mHandler.postDelayed(new Runnable() {
view.setVisibility(View.INVISIBLE);
}, 5000);
}
}
你只需要將在以前的Handler替換成WeakHandler就行了。
2、WeakHandler的原理
WeakHandler的思想是將Handler和Runnable做一次封裝,我們使用的是封裝后的WeakHandler,但其實(shí)真正起到Handler作用的是封裝的內(nèi)部,而封裝的內(nèi)部對(duì)handler和runnable都是用的弱引用。

- 第一幅圖是普通handler的引用關(guān)系圖
- 第二幅圖是使用WeakHandler的引用關(guān)系
其實(shí)原文有對(duì)WeakHandler跟多的解釋?zhuān)潜硎銎饋?lái)也是挺復(fù)雜的。
原文地址:https://techblog.badoo.com/blog/2014/10/09/calabash-android-query/
github項(xiàng)目地址:https://github.com/badoo/android-weak-handler
六、Handler的面試題
1、為什么安卓要使用Handler?
因?yàn)閍ndroid更新UI只能在UI線(xiàn)程。為什么只能在UI線(xiàn)程更新UI?因?yàn)锳ndroid是單線(xiàn)程模型。為什么Android是單線(xiàn)程模型?那是因?yàn)槿绻我痪€(xiàn)程都可以更新UI的話(huà),線(xiàn)程安全處理起來(lái)相當(dāng)麻煩,所以就規(guī)定了Android是單線(xiàn)程模型,只允許在UI線(xiàn)程更新UI
2、消息機(jī)制的原理:
這個(gè)請(qǐng)參考 本篇文章 二、Handler消息機(jī)制
3、MessageQueue是什么時(shí)候創(chuàng)建的?
MessageQueue是在Looper的構(gòu)造函數(shù)里面創(chuàng)建的,所以一個(gè)線(xiàn)程對(duì)應(yīng)一個(gè)Looper,一個(gè)Looper對(duì)應(yīng)一個(gè)MessageQueue。
4、ThreadLocal在Handler機(jī)制中的作用
ThreadLocal是一個(gè)線(xiàn)程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),通過(guò)它可以在制定的線(xiàn)程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線(xiàn)程中可以獲取的存儲(chǔ)的數(shù)據(jù),對(duì)于其他線(xiàn)程就獲取不到數(shù)據(jù)。一般來(lái)說(shuō),當(dāng)某些數(shù)據(jù)是以線(xiàn)程為作用域而且不同線(xiàn)程需要有不同的數(shù)據(jù)副本的時(shí)候,可以考慮用ThreadLocal。比如對(duì)于Handler,它要獲取當(dāng)前線(xiàn)程的Looper,很顯然Looper的作用域就是線(xiàn)程,所以不同線(xiàn)程有不同的Looper。
5、Looper,Handler,MessageQueue的引用關(guān)系?
一個(gè)Handler對(duì)象持有一個(gè)MessageQueue和它構(gòu)造時(shí)所屬的線(xiàn)程的Looper引用。也就是說(shuō)一個(gè)Handler必須頂有它對(duì)應(yīng)的消息隊(duì)列和Looper。一個(gè)線(xiàn)程可能有多個(gè)Handler,但是至多有只能有一個(gè)Looper和一個(gè)消息隊(duì)列。
在主線(xiàn)程中new了一個(gè)Handler對(duì)象后,這個(gè)Handler對(duì)象自動(dòng)和主線(xiàn)程生成的Looper以及消息隊(duì)列關(guān)聯(lián)上了。子線(xiàn)程中拿到主線(xiàn)程中Handler的引用,發(fā)送消息后,消息對(duì)象就會(huì)發(fā)送到target屬性對(duì)應(yīng)的的那個(gè)Handler對(duì)應(yīng)的消息隊(duì)列中去,由對(duì)應(yīng)Looper來(lái)處理(子線(xiàn)程msg->主線(xiàn)程handler->主線(xiàn)程messageQueue->主線(xiàn)程Looper->主線(xiàn)程Handler的handlerMessage)。而消息發(fā)送到主線(xiàn)程Handler,那么也就是發(fā)送到主線(xiàn)程的消息隊(duì)列,用主線(xiàn)程的Looper輪詢(xún)。
6、MessageQueue里面的數(shù)據(jù)結(jié)構(gòu)是什么類(lèi)型的,為什么不是Map或者其他什么類(lèi)型的?
這個(gè)請(qǐng)參考 本片文章 一、Handler機(jī)制的思考
7、Handler引起的內(nèi)存泄漏以及解決辦法
這個(gè)請(qǐng)參考 本片文章 五、Handler的內(nèi)存泄露