Android平臺上,主要用到的通信機制有兩種:Handler和Binder,前者用于進程內部的通信,后者主要用于跨進程通信。
目錄
本文基于原生 Android9.0源碼來解析 Android 消息機制:
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的主線程中不能進行耗時操作,例如網絡訪問、數據處理等,因為一旦主線程的任務處理時間超過系統規定的限制就會出現應用不響應的情況。但在實際工作中,處理耗時任務是不可避免的,而且經常需要在處理完耗時任務后更新某些UI控件,以顯示處理結果。在這種場景下,最常用方案就是在新線程中進行耗時操作,處理完成后通知主線程進行相關UI的更新,這時就需要使用到Android消息機制了。
到底什么是消息機制呢?簡單來說,Android消息機制是一套以“消息”為中介來實現線程之間的任務切換或同一線程中任務的按需執行的機制,其中涉及到消息的發送、存儲消息、消息循環以及消息的分發和處理。
本文將先通過一個簡單的示例演示如何使用Android消息機制,再通過分析源碼來進一步了解消息機制的內部實現方式,最后會講解一些使用Android消息機制的注意點。
2. 初見 Android 消息機制
先用一個簡單示例來展示下Android消息機制在實際工作中如何使用,就直接利用前面提到的場景,即子線程處理耗時任務并在任務處理完畢后通知主線程進行UI的更新,示例代碼如下:
小菜在示例代碼里通過序號標注了邏輯流程,即先開啟子線程并在線程內部處理任務,任務處理完成后通過Handler向主線程發送消息,最后在主線程中處理消息并更新UI。
看起來Android消息機制很簡單嘛,只要利用Handler發送消息并處理其中的消息就可以了嘛。真的這么簡單嗎?當然不是!前面提到過在消息機制中涉及到幾個關鍵點:發送消息、存儲消息、消息循環和分發處理消息,在這個示例中我們只看到了發送消息和處理消息,并沒有看到存儲消息和消息循環。
這是因為這個例子中的Handler使用的消息是發送和存儲在主線程中的消息隊列中,這個消息隊列的創建和循環都是在主線程創建的時候系統自動進行的,對我們是透明的,不利于理解消息機制的整體流程。
現在給出一個更為通用的示例,從這個例子中可以清楚地看到消息隊列的創建和消息循環的開啟:
class LooperThread extends Thread {
? ? public Handler mHandler;
? ? public void run() {
? ? ? ? Looper.prepare(); ?? // 初始化 Looper 對象,其內部會創建消息隊列。
? ? ? ? mHandler = new Handler() {
? ? ? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? // 處理消息隊列中的消息。
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Looper.loop(); ?? // 開啟消息循環,會從消息隊列中取出消息,沒有消息時等待新消息的到來。
? ? }
}
綜合這兩個示例,我們了解了Android消息機制的使用方法,也看到了發送消息、創建消息隊列、開啟消息循環以及處理消息的過程,下面給出一個更直觀的“消息傳遞流程圖”:
通過流程圖可以看到整個消息傳遞過程,也可以看到在不同的階段涉及的類:
1. 消息發送:通過Handler向關聯的MessageQueue發送消息;
2. 消息存儲: 把發送的消息以Message的形式存儲在MessageQueue中;
3. 消息循環:通過Looper不停地從MessageQueue中獲取消息,隊列中沒有消息時就阻塞等待新消息;
4. 消息分發和處理:Looper獲取消息后分發給Handler進行處理。
3. 理解 Android 消息機制
前面提到消息傳遞流程主要分為“發送消息”、“存儲消息”、“消息循環”和“消息分發和處理”幾個不同階段,小菜本打算按照這個流程來分別講解每個階段,但是在具體行文的時候發現每個階段并不是完全分割開來的,比如在講“發送消息”之前要先了解“消息的存儲結構”和“消息循環的開啟”,而“消息的分發”又是屬于“消息循環”的功能。
正是由于這幾個階段之間的相互關系,導致沒有辦法嚴格按照消息傳遞的順序講解Android消息機制。思慮再三,小菜決定通過上面講解的Android消息機制通用示例來一步步解析其背后的邏輯流程。
再來看下通用示例:
class LooperThread extends Thread {
? ? public Handler mHandler;
? ? public void run() {
? ? ? ? // 1. 初始化 Looper 對象,其內部會創建消息隊列。
? ? ? ? Looper.prepare();
? ? ? ? mHandler = new Handler() {
? ? ? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? // 4. 處理消息隊列中的消息。
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? // 2. 開啟消息循環,會從消息隊列中取出消息,沒有消息時阻塞等待新消息的到來。
? ? ? ? Looper.loop();
? ? }
? ? // 3. 發送消息
? ? mHandler.sendEmptyMessage(0);
}
在示例代碼中用不同的序號標注了“消息傳遞機制”的各個關鍵點,以下的內容也都是根據這些關鍵節點進行講解的。
3.1 消息載體
“消息”是Android消息機制中信息的載體,它包含了在整個消息傳遞過程中想要傳送的數據,要理解“消息機制”就要先了解這個消息載體類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框架中對消息載體Message的聲明雖然簡短,卻傳達了最核心最重要的兩點信息:
Message的作用:Message是包含了描述信息和數據對象并且在消息機制中發送給Handler的對象,其中的數據對象主要包括兩個整型域和一個對象域,通過這些域可以傳遞信息。整型的代價是最小的,所以盡量使用整型域傳遞信息。
/**
* 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的創建方式:雖然Message有公有構造函數,但是建議使用其提供的obtain系列函數來獲取Message對象,這種創建方式會重復利用緩存池中的對象而不是直接創建新的對象,從而避免在內存中創建太多對象,避免可能的性能問題。
/**
* 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) {
? ? ? ? // 緩存池中存在可用對象時去緩存池獲取 Message 對象。
? ? ? ? if (sPool != null) {
? ? ? ? ? ? // 獲取緩存中的對象,并把緩存池指針后移。
? ? ? ? ? ? Message m = sPool;
? ? ? ? ? ? sPool = m.next;
? ? ? ? ? ? m.next = null;
? ? ? ? ? ? // 清除標志位
? ? ? ? ? ? m.flags = 0; // clear in-use flag
? ? ? ? ? ? // 更新當前緩存池大小
? ? ? ? ? ? sPoolSize--;
? ? ? ? ? ? return m;
? ? ? ? }
? ? }
? ? // 緩存池中沒有可用對象時直接創建一個新的 Message 對象。
? ? return new Message();
}
Message中有一系列obtain函數用以在不同場景中獲取對象,但這個是最核心的,其他函數都會在其內部調用它,有興趣的同學可以自行查看源碼,考慮到篇幅問題,這里就不再一一列舉說明了。
看到這里,相信大家都會有一個疑問:obtain函數是從緩存池中獲取Message對象,那緩存池中的對象是什么時候被添加進去的呢?既然緩存池中的對象都是一些可以被重復使用的對象,很明顯是在Message對象不再被需要的時候,即從MessageQueue中取出并分發給Handler的時候,被添加到緩存中的,使用的是recycleUnchecked函數:
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
? ? // 設置標志位為“使用中”,在從緩存中取出時會清除這個標志位。
? ? flags = FLAG_IN_USE;
? ? // Message 對象中的信息都不再有意義,在放入緩存池前直接清空。
? ? what = 0;
? ? arg1 = 0;
? ? arg2 = 0;
? ? obj = null;
? ? replyTo = null;
? ? sendingUid = -1;
? ? when = 0;
? ? target = null;
? ? callback = null;
? ? data = null;
? ? synchronized (sPoolSync) {
? ? ? ? // 緩存池中只緩存一定數量的 Message 對象,默認是 50 個。
? ? ? ? if (sPoolSize < MAX_POOL_SIZE) {
? ? ? ? ? ? // 把對象放在緩存池的鏈表首部。
? ? ? ? ? ? next = sPool;
? ? ? ? ? ? sPool = this;
? ? ? ? ? ? // 及時更新緩存池大小。
? ? ? ? ? ? sPoolSize++;
? ? ? ? }
? ? }
}
3.2 創建消息隊列
消息隊列的創建對消息傳遞至關重要,它決定了消息在傳遞過程中的存取方式。但是線程在默認情況下是沒有消息隊列的,也無法在其內部進行消息循環。如果想為線程開啟消息循環就需要使用到Looper類,它可以為關聯的線程創建消息隊列并開啟消息循環,創建消息隊列的方式是調用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 {
? ? // 省略無關代碼
? ? // sThreadLocal.get() will return null unless you've called prepare().
? ? static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
? ? // 內部的消息隊列和關聯的線程
? ? final MessageQueue mQueue;
? ? final Thread mThread;
? ? // 省略無關代碼
? ? /** 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() {
? ? ? ? // 創建可退出的消息循環,主線程的消息循環是不可退出的。
? ? ? ? prepare(true);
? ? }
? ? private static void prepare(boolean quitAllowed) {
? ? ? ? // 如果當前線程已經有了 Looper 對象就直接拋出異常,
? ? ? ? // 因為一個線程只能有一個消息隊列。
? ? ? ? if (sThreadLocal.get() != null) {
? ? ? ? ? ? throw new RuntimeException("Only one Looper may be created per thread");
? ? ? ? }
? ? ? ? // 創建 Looper 對象并和線程關聯。
? ? ? ? sThreadLocal.set(new Looper(quitAllowed));
? ? }
? ? // 私有構造函數,創建消息隊列并獲取當前線程對象。
? ? private Looper(boolean quitAllowed) {
? ? ? ? mQueue = new MessageQueue(quitAllowed);
? ? ? ? mThread = Thread.currentThread();
? ? }
可以看到Looper.prepare()只是在內部創建了一個MessageQueue對象并和當前線程關聯起來,同時還保證了每個線程只能有一個消息隊列。
很顯然MessageQueue就是用來存儲消息對象的結構了,看下它的聲明:
/**
* 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是一個持有消息對象列表的類,而這些消息對象通過和Looper關聯的Handler添加并最終由Looper進行分發,其中有個關鍵信息需要引起我們的格外關注:list of messages,這是不是告訴我們雖然這個類的名字是queue但是其內部并不是隊列而是列表呢?確實如此,MessageQueue的內部是使用單向鏈表的方法進行存取的,這點在后面解析Message的存取過程中會看到,在這里就不詳細講述了。
3.3 開啟消息循環
“消息隊列”創建完成了,是不是就可以直接向其中添加消息對象了呢?還不到時候,還需要先開啟消息循環,來監聽消息隊列的情況,這時需要使用Looper.loop()接口:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
? ? // 獲取當前線程的 Looper 對象,獲取失敗時拋出異常。
? ? final Looper me = myLooper();
? ? 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) {
? ? ? ? ? ? // 消息隊列正在退出時就終止監聽并退出循環
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // 省略無關代碼
? ? ? ? try {
? ? ? ? ? ? // 分發消息,把消息發送合適的處理對象。
? ? ? ? ? ? msg.target.dispatchMessage(msg);
? ? ? ? ? ? dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
? ? ? ? } finally {
? ? ? ? ? ? if (traceTag != 0) {
? ? ? ? ? ? ? ? Trace.traceEnd(traceTag);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 省略無關代碼
? ? ? ? // 回收消息對象,放入消息緩存池中以待后續復用。
? ? ? ? msg.recycleUnchecked();
? ? }
}
這段代碼本身比較復雜,小菜省略了其中和核心邏輯無關的部分代碼,以方便大家閱讀和理解,其核心邏輯就是利用一個“無限循環”來監聽消息隊列,當發現有可用消息就取出并分發處理,如果沒有就一直等待。
3.4 發送和存儲消息
“消息隊列”已經創建完成,“消息循環”也已經開啟,終于可用發送消息了。
要發送消息,就要使用到Handler類了,其中的send和post系列方法都可以進行“消息的發送”,核心方法都是一樣的,在這里就以post方法來講解下發送消息的過程:
/**
* 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 對象封裝成 Message 并設置 callback,
? ? // 這個 callback 會在后面消息的分發處理中起到作用。
? ? Message m = Message.obtain();
? ? m.callback = r;
? ? return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
? ? if (delayMillis < 0) {
? ? ? ? delayMillis = 0;
? ? }
? ? // 把延遲時間轉換為絕對時間,方便后續執行。
? ? return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
? ? // 消息隊列,即通過 Looper.prepare() 創建的消息隊列。
? ? 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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
? ? // 設置消息隊列的目標,用于后續的消息分發過程。
? ? msg.target = this;
? ? if (mAsynchronous) {
? ? ? ? msg.setAsynchronous(true);
? ? }
? ? // 消息對象入隊
? ? return queue.enqueueMessage(msg, uptimeMillis);
}
通過一系列的調用過程,Handler最終會通過MessageQueue.enqueueMessage()把消息存儲到消息隊列中,MessageQueue內部又是如何存儲這個發送過來的消息對象的呢?
boolean enqueueMessage(Message msg, long when) {
? ? // 消息對象的目標是 null 時直接拋出異常,因為這意味這個消息無法進行分發處理,
? ? // 是不合法的消息對象。
? ? if (msg.target == null) {
? ? ? ? throw new IllegalArgumentException("Message must have a target.");
? ? }
? ? // 消息正在使用時拋出異常,消息不能并發使用。
? ? if (msg.isInUse()) {
? ? ? ? throw new IllegalStateException(msg + " This message is already in use.");
? ? }
? ? synchronized (this) {
? ? ? ? // 正在退出消息循環時,回收消息對象。
? ? ? ? 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;
}
3.5 消息分發處理
當消息隊列中有新的消息并且消息循環被喚醒后,消息隊列中的消息就可以被取出并分發給合適的處理者了,這點可以在“開啟消息循環”一節中看到,利用的是msg.target.dispatchMessage(msg),而target就是Handler對象,直接看具體的分發過程:
public void dispatchMessage(Message msg) {
? ? // Message 對象是從 Runnable 封裝形成的時候,callback 不為空。
? ? if (msg.callback != null) {
? ? ? ? handleCallback(msg);
? ? } else {
? ? ? ? // mCallback 是在 Handler 的構造函數中設置的,也可以不設置。
? ? ? ? if (mCallback != null) {
? ? ? ? ? ? // 調用 Handler 的 callback 處理消息
? ? ? ? ? ? if (mCallback.handleMessage(msg)) {
? ? ? ? ? ? ? ? // 可以攔截消息,之后 Handler.handleMessage 將無法繼續處理這個消息。
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 調用 Handler 的 handleMessage 處理消息,子類會實現這個方法。
? ? ? ? handleMessage(msg);
? ? }
}
private static void handleCallback(Message message) {
? ? // Message 中的 callback 是 Runnable,直接執行 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 的回調方法,通過返回值可以進行消息攔截。
? ? public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
// Handler 的處理消息回調,子類需要實現。
public void handleMessage(Message msg) {
}
消息的分發是有一定優先順序的:
首先會考慮交給Message.callback來處理,如果是通過post系列函數發送的消息會走到這里進行處理,而通過send系列函數發送的消息默認是沒有這個回調接口的;
如果Message.callback不存在就考慮交給Handler.callback來處理,在處理過程中可以通過返回值攔截消息;
如果Handler.callback不存在或者存在但是在處理消息過程中沒有進行攔截,就會交給Handler.handleMessage來處理,這個接口需要子類實現,也是在實際工作中最常用的處理消息的地方。
到這里,消息的傳遞過程就基本講完了,大家可以結合之前的流程圖仔細揣摩,相信可以對Android消息機制有更深刻的理解。
4. 延伸知識點
4.1 主線程消息循環的創建
前面講到一個線程默認是沒有消息隊列的,也無法在其內部開啟消息循環,但是我們在實際工作中經常會直接在主線程中使用Handler來進行消息的發送和處理,并且運行正常,這是因為主線程在啟動的時候就已經創建了消息隊列并開啟了消息循環,只是這個過程是透明的,我們沒有感知到。
了解Activity啟動過程的同學應該已經想到了這個創建過程是在哪里了,沒錯,就是在ActivityThread,不了解啟動過程的同學也不要擔心,后續我會講解具體的啟動過程。在這里,大家只要簡單地把ActivityThread當做Activity的啟動入口即可,直接來看入口函數:
/**
* 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) {
? ? ? ? // 記錄開始,用于后續通過 systrace 檢查和調試性能問題。
? ? ? ? Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
? ? ? ? // 省略無關代碼
? ? ? ? // 為主線程創建消息隊列
? ? ? ? Looper.prepareMainLooper();
? ? ? ? // 省略無關代碼
? ? ? ? ActivityThread thread = new ActivityThread();
? ? ? ? thread.attach(false, startSeq);
? ? ? ? if (sMainThreadHandler == null) {
? ? ? ? ? ? sMainThreadHandler = thread.getHandler();
? ? ? ? }
? ? ? ? if (false) {
? ? ? ? ? ? Looper.myLooper().setMessageLogging(new
? ? ? ? ? ? ? ? ? ? LogPrinter(Log.DEBUG, "ActivityThread"));
? ? ? ? }
? ? ? ? // 記錄結束,后續可以通過 systrace 觀察這段代碼的執行情況。
? ? ? ? // End of event ActivityThreadMain.
? ? ? ? Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
? ? ? ? // 開啟消息循環
? ? ? ? Looper.loop();
? ? ? ? // 主線程消息循環不會退出,如果走到這意味著發生意外,拋出異常。
? ? ? ? throw new RuntimeException("Main thread loop unexpectedly exited");
? ? }
}
代碼結構和Android消息機制的通用示例很像,在里面看到了消息隊列的創建和消息循環的開啟,不同之處在于主線程中創建消息隊列使用的是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() {
? ? // 啟動一個無法退出的消息循環
? ? prepare(false);
? ? synchronized (Looper.class) {
? ? ? ? if (sMainLooper != null) {
? ? ? ? ? ? throw new IllegalStateException("The main Looper has already been prepared.");
? ? ? ? }
? ? ? ? // 返回主線程 looper 對象
? ? ? ? sMainLooper = myLooper();
? ? }
}
為主線程創建的消息循環是無法退出的,因為這個消息循環要處理很多重要事務,比如Activity生命周期的回調等,如果退出將導致異常,這點在后續講解Activity啟動過程的時候再詳細解析。
4.2 內存泄露
Java垃圾回收機制對于每個從事Java的開發者應該都不陌生,我們也清楚并不是所有對象占用的內存都可以被及時回收,如果垃圾回收器準備回收某些對象,但是由于它們還被其他對象引用,那么這些對象就無法被回收,這也是內存泄漏的主要原因。
使用Android消息機制時會不會導致內存泄漏呢?首先來看一種常見的使用方法:
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 對象
? ? ? ? mMyHandler = new MyHandler();
? ? ? ? // 啟動一個延遲消息,在 3000ms 后有 mMyHandler 執行。
? ? ? ? mMyHandler.sendEmptyMessageDelayed(0, 3000);
? ? }
? ? private class MyHandler extends Handler {
? ? ? ? @Override
? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? // 執行消息,更新主線程中的控件。
? ? ? ? ? ? if (mTextView != null) {
? ? ? ? ? ? ? ? mTextView.setText("execute message");
? ? ? ? ? ? }
? ? ? ? }
? ? };
? ? @Override
? ? public void onDestroy() {
? ? ? ? super.onDestroy();
? ? }
}
在這個示例中,MyHandler是以Activity內部類的形式存在的,所以mMyHandler是需要持有外部類對象引用的,而mMyHandler又被其發送的Message對象以target的方式引用,最終的結果就是Activity間接被Message引用。由于這個Message需要在一定的延遲后被執行,如果在這之前Activity退出,但是由于其引用被Message持有,導致無法被系統回收,進而導致內存泄露。
既然Activity被Message引用導致內存泄露,那有沒有辦法不讓其持有引用呢?當然可以,使用“靜態內部類”就可以避免這種情況,因為“靜態內部類”不需要持有外部類對象的引用,來看示例代碼:
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 對象,并把主線程控件作為參數傳入。
? ? ? ? mMyHandler = new MyHandler(mTextView);
? ? ? ? // 啟動一個延遲消息,在 3000ms 后有 mMyHandler 執行。
? ? ? ? mMyHandler.sendEmptyMessageDelayed(0, 3000);
? ? }
? ? private static class MyHandler extends Handler {
? ? ? ? // 通過弱引用的方式持有外部對象的變量。
? ? ? ? private WeakReference<TextView> mTextViewRef = null;
? ? ? ? // 初始化弱引用對象,此后就持有了正確的對象引用。
? ? ? ? public MyHandler(TextView textView) {
? ? ? ? ? ? mTextViewRef = new WeakReference<>(textView);
? ? ? ? }
? ? ? ? @Override
? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? // 執行消息,更新主線程中的控件。
? ? ? ? ? ? if (mTextViewRef != null && mTextViewRef.get() != null) {
? ? ? ? ? ? ? ? mTextViewRef.get().setText("execute message");
? ? ? ? ? ? }
? ? ? ? }
? ? };
? ? @Override
? ? public void onDestroy() {
? ? ? ? super.onDestroy();
? ? ? ? // 退出時清空消息隊列中的消息
? ? ? ? mMyHandler.removeCallbacksAndMessages(null);
? ? }
}
通過“靜態內部類”和“弱引用”的結合,既可以不持有外部類對象引用又可以訪問外部類對象的變量,并在Activity退出時又移除消息隊列中的消息,進一步避免了內存泄露的風險。
這只是其中一中避免內存泄露的方法,肯定還有其他方法也可以達到目的,有興趣的同學可以自行研究。
5. 總結
本文講解了Android消息機制的使用方法、整體流程和每個階段的實現原理,在最后還提到主線程消息循環的創建以及錯誤使用導致的內存泄漏及避免方法,希望能對大家學習消息機制有所幫忙。
鏈接:https://juejin.im/post/5c91e3176fb9a070d4199f2b