理解 Android 消息機(jī)制

Android平臺(tái)上,主要用到的通信機(jī)制有兩種:Handler和Binder,前者用于進(jìn)程內(nèi)部的通信,后者主要用于跨進(jìn)程通信。

目錄

1. 概述

2. 初見 Android 消息機(jī)制

3. 理解 Android 消息機(jī)制

3.1 消息載體

3.2 創(chuàng)建消息隊(duì)列

3.3 開啟消息循環(huán)

3.4 發(fā)送和存儲(chǔ)消息

3.5 消息分發(fā)處理

4. 延伸知識(shí)點(diǎn)

4.1 主線程消息循環(huán)的創(chuàng)建

4.2 內(nèi)存泄露

5. 總結(jié)

本文基于原生 Android9.0源碼來解析 Android 消息機(jī)制:

frameworks/base/core/java/android/os/Handler.java

frameworks/base/core/java/android/os/Looper.java

frameworks/base/core/java/android/os/MessageQueue.java

frameworks/base/core/java/android/os/Message.java

frameworks/base/core/java/android/app/ActivityThread.java

1. 概述

我們知道在Android的主線程中不能進(jìn)行耗時(shí)操作,例如網(wǎng)絡(luò)訪問、數(shù)據(jù)處理等,因?yàn)橐坏┲骶€程的任務(wù)處理時(shí)間超過系統(tǒng)規(guī)定的限制就會(huì)出現(xiàn)應(yīng)用不響應(yīng)的情況。但在實(shí)際工作中,處理耗時(shí)任務(wù)是不可避免的,而且經(jīng)常需要在處理完耗時(shí)任務(wù)后更新某些UI控件,以顯示處理結(jié)果。在這種場(chǎng)景下,最常用方案就是在新線程中進(jìn)行耗時(shí)操作,處理完成后通知主線程進(jìn)行相關(guān)UI的更新,這時(shí)就需要使用到Android消息機(jī)制了。

到底什么是消息機(jī)制呢?簡(jiǎn)單來說,Android消息機(jī)制是一套以“消息”為中介來實(shí)現(xiàn)線程之間的任務(wù)切換或同一線程中任務(wù)的按需執(zhí)行的機(jī)制,其中涉及到消息的發(fā)送、存儲(chǔ)消息、消息循環(huán)以及消息的分發(fā)和處理。

本文將先通過一個(gè)簡(jiǎn)單的示例演示如何使用Android消息機(jī)制,再通過分析源碼來進(jìn)一步了解消息機(jī)制的內(nèi)部實(shí)現(xiàn)方式,最后會(huì)講解一些使用Android消息機(jī)制的注意點(diǎn)。

2. 初見 Android 消息機(jī)制

先用一個(gè)簡(jiǎn)單示例來展示下Android消息機(jī)制在實(shí)際工作中如何使用,就直接利用前面提到的場(chǎng)景,即子線程處理耗時(shí)任務(wù)并在任務(wù)處理完畢后通知主線程進(jìn)行UI的更新,示例代碼如下:

小菜在示例代碼里通過序號(hào)標(biāo)注了邏輯流程,即先開啟子線程并在線程內(nèi)部處理任務(wù),任務(wù)處理完成后通過Handler向主線程發(fā)送消息,最后在主線程中處理消息并更新UI。

看起來Android消息機(jī)制很簡(jiǎn)單嘛,只要利用Handler發(fā)送消息并處理其中的消息就可以了嘛。真的這么簡(jiǎn)單嗎?當(dāng)然不是!前面提到過在消息機(jī)制中涉及到幾個(gè)關(guān)鍵點(diǎn):發(fā)送消息、存儲(chǔ)消息、消息循環(huán)和分發(fā)處理消息,在這個(gè)示例中我們只看到了發(fā)送消息和處理消息,并沒有看到存儲(chǔ)消息和消息循環(huán)。

這是因?yàn)檫@個(gè)例子中的Handler使用的消息是發(fā)送和存儲(chǔ)在主線程中的消息隊(duì)列中,這個(gè)消息隊(duì)列的創(chuàng)建和循環(huán)都是在主線程創(chuàng)建的時(shí)候系統(tǒng)自動(dòng)進(jìn)行的,對(duì)我們是透明的,不利于理解消息機(jī)制的整體流程。

現(xiàn)在給出一個(gè)更為通用的示例,從這個(gè)例子中可以清楚地看到消息隊(duì)列的創(chuàng)建和消息循環(huán)的開啟:

class LooperThread extends Thread {

? ? public Handler mHandler;

? ? public void run() {

? ? ? ? Looper.prepare(); ?? // 初始化 Looper 對(duì)象,其內(nèi)部會(huì)創(chuàng)建消息隊(duì)列。

? ? ? ? mHandler = new Handler() {

? ? ? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? // 處理消息隊(duì)列中的消息。

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? Looper.loop(); ?? // 開啟消息循環(huán),會(huì)從消息隊(duì)列中取出消息,沒有消息時(shí)等待新消息的到來。

? ? }

}

綜合這兩個(gè)示例,我們了解了Android消息機(jī)制的使用方法,也看到了發(fā)送消息、創(chuàng)建消息隊(duì)列、開啟消息循環(huán)以及處理消息的過程,下面給出一個(gè)更直觀的“消息傳遞流程圖”:

通過流程圖可以看到整個(gè)消息傳遞過程,也可以看到在不同的階段涉及的類:

1. 消息發(fā)送:通過Handler向關(guān)聯(lián)的MessageQueue發(fā)送消息;

2. 消息存儲(chǔ): 把發(fā)送的消息以Message的形式存儲(chǔ)在MessageQueue中;

3. 消息循環(huán):通過Looper不停地從MessageQueue中獲取消息,隊(duì)列中沒有消息時(shí)就阻塞等待新消息;

4. 消息分發(fā)和處理:Looper獲取消息后分發(fā)給Handler進(jìn)行處理。

3. 理解 Android 消息機(jī)制

前面提到消息傳遞流程主要分為“發(fā)送消息”、“存儲(chǔ)消息”、“消息循環(huán)”和“消息分發(fā)和處理”幾個(gè)不同階段,小菜本打算按照這個(gè)流程來分別講解每個(gè)階段,但是在具體行文的時(shí)候發(fā)現(xiàn)每個(gè)階段并不是完全分割開來的,比如在講“發(fā)送消息”之前要先了解“消息的存儲(chǔ)結(jié)構(gòu)”和“消息循環(huán)的開啟”,而“消息的分發(fā)”又是屬于“消息循環(huán)”的功能。

正是由于這幾個(gè)階段之間的相互關(guān)系,導(dǎo)致沒有辦法嚴(yán)格按照消息傳遞的順序講解Android消息機(jī)制。思慮再三,小菜決定通過上面講解的Android消息機(jī)制通用示例來一步步解析其背后的邏輯流程。

再來看下通用示例:

class LooperThread extends Thread {

? ? public Handler mHandler;

? ? public void run() {

? ? ? ? // 1. 初始化 Looper 對(duì)象,其內(nèi)部會(huì)創(chuàng)建消息隊(duì)列。

? ? ? ? Looper.prepare();


? ? ? ? mHandler = new Handler() {

? ? ? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? // 4. 處理消息隊(duì)列中的消息。

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? // 2. 開啟消息循環(huán),會(huì)從消息隊(duì)列中取出消息,沒有消息時(shí)阻塞等待新消息的到來。

? ? ? ? Looper.loop();

? ? }

? ? // 3. 發(fā)送消息

? ? mHandler.sendEmptyMessage(0);

}

在示例代碼中用不同的序號(hào)標(biāo)注了“消息傳遞機(jī)制”的各個(gè)關(guān)鍵點(diǎn),以下的內(nèi)容也都是根據(jù)這些關(guān)鍵節(jié)點(diǎn)進(jìn)行講解的。

3.1 消息載體

“消息”是Android消息機(jī)制中信息的載體,它包含了在整個(gè)消息傳遞過程中想要傳送的數(shù)據(jù),要理解“消息機(jī)制”就要先了解這個(gè)消息載體類Message:

/**

* Defines a message containing a description and arbitrary data object that can be

* sent to a {@link Handler}.? This object contains two extra int fields and an

* extra object field that allow you to not do allocations in many cases.

*

* <p class="note">While the constructor of Message is public, the best way to get

* one of these is to call {@link #obtain Message.obtain()} or one of the

* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull

* them from a pool of recycled objects.</p>

*/

public final class Message implements Parcelable { ... }

Android框架中對(duì)消息載體Message的聲明雖然簡(jiǎn)短,卻傳達(dá)了最核心最重要的兩點(diǎn)信息:

Message的作用:Message是包含了描述信息和數(shù)據(jù)對(duì)象并且在消息機(jī)制中發(fā)送給Handler的對(duì)象,其中的數(shù)據(jù)對(duì)象主要包括兩個(gè)整型域和一個(gè)對(duì)象域,通過這些域可以傳遞信息。整型的代價(jià)是最小的,所以盡量使用整型域傳遞信息。

/**

* User-defined message code so that the recipient can identify

* what this message is about. Each {@link Handler} has its own name-space

* for message codes, so you do not need to worry about yours conflicting

* with other handlers.

*/

public int what;

/**

* arg1 and arg2 are lower-cost alternatives to using

* {@link #setData(Bundle) setData()} if you only need to store a

* few integer values.

*/

public int arg1;

public int arg2;

/**

* An arbitrary object to send to the recipient.? When using

* {@link Messenger} to send the message across processes this can only

* be non-null if it contains a Parcelable of a framework class (not one

* implemented by the application).? For other data transfer use

* {@link #setData}.

*

* <p>Note that Parcelable objects here are not supported prior to

* the {@link android.os.Build.VERSION_CODES#FROYO} release.

*/

public Object obj;

Message的創(chuàng)建方式:雖然Message有公有構(gòu)造函數(shù),但是建議使用其提供的obtain系列函數(shù)來獲取Message對(duì)象,這種創(chuàng)建方式會(huì)重復(fù)利用緩存池中的對(duì)象而不是直接創(chuàng)建新的對(duì)象,從而避免在內(nèi)存中創(chuàng)建太多對(duì)象,避免可能的性能問題。

/**

* Return a new Message instance from the global pool. Allows us to

* avoid allocating new objects in many cases.

*/

public static Message obtain() {

? ? synchronized (sPoolSync) {

? ? ? ? // 緩存池中存在可用對(duì)象時(shí)去緩存池獲取 Message 對(duì)象。

? ? ? ? if (sPool != null) {

? ? ? ? ? ? // 獲取緩存中的對(duì)象,并把緩存池指針后移。

? ? ? ? ? ? Message m = sPool;

? ? ? ? ? ? sPool = m.next;


? ? ? ? ? ? m.next = null;

? ? ? ? ? ? // 清除標(biāo)志位

? ? ? ? ? ? m.flags = 0; // clear in-use flag

? ? ? ? ? ? // 更新當(dāng)前緩存池大小

? ? ? ? ? ? sPoolSize--;

? ? ? ? ? ? return m;

? ? ? ? }

? ? }

? ? // 緩存池中沒有可用對(duì)象時(shí)直接創(chuàng)建一個(gè)新的 Message 對(duì)象。

? ? return new Message();

}

Message中有一系列obtain函數(shù)用以在不同場(chǎng)景中獲取對(duì)象,但這個(gè)是最核心的,其他函數(shù)都會(huì)在其內(nèi)部調(diào)用它,有興趣的同學(xué)可以自行查看源碼,考慮到篇幅問題,這里就不再一一列舉說明了。

看到這里,相信大家都會(huì)有一個(gè)疑問:obtain函數(shù)是從緩存池中獲取Message對(duì)象,那緩存池中的對(duì)象是什么時(shí)候被添加進(jìn)去的呢?既然緩存池中的對(duì)象都是一些可以被重復(fù)使用的對(duì)象,很明顯是在Message對(duì)象不再被需要的時(shí)候,即從MessageQueue中取出并分發(fā)給Handler的時(shí)候,被添加到緩存中的,使用的是recycleUnchecked函數(shù):

/**

* Recycles a Message that may be in-use.

* Used internally by the MessageQueue and Looper when disposing of queued Messages.

*/

void recycleUnchecked() {

? ? // 設(shè)置標(biāo)志位為“使用中”,在從緩存中取出時(shí)會(huì)清除這個(gè)標(biāo)志位。

? ? flags = FLAG_IN_USE;

? ? // Message 對(duì)象中的信息都不再有意義,在放入緩存池前直接清空。

? ? what = 0;

? ? arg1 = 0;

? ? arg2 = 0;

? ? obj = null;

? ? replyTo = null;

? ? sendingUid = -1;

? ? when = 0;

? ? target = null;

? ? callback = null;

? ? data = null;

? ? synchronized (sPoolSync) {

? ? ? ? // 緩存池中只緩存一定數(shù)量的 Message 對(duì)象,默認(rèn)是 50 個(gè)。

? ? ? ? if (sPoolSize < MAX_POOL_SIZE) {

? ? ? ? ? ? // 把對(duì)象放在緩存池的鏈表首部。

? ? ? ? ? ? next = sPool;

? ? ? ? ? ? sPool = this;

? ? ? ? ? ? // 及時(shí)更新緩存池大小。

? ? ? ? ? ? sPoolSize++;

? ? ? ? }

? ? }

}

3.2 創(chuàng)建消息隊(duì)列

消息隊(duì)列的創(chuàng)建對(duì)消息傳遞至關(guān)重要,它決定了消息在傳遞過程中的存取方式。但是線程在默認(rèn)情況下是沒有消息隊(duì)列的,也無法在其內(nèi)部進(jìn)行消息循環(huán)。如果想為線程開啟消息循環(huán)就需要使用到Looper類,它可以為關(guān)聯(lián)的線程創(chuàng)建消息隊(duì)列并開啟消息循環(huán),創(chuàng)建消息隊(duì)列的方式是調(diào)用prepare()接口:

/**

? * Class used to run a message loop for a thread.? Threads by default do

? * not have a message loop associated with them; to create one, call

? * {@link #prepare} in the thread that is to run the loop, and then

? * {@link #loop} to have it process messages until the loop is stopped.

? */

public final class Looper {

? ? // 省略無關(guān)代碼

? ? // sThreadLocal.get() will return null unless you've called prepare().

? ? static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

? ? // 內(nèi)部的消息隊(duì)列和關(guān)聯(lián)的線程

? ? final MessageQueue mQueue;

? ? final Thread mThread;

? ? // 省略無關(guān)代碼

? ? /** Initialize the current thread as a looper.

? ? ? * This gives you a chance to create handlers that then reference

? ? ? * this looper, before actually starting the loop. Be sure to call

? ? ? * {@link #loop()} after calling this method, and end it by calling

? ? ? * {@link #quit()}.

? ? ? */

? ? public static void prepare() {

? ? ? ? // 創(chuàng)建可退出的消息循環(huán),主線程的消息循環(huán)是不可退出的。

? ? ? ? prepare(true);

? ? }

? ? private static void prepare(boolean quitAllowed) {

? ? ? ? // 如果當(dāng)前線程已經(jīng)有了 Looper 對(duì)象就直接拋出異常,

? ? ? ? // 因?yàn)橐粋€(gè)線程只能有一個(gè)消息隊(duì)列。

? ? ? ? if (sThreadLocal.get() != null) {

? ? ? ? ? ? throw new RuntimeException("Only one Looper may be created per thread");

? ? ? ? }

? ? ? ? // 創(chuàng)建 Looper 對(duì)象并和線程關(guān)聯(lián)。

? ? ? ? sThreadLocal.set(new Looper(quitAllowed));

? ? }


? ? // 私有構(gòu)造函數(shù),創(chuàng)建消息隊(duì)列并獲取當(dāng)前線程對(duì)象。

? ? private Looper(boolean quitAllowed) {

? ? ? ? mQueue = new MessageQueue(quitAllowed);

? ? ? ? mThread = Thread.currentThread();

? ? }

可以看到Looper.prepare()只是在內(nèi)部創(chuàng)建了一個(gè)MessageQueue對(duì)象并和當(dāng)前線程關(guān)聯(lián)起來,同時(shí)還保證了每個(gè)線程只能有一個(gè)消息隊(duì)列。

很顯然MessageQueue就是用來存儲(chǔ)消息對(duì)象的結(jié)構(gòu)了,看下它的聲明:

/**

* Low-level class holding the list of messages to be dispatched by a

* {@link Looper}.? Messages are not added directly to a MessageQueue,

* but rather through {@link Handler} objects associated with the Looper.

*

* <p>You can retrieve the MessageQueue for the current thread with

* {@link Looper#myQueue() Looper.myQueue()}.

*/

public final class MessageQueue { ... }

MessageQueue是一個(gè)持有消息對(duì)象列表的類,而這些消息對(duì)象通過和Looper關(guān)聯(lián)的Handler添加并最終由Looper進(jìn)行分發(fā),其中有個(gè)關(guān)鍵信息需要引起我們的格外關(guān)注:list of messages,這是不是告訴我們雖然這個(gè)類的名字是queue但是其內(nèi)部并不是隊(duì)列而是列表呢?確實(shí)如此,MessageQueue的內(nèi)部是使用單向鏈表的方法進(jìn)行存取的,這點(diǎn)在后面解析Message的存取過程中會(huì)看到,在這里就不詳細(xì)講述了。

3.3 開啟消息循環(huán)

“消息隊(duì)列”創(chuàng)建完成了,是不是就可以直接向其中添加消息對(duì)象了呢?還不到時(shí)候,還需要先開啟消息循環(huán),來監(jiān)聽消息隊(duì)列的情況,這時(shí)需要使用Looper.loop()接口:

/**

* Run the message queue in this thread. Be sure to call

* {@link #quit()} to end the loop.

*/

public static void loop() {

? ? // 獲取當(dāng)前線程的 Looper 對(duì)象,獲取失敗時(shí)拋出異常。

? ? final Looper me = myLooper();

? ? if (me == null) {

? ? ? ? throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

? ? }

? ? // 獲取當(dāng)前線程的消息隊(duì)列。

? ? final MessageQueue queue = me.mQueue;

? ? // 省略無關(guān)代碼

? ? // 開啟一個(gè)無限循環(huán)來監(jiān)聽消息隊(duì)列的情況

? ? for (;;) {

? ? ? ? // 獲取消息隊(duì)列中的消息對(duì)象,如果沒有消息對(duì)象就阻塞等待。

? ? ? ? Message msg = queue.next(); // might block

? ? ? ? if (msg == null) {

? ? ? ? ? ? // 消息隊(duì)列正在退出時(shí)就終止監(jiān)聽并退出循環(huán)

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? // 省略無關(guān)代碼


? ? ? ? try {

? ? ? ? ? ? // 分發(fā)消息,把消息發(fā)送合適的處理對(duì)象。

? ? ? ? ? ? msg.target.dispatchMessage(msg);

? ? ? ? ? ? dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

? ? ? ? } finally {

? ? ? ? ? ? if (traceTag != 0) {

? ? ? ? ? ? ? ? Trace.traceEnd(traceTag);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // 省略無關(guān)代碼

? ? ? ? // 回收消息對(duì)象,放入消息緩存池中以待后續(xù)復(fù)用。

? ? ? ? msg.recycleUnchecked();

? ? }

}

這段代碼本身比較復(fù)雜,小菜省略了其中和核心邏輯無關(guān)的部分代碼,以方便大家閱讀和理解,其核心邏輯就是利用一個(gè)“無限循環(huán)”來監(jiān)聽消息隊(duì)列,當(dāng)發(fā)現(xiàn)有可用消息就取出并分發(fā)處理,如果沒有就一直等待。

3.4 發(fā)送和存儲(chǔ)消息

“消息隊(duì)列”已經(jīng)創(chuàng)建完成,“消息循環(huán)”也已經(jīng)開啟,終于可用發(fā)送消息了。

要發(fā)送消息,就要使用到Handler類了,其中的send和post系列方法都可以進(jìn)行“消息的發(fā)送”,核心方法都是一樣的,在這里就以post方法來講解下發(fā)送消息的過程:

/**

* Causes the Runnable r to be added to the message queue.

* The runnable will be run on the thread to which this handler is

* attached.

*?

* @param r The Runnable that will be executed.

*

* @return Returns true if the Runnable was successfully placed in to the

*? ? ? ? message queue.? Returns false on failure, usually because the

*? ? ? ? looper processing the message queue is exiting.

*/

public final boolean post(Runnable r) {

? ? return? sendMessageDelayed(getPostMessage(r), 0);

}


private static Message getPostMessage(Runnable r) {

? ? // 把 Runnable 對(duì)象封裝成 Message 并設(shè)置 callback,

? ? // 這個(gè) callback 會(huì)在后面消息的分發(fā)處理中起到作用。

? ? Message m = Message.obtain();

? ? m.callback = r;

? ? return m;

}


public final boolean sendMessageDelayed(Message msg, long delayMillis) {

? ? if (delayMillis < 0) {

? ? ? ? delayMillis = 0;

? ? }

? ? // 把延遲時(shí)間轉(zhuǎn)換為絕對(duì)時(shí)間,方便后續(xù)執(zhí)行。

? ? return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}


public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

? ? // 消息隊(duì)列,即通過 Looper.prepare() 創(chuàng)建的消息隊(duì)列。

? ? MessageQueue queue = mQueue;

? ? if (queue == null) {

? ? ? ? RuntimeException e = new RuntimeException(

? ? ? ? ? ? ? ? this + " sendMessageAtTime() called with no mQueue");

? ? ? ? Log.w("Looper", e.getMessage(), e);

? ? ? ? return false;

? ? }

? ? // 把消息添加到消息隊(duì)列

? ? return enqueueMessage(queue, msg, uptimeMillis);

}


private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

? ? // 設(shè)置消息隊(duì)列的目標(biāo),用于后續(xù)的消息分發(fā)過程。

? ? msg.target = this;

? ? if (mAsynchronous) {

? ? ? ? msg.setAsynchronous(true);

? ? }

? ? // 消息對(duì)象入隊(duì)

? ? return queue.enqueueMessage(msg, uptimeMillis);

}

通過一系列的調(diào)用過程,Handler最終會(huì)通過MessageQueue.enqueueMessage()把消息存儲(chǔ)到消息隊(duì)列中,MessageQueue內(nèi)部又是如何存儲(chǔ)這個(gè)發(fā)送過來的消息對(duì)象的呢?

boolean enqueueMessage(Message msg, long when) {

? ? // 消息對(duì)象的目標(biāo)是 null 時(shí)直接拋出異常,因?yàn)檫@意味這個(gè)消息無法進(jìn)行分發(fā)處理,

? ? // 是不合法的消息對(duì)象。

? ? if (msg.target == null) {

? ? ? ? throw new IllegalArgumentException("Message must have a target.");

? ? }

? ? // 消息正在使用時(shí)拋出異常,消息不能并發(fā)使用。

? ? if (msg.isInUse()) {

? ? ? ? throw new IllegalStateException(msg + " This message is already in use.");

? ? }

? ? synchronized (this) {

? ? ? ? // 正在退出消息循環(huán)時(shí),回收消息對(duì)象。

? ? ? ? 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;


? ? ? ? // 把消息對(duì)象添加到消息隊(duì)列的合適位置

? ? ? ? if (p == null || when == 0 || when < p.when) {

? ? ? ? ? ? // New head, wake up the event queue if blocked.

? ? ? ? ? ? // 消息隊(duì)列為空或者當(dāng)前消息對(duì)象的時(shí)間最近,直接放在鏈表首部。

? ? ? ? ? ? msg.next = p;

? ? ? ? ? ? // 更新鏈表指針

? ? ? ? ? ? mMessages = msg;

? ? ? ? ? ? // 如果原來消息循環(huán)處于阻塞狀態(tài)就重新喚醒

? ? ? ? ? ? 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;

? ? ? ? ? ? // 根據(jù)消息對(duì)象中的時(shí)間信息尋找合適的插入位置

? ? ? ? ? ? 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.

? ? ? ? // 喚醒等待,這時(shí)消息循環(huán)可以繼續(xù)獲取消息了,之前有可能處于阻塞等待狀態(tài)。

? ? ? ? if (needWake) {

? ? ? ? ? ? nativeWake(mPtr);

? ? ? ? }

? ? }

? ? return true;

}

3.5 消息分發(fā)處理

當(dāng)消息隊(duì)列中有新的消息并且消息循環(huán)被喚醒后,消息隊(duì)列中的消息就可以被取出并分發(fā)給合適的處理者了,這點(diǎn)可以在“開啟消息循環(huán)”一節(jié)中看到,利用的是msg.target.dispatchMessage(msg),而target就是Handler對(duì)象,直接看具體的分發(fā)過程:

public void dispatchMessage(Message msg) {

? ? // Message 對(duì)象是從 Runnable 封裝形成的時(shí)候,callback 不為空。

? ? if (msg.callback != null) {

? ? ? ? handleCallback(msg);

? ? } else {

? ? ? ? // mCallback 是在 Handler 的構(gòu)造函數(shù)中設(shè)置的,也可以不設(shè)置。

? ? ? ? if (mCallback != null) {

? ? ? ? ? ? // 調(diào)用 Handler 的 callback 處理消息

? ? ? ? ? ? if (mCallback.handleMessage(msg)) {

? ? ? ? ? ? ? ? // 可以攔截消息,之后 Handler.handleMessage 將無法繼續(xù)處理這個(gè)消息。

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // 調(diào)用 Handler 的 handleMessage 處理消息,子類會(huì)實(shí)現(xiàn)這個(gè)方法。

? ? ? ? handleMessage(msg);

? ? }

}

private static void handleCallback(Message message) {

? ? // Message 中的 callback 是 Runnable,直接執(zhí)行 Runnable.run()。

? ? message.callback.run();

}

/**

* Callback interface you can use when instantiating a Handler to avoid

* having to implement your own subclass of Handler.

*/

public interface Callback {

? ? /**

? ? * @param msg A {@link android.os.Message Message} object

? ? * @return True if no further handling is desired

? ? */

? ? // Handler 的回調(diào)方法,通過返回值可以進(jìn)行消息攔截。

? ? public boolean handleMessage(Message msg);

}


/**

* Subclasses must implement this to receive messages.

*/

// Handler 的處理消息回調(diào),子類需要實(shí)現(xiàn)。

public void handleMessage(Message msg) {

}

消息的分發(fā)是有一定優(yōu)先順序的:

首先會(huì)考慮交給Message.callback來處理,如果是通過post系列函數(shù)發(fā)送的消息會(huì)走到這里進(jìn)行處理,而通過send系列函數(shù)發(fā)送的消息默認(rèn)是沒有這個(gè)回調(diào)接口的;

如果Message.callback不存在就考慮交給Handler.callback來處理,在處理過程中可以通過返回值攔截消息;

如果Handler.callback不存在或者存在但是在處理消息過程中沒有進(jìn)行攔截,就會(huì)交給Handler.handleMessage來處理,這個(gè)接口需要子類實(shí)現(xiàn),也是在實(shí)際工作中最常用的處理消息的地方。

到這里,消息的傳遞過程就基本講完了,大家可以結(jié)合之前的流程圖仔細(xì)揣摩,相信可以對(duì)Android消息機(jī)制有更深刻的理解。

4. 延伸知識(shí)點(diǎn)

4.1 主線程消息循環(huán)的創(chuàng)建

前面講到一個(gè)線程默認(rèn)是沒有消息隊(duì)列的,也無法在其內(nèi)部開啟消息循環(huán),但是我們?cè)趯?shí)際工作中經(jīng)常會(huì)直接在主線程中使用Handler來進(jìn)行消息的發(fā)送和處理,并且運(yùn)行正常,這是因?yàn)橹骶€程在啟動(dòng)的時(shí)候就已經(jīng)創(chuàng)建了消息隊(duì)列并開啟了消息循環(huán),只是這個(gè)過程是透明的,我們沒有感知到。

了解Activity啟動(dòng)過程的同學(xué)應(yīng)該已經(jīng)想到了這個(gè)創(chuàng)建過程是在哪里了,沒錯(cuò),就是在ActivityThread,不了解啟動(dòng)過程的同學(xué)也不要擔(dān)心,后續(xù)我會(huì)講解具體的啟動(dòng)過程。在這里,大家只要簡(jiǎn)單地把ActivityThread當(dāng)做Activity的啟動(dòng)入口即可,直接來看入口函數(shù):

/**

* This manages the execution of the main thread in an

* application process, scheduling and executing activities,

* broadcasts, and other operations on it as the activity

* manager requests.

*

* {@hide}

*/

public final class ActivityThread extends ClientTransactionHandler {

? ? public static void main(String[] args) {

? ? ? ? // 記錄開始,用于后續(xù)通過 systrace 檢查和調(diào)試性能問題。

? ? ? ? Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

? ? ? ? // 省略無關(guān)代碼

? ? ? ? // 為主線程創(chuàng)建消息隊(duì)列

? ? ? ? Looper.prepareMainLooper();

? ? ? ? // 省略無關(guān)代碼

? ? ? ? ActivityThread thread = new ActivityThread();

? ? ? ? thread.attach(false, startSeq);

? ? ? ? if (sMainThreadHandler == null) {

? ? ? ? ? ? sMainThreadHandler = thread.getHandler();

? ? ? ? }

? ? ? ? if (false) {

? ? ? ? ? ? Looper.myLooper().setMessageLogging(new

? ? ? ? ? ? ? ? ? ? LogPrinter(Log.DEBUG, "ActivityThread"));

? ? ? ? }

? ? ? ? // 記錄結(jié)束,后續(xù)可以通過 systrace 觀察這段代碼的執(zhí)行情況。

? ? ? ? // End of event ActivityThreadMain.

? ? ? ? Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);


? ? ? ? // 開啟消息循環(huán)

? ? ? ? Looper.loop();

? ? ? ? // 主線程消息循環(huán)不會(huì)退出,如果走到這意味著發(fā)生意外,拋出異常。

? ? ? ? throw new RuntimeException("Main thread loop unexpectedly exited");

? ? }

}

代碼結(jié)構(gòu)和Android消息機(jī)制的通用示例很像,在里面看到了消息隊(duì)列的創(chuàng)建和消息循環(huán)的開啟,不同之處在于主線程中創(chuàng)建消息隊(duì)列使用的是Looper.prepareMainLooper:

/**

* Initialize the current thread as a looper, marking it as an

* application's main looper. The main looper for your application

* is created by the Android environment, so you should never need

* to call this function yourself.? See also: {@link #prepare()}

*/

public static void prepareMainLooper() {

? ? // 啟動(dòng)一個(gè)無法退出的消息循環(huán)

? ? prepare(false);

? ? synchronized (Looper.class) {

? ? ? ? if (sMainLooper != null) {

? ? ? ? ? ? throw new IllegalStateException("The main Looper has already been prepared.");

? ? ? ? }

? ? ? ? // 返回主線程 looper 對(duì)象

? ? ? ? sMainLooper = myLooper();

? ? }

}

為主線程創(chuàng)建的消息循環(huán)是無法退出的,因?yàn)檫@個(gè)消息循環(huán)要處理很多重要事務(wù),比如Activity生命周期的回調(diào)等,如果退出將導(dǎo)致異常,這點(diǎn)在后續(xù)講解Activity啟動(dòng)過程的時(shí)候再詳細(xì)解析。

4.2 內(nèi)存泄露

Java垃圾回收機(jī)制對(duì)于每個(gè)從事Java的開發(fā)者應(yīng)該都不陌生,我們也清楚并不是所有對(duì)象占用的內(nèi)存都可以被及時(shí)回收,如果垃圾回收器準(zhǔn)備回收某些對(duì)象,但是由于它們還被其他對(duì)象引用,那么這些對(duì)象就無法被回收,這也是內(nèi)存泄漏的主要原因。

使用Android消息機(jī)制時(shí)會(huì)不會(huì)導(dǎo)致內(nèi)存泄漏呢?首先來看一種常見的使用方法:

public class MainActivity extends Activity {

? ? private TextView mTextView = null;

? ? private Handler mMyHandler = null;

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);

? ? ? ? // 初始化控件

? ? ? ? mTextView = (TextView) findViewById(R.id.sample_text);

? ? ? ? // 初始化 Handler 對(duì)象

? ? ? ? mMyHandler = new MyHandler();

? ? ? ? // 啟動(dòng)一個(gè)延遲消息,在 3000ms 后有 mMyHandler 執(zhí)行。

? ? ? ? mMyHandler.sendEmptyMessageDelayed(0, 3000);

? ? }

? ? private class MyHandler extends Handler {

? ? ? ? @Override

? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? // 執(zhí)行消息,更新主線程中的控件。

? ? ? ? ? ? if (mTextView != null) {

? ? ? ? ? ? ? ? mTextView.setText("execute message");

? ? ? ? ? ? }

? ? ? ? }

? ? };

? ? @Override

? ? public void onDestroy() {

? ? ? ? super.onDestroy();

? ? }

}

在這個(gè)示例中,MyHandler是以Activity內(nèi)部類的形式存在的,所以mMyHandler是需要持有外部類對(duì)象引用的,而mMyHandler又被其發(fā)送的Message對(duì)象以target的方式引用,最終的結(jié)果就是Activity間接被Message引用。由于這個(gè)Message需要在一定的延遲后被執(zhí)行,如果在這之前Activity退出,但是由于其引用被Message持有,導(dǎo)致無法被系統(tǒng)回收,進(jìn)而導(dǎo)致內(nèi)存泄露。

既然Activity被Message引用導(dǎo)致內(nèi)存泄露,那有沒有辦法不讓其持有引用呢?當(dāng)然可以,使用“靜態(tài)內(nèi)部類”就可以避免這種情況,因?yàn)椤办o態(tài)內(nèi)部類”不需要持有外部類對(duì)象的引用,來看示例代碼:

public class MainActivity extends Activity {

? ? private TextView mTextView = null;

? ? private Handler mMyHandler = null;

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);

? ? ? ? mTextView = (TextView) findViewById(R.id.sample_text);

? ? ? ? // 初始化 Handler 對(duì)象,并把主線程控件作為參數(shù)傳入。

? ? ? ? mMyHandler = new MyHandler(mTextView);

? ? ? ? // 啟動(dòng)一個(gè)延遲消息,在 3000ms 后有 mMyHandler 執(zhí)行。

? ? ? ? mMyHandler.sendEmptyMessageDelayed(0, 3000);

? ? }

? ? private static class MyHandler extends Handler {

? ? ? ? // 通過弱引用的方式持有外部對(duì)象的變量。

? ? ? ? private WeakReference<TextView> mTextViewRef = null;

? ? ? ? // 初始化弱引用對(duì)象,此后就持有了正確的對(duì)象引用。

? ? ? ? public MyHandler(TextView textView) {

? ? ? ? ? ? mTextViewRef = new WeakReference<>(textView);

? ? ? ? }

? ? ? ? @Override

? ? ? ? public void handleMessage(Message msg) {

? ? ? ? ? ? // 執(zhí)行消息,更新主線程中的控件。

? ? ? ? ? ? if (mTextViewRef != null && mTextViewRef.get() != null) {

? ? ? ? ? ? ? ? mTextViewRef.get().setText("execute message");

? ? ? ? ? ? }

? ? ? ? }

? ? };

? ? @Override

? ? public void onDestroy() {

? ? ? ? super.onDestroy();

? ? ? ? // 退出時(shí)清空消息隊(duì)列中的消息

? ? ? ? mMyHandler.removeCallbacksAndMessages(null);

? ? }

}

通過“靜態(tài)內(nèi)部類”和“弱引用”的結(jié)合,既可以不持有外部類對(duì)象引用又可以訪問外部類對(duì)象的變量,并在Activity退出時(shí)又移除消息隊(duì)列中的消息,進(jìn)一步避免了內(nèi)存泄露的風(fēng)險(xiǎn)。

這只是其中一中避免內(nèi)存泄露的方法,肯定還有其他方法也可以達(dá)到目的,有興趣的同學(xué)可以自行研究。

5. 總結(jié)

本文講解了Android消息機(jī)制的使用方法、整體流程和每個(gè)階段的實(shí)現(xiàn)原理,在最后還提到主線程消息循環(huán)的創(chuàng)建以及錯(cuò)誤使用導(dǎo)致的內(nèi)存泄漏及避免方法,希望能對(duì)大家學(xué)習(xí)消息機(jī)制有所幫忙。

鏈接:https://juejin.im/post/5c91e3176fb9a070d4199f2b

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容