基礎(chǔ)知識(shí)
-
Handler 機(jī)制相關(guān)的概念包括 Handler、Message 、Message Queue、Looper具體如下圖
相關(guān)的概念
核心方法
接下來(lái)我們將四個(gè)相關(guān)的類(lèi)一個(gè)個(gè)按著源碼來(lái)看一遍
Message
- Message是信息傳遞的載體里面沒(méi)有太多邏輯就是包括一些信息傳遞機(jī)制里面必須要用到的屬性主要看其構(gòu)造函數(shù)的源碼
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
/*package*/ Handler target;
/*package*/ Runnable callback;
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
- 可以看出來(lái)是有一個(gè)Message池的,可以避免過(guò)多創(chuàng)建對(duì)象,造成系統(tǒng)浪費(fèi)
- 其中最重要的兩個(gè)屬性是target和callback分別是Handler和Runnable類(lèi)型的其作用我們后面會(huì)講到的
Handler
它在消息傳遞過(guò)程中扮演著重要的角色,是消息的主要處理者,說(shuō)白了,就是發(fā)消息,收消息,最終處理消息其主要屬性包括
當(dāng)前Handler實(shí)例所在線程的Looper對(duì)象:mLooper = Looper.myLooper()
Looper的消息隊(duì)列賦值給mQueue:mQueue = mLooper.mQueue
Callback 來(lái)處理消息回調(diào):mCallback = callback
注意點(diǎn)
- Handler有且只能綁定一個(gè)線程的Looper
- Handler的消息是發(fā)送給Looper的消息隊(duì)列MessageQueue,需要等待處理
- 調(diào)用Handler構(gòu)造函數(shù)前要先構(gòu)造Looperhe 和Message Queue,在主線程中可以直接調(diào)用是因?yàn)閯?chuàng)建主線程的時(shí)候會(huì)自動(dòng)調(diào)用Looper.prepareMainLooper和Looper.prepare,而其他線程是沒(méi)有這一步的需要前創(chuàng)建Looper和Message Queue
以上是handler創(chuàng)建部分
sendMessage()
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //獲得當(dāng)前的消息隊(duì)列
if (queue == null) { //若是在創(chuàng)建Handler時(shí)沒(méi)有指定Looper,就不會(huì)有對(duì)應(yīng)的消息隊(duì)列queue ,自然就會(huì)為null
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //這個(gè)target就是前面我們說(shuō)到過(guò)的
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最終調(diào)用sendMessageAtTime()這個(gè)方法,此方法注意點(diǎn)有兩個(gè)
- msg.target = this 將Message的目標(biāo)handler設(shè)置為本身
- queue.enqueueMessage(msg, uptimeMillis)將這個(gè)message加入到當(dāng)前的目標(biāo)消息隊(duì)列
以上是handler發(fā)送消息的部分
dispatchMessage()
前面說(shuō)了消息的發(fā)送,交給Looper等待處理,處理完后會(huì)重新通知Handler處理,那么,是怎樣通知Handler處理消息的呢?秘密就在dispatchMessage()這個(gè)方法中
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
/**
* 子類(lèi)必須實(shí)現(xiàn)這個(gè)來(lái)接收消息
*/
public void handleMessage(Message msg) {
}
當(dāng)Looper處理完Message后,會(huì)使用到Message的target,即上面說(shuō)到的target,即發(fā)送消息的那個(gè)Handler,Looper會(huì)調(diào)用Handler的dispatchMessage()方法分發(fā)消息,所以前面在enqueueMessage()發(fā)送消息的時(shí)候,為什么非得指明Message的target就是這個(gè)道理。
回到dispatchMessage()這個(gè)方法:
1、首先會(huì)判斷Message的callback是否為空,此處的callback就是前面我們?cè)贛essage中說(shuō)到的,在靜態(tài)方法創(chuàng)建Message時(shí),可以指定的callback,若不為空,則將結(jié)果回調(diào)到callback中;
2、若Handler的mCallback 不為空,也一樣的道理。
3、平時(shí)我們都沒(méi)有傳入這個(gè)callback,而是直接實(shí)現(xiàn)handleMessage()這個(gè)方法,在這個(gè)方法中處理更新UI任務(wù)。
以上就是Handler發(fā)送和接收消息的基本過(guò)程:把消息發(fā)送到隊(duì)列—>然后喝茶等待—>接收消息—>分發(fā)消息—>在回調(diào)中處理。
MessageQueue
前面我們知道,Handler發(fā)送消息會(huì)調(diào)用MessageQueue的enqueueMessage()方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
以上就是消息隊(duì)列插入消息的過(guò)程原理,通過(guò)單向鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息。
Looper
Looper在Handler機(jī)制中扮演著關(guān)鍵的一環(huán),他是循環(huán)處理消息的發(fā)動(dòng)機(jī)),它不斷的從消息隊(duì)列中取出的消息,處理,然后分發(fā)處理事件。每個(gè)線程都可以且只能綁定一個(gè)Looper。主線程之所以能處理消息,也是因?yàn)樵贏PP啟動(dòng)時(shí),在ActivityThread中的main()方法中就已經(jīng)啟動(dòng)了Looper循環(huán)。
loop( )的源碼
public static void loop() {
final Looper me = myLooper(); //獲得當(dāng)前的Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲取當(dāng)前Looper的消息隊(duì)列
//......
for (;;) {
Message msg = queue.next(); //取出隊(duì)頭的消息
if (msg == null) {
// 如果消息為空,則跳過(guò),繼續(xù)執(zhí)行下一個(gè)message
return;
}
//......
try {
msg.target.dispatchMessage(msg);
//......
} finally {
//......
}
//......
msg.recycleUnchecked(); //回收可能正在使用的消息
}
}
總結(jié)


面試問(wèn)題
Looper 死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)消耗大量資源嗎?
對(duì)于線程即是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對(duì)于主線程,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出,那么如何保證能一直存活呢?簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出,例如,binder線程也是采用死循環(huán)的方法,通過(guò)循環(huán)方式不同與Binder驅(qū)動(dòng)進(jìn)行讀寫(xiě)操作,當(dāng)然并非簡(jiǎn)單地死循環(huán),無(wú)消息時(shí)會(huì)休眠。但這里可能又引發(fā)了另一個(gè)問(wèn)題,既然是死循環(huán)又如何去處理其他事務(wù)呢?通過(guò)創(chuàng)建新線程的方式。真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR,looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死。
主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢? 其實(shí)不然,這里就涉及到Linux pipe/epoll機(jī)制,簡(jiǎn)單說(shuō)就是在主線程的MessageQueue沒(méi)有消息時(shí),便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)同步I/O,即讀寫(xiě)是阻塞的。 所以說(shuō),主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源主線程的消息循環(huán)機(jī)制是什么?
Activity的生命周期都是依靠主線程的Looper.loop,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施:一旦退出消息循環(huán),那么你的程序也就可以退出了。 從消息隊(duì)列中取消息可能會(huì)阻塞,取到消息會(huì)做出相應(yīng)的處理。如果某個(gè)消息處理時(shí)間過(guò)長(zhǎng),就可能會(huì)影響UI線程的刷新速率,造成卡頓的現(xiàn)象。
thread.attach(false)方法函數(shù)中便會(huì)創(chuàng)建一個(gè)Binder線程(具體是指ApplicationThread,Binder的服務(wù)端,用于接收系統(tǒng)服務(wù)AMS發(fā)送來(lái)的事件),該Binder線程通過(guò)Handler將Message發(fā)送給主線程。「Activity 啟動(dòng)過(guò)程」
比如收到msg=H.LAUNCH_ACTIVITY,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會(huì)通過(guò)反射機(jī)制,創(chuàng)建Activity實(shí)例,然后再執(zhí)行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,則調(diào)用ActivityThread.handlePauseActivity()方法,最終會(huì)執(zhí)行Activity.onPause()等方法。
主線程的消息又是哪來(lái)的呢?當(dāng)然是App進(jìn)程中的其他線程通過(guò)Handler發(fā)送給主線程
Binder用于不同進(jìn)程之間通信,由一個(gè)進(jìn)程的Binder客戶(hù)端向另一個(gè)進(jìn)程的服務(wù)端發(fā)送事務(wù),比如圖中線程2向線程4發(fā)送事務(wù);而handler用于同一個(gè)進(jìn)程中不同線程的通信,比如圖中線程4向主線程發(fā)送消息。
ActivityThread通過(guò)ApplicationThread和AMS進(jìn)行進(jìn)程間通訊,AMS以進(jìn)程間通信的方式完成ActivityThread的請(qǐng)求后會(huì)回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會(huì)向H發(fā)送消息,H收到消息后會(huì)將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程中去執(zhí)行,這個(gè)過(guò)程就是。主線程的消息循環(huán)模型Handler 是如何能夠線程切換
這里再引申出Handler的一些小知識(shí)點(diǎn)。 Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)造消息循環(huán)系統(tǒng),Looper在哪個(gè)線程創(chuàng)建,就跟哪個(gè)線程綁定,并且Handler是在他關(guān)聯(lián)的Looper對(duì)應(yīng)的線程中處理消息的。
那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的線程中互不干擾的存儲(chǔ)并提供數(shù)據(jù),通過(guò)ThreadLocal可以輕松獲取每個(gè)線程的Looper。當(dāng)然需要注意的是①線程是默認(rèn)沒(méi)有Looper的,如果需要使用Handler,就必須為線程創(chuàng)建Looper。我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread,②ActivityThread被創(chuàng)建時(shí)就會(huì)初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。子線程有哪些更新UI的方法
- 使用runOnUiThread()
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果是在主線程,直接運(yùn)行,不是進(jìn)行mhandler.post()
- post
new Thread() {
public void run() {
button.post(new Runnable(){
@Override
public void run() {
button.setText("sssssss");
}
});
}
}
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action); //一般情況走這里
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
實(shí)際還是調(diào)用handler
AsyncTask
通過(guò)HandlerHandler+handleMessage
- 系統(tǒng)為什么不允許在子線程中訪問(wèn)UI?(摘自《Android開(kāi)發(fā)藝術(shù)探索》) 這是因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè): ①首先加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜 ②鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。 所以最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。
6.Handler使用引起的內(nèi)存泄漏原因以及解決辦法
原因
- 當(dāng)一個(gè)android應(yīng)用程序啟動(dòng)的時(shí)候,frameworks會(huì)自動(dòng)為這個(gè)應(yīng)用程序在主線程創(chuàng)建一個(gè)Looper對(duì)象。這個(gè)被創(chuàng)建的Looper對(duì)象也有它主要的工作,它主要的工作就是不斷地處理消息隊(duì)列中的消息對(duì)象。在android應(yīng)用程序中,所有主要的框架事件(例如Activity的生命周期方法,按鈕的點(diǎn)擊事件等等)都包含在消息對(duì)象里面,然后被添加到Looper要處理的消息隊(duì)列中,主線程的Looper一直存在于整個(gè)應(yīng)用程序的生命周期中。
當(dāng)一個(gè)Handler在主線程中被初始化。那它就一直都和Looper的消息隊(duì)列相關(guān)聯(lián)著。當(dāng)消息被發(fā)送到Looper關(guān)聯(lián)的消息隊(duì)列的時(shí)候,會(huì)持有一個(gè)Handler的引用,以便于當(dāng)Looper處理消息的時(shí)候,框架可以調(diào)用Handler的handleMessage(Message msg)。
在java中,非靜態(tài)的內(nèi)部類(lèi)和匿名內(nèi)部類(lèi)都會(huì)隱式的持有一個(gè)外部類(lèi)的引用。靜態(tài)內(nèi)部類(lèi)則不會(huì)持有外部類(lèi)的引用。
當(dāng)Activity被finished掉的時(shí)候,被延時(shí)的消息會(huì)在被處理之前存在于主線程的消息隊(duì)列中十分鐘,而這個(gè)消息中又包含了Handler的引用,而Handler是一個(gè)匿名內(nèi)部類(lèi)的實(shí)例,其持有外面的MainActivity的引用。這些引用會(huì)一直保持到該消息被處理,從而阻止了MainActivity被垃圾回收器回收。因此這就導(dǎo)致了MainActivity無(wú)法被回收,進(jìn)而導(dǎo)致MainActivity持有的很多資源都無(wú)法回收,這就是我們常說(shuō)的內(nèi)存泄漏。
解決方法:
1 有延時(shí)消息,要在Activity銷(xiāo)毀的時(shí)候移除Messages
2 匿名內(nèi)部類(lèi)導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類(lèi),并且對(duì)上下文或者Activity使用弱引用。

