Android 為什么要用消息處理機制
如果有多個線程更新 UI,並且沒有加鎖處理,會導致界面更新的錯亂,而如果每個更新操作都進行加鎖處理,那么必然會造成性能的下降。所以在 Android 開發中,為了使 UI 操作是線程安全的,規定只許主線程即 UI 線程可以更新 UI 組件。但實際開發中,常常會遇到多個線程並發操作 UI 組件的需求,於是 Android 提供了一套消息傳遞與處理機制來解決這個問題。也就是在非主線程需要更新 UI 時,通過向主線程發送消息通知,讓主線程更新 UI。
當然消息處理機制不僅僅可以用來解決 UI 線程安全問題,它同時也是一種線程之間溝通的方式。
Looper
Looper 即消息循環器,是消息處理機制的核心,它可以將一個普通線程轉換為一個 Looper 線程。所謂的 Looper 線程就是一個不斷循環的線程,線程不斷循環的從 MessageQueue 中獲取 Message,交給相應的 Handler 處理任務。在 Looper 類的注釋介紹中,我們可以得知通過兩個靜態方法 Looper.prepare()
和 Looper.loop()
就可以將線程升級成 Looper 線程:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
// 將當前線程初始化為Looper線程
Looper.prepare();
// 注意:一定要在兩個方法之間創建綁定當前Looper的Handler對象,
// 否則一旦線程開始進入死循環就沒法再創建Handler處理Message了
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 處理收到的消息
}
};
// 開始循環處理消息隊列
Looper.loop();
}
}
Looper.prepare()
該方法在線程中將 Looper 對象定義為 ThreadLocal 對象,使得 Looper 對象成為該線程的私有對象,一個線程最多僅有一個 Looper。並在這個 Looper 對象中維護一個消息隊列 MessageQueue 和持有當前線程的引用,因此 MessageQueue 也是線程私有。
public final class Looper {
// 每個線程僅包含一個的Looper對象,定義為一個線程本地存儲(TLS)對象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// 每個Looper維護一個唯一的消息隊列
final MessageQueue mQueue;
// 持有當前線程引用
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static void prepare() {
prepare(true);
}
// 該方法會在調用線程的TLS中創建Looper對象,為線程私有
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
// 試圖在有Looper的線程中再次創建Looper對象將拋出異常
throw new RuntimeException(
"Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
}
Looper.loop()
該方法啟動線程的循環模式,從 Looper 的 MessageQueue 中不斷的提取 Message,再交由 Handler 處理任務,最后回收 Message 供以后復用。
public static void loop() {
// 得到當前線程的Looper對象
final Looper me = myLooper();
if (me == null) {
// 線程沒有調用Looper.prepare(),所以沒有Looper對象
throw new RuntimeException(
"No Looper; Looper.prepare() wasn't called on this thread.");
}
// 得到當前消息隊列
final MessageQueue queue = me.mQueue;
...
// 開始循環
for (;;) {
// 從消息隊列中獲取下一個Message,該方法可以被阻塞
Message msg = queue.next();
...
// 將Message推送到Message中的target處理
// 此處的target就是發送該Message的Handler對象
msg.target.dispatchMessage(msg);
...
// 回收Message,這樣就可以通過Message.obtain()復用
msg.recycleUnchecked();
}
}
Handler
Handler 可以稱之為消息處理者。Looper 線程不斷的從消息隊列中獲取消息,而向消息隊列中推送消息的正是 Handler 這個類。
Handler 在工作線程通過 sendMessage()
向 MessageQueue 中推送 Message,而主線程 Looper 循環得到 Message 后,即可得到發出該 Message 的 Handler 對象(Handler 發送消息時將自身引用賦值給 message.target
),再通過 Handler 對象的 dispatchMessage()
和 handleMessage()
方法處理相應的任務。這樣我們就可以通過 Handler 同時完成了異步線程的消息發送與消息處理兩個功能。
Handler 默認構造方法會關聯當前線程的 Looper 對象,一個線程只能有一個 Looper 對象,但可以有多個 Handler 關聯這個 Looper 對象。Handler 也提供一些構造方法關聯自定義的 Looper 對象。
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
public Handler() {
this(null, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
/**
* @hide
*/
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
// 如果打開了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());
}
}
// 關聯到當前線程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
// 當前線程未調用Looper.prepare(),不是Looper線程
throw new RuntimeException(
"Can't create handler inside thread "
+ Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 持有當前Looper中消息隊列的引用
mQueue = mLooper.mQueue;
mCallback = callback;
// 設置是否是異步消息
mAsynchronous = async;
}
/**
* @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {
// 指定了Looper對象,Handler處理消息就會在該Looper對應的線程中執行
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
}
sendMessage()
Handler 的功能之一就是向消息隊列發送消息,其有多個方法可以實現這個功能,如 post()
、postAtTime()
、postDelayed()
、sendMessage()
、sendMessageAtTime()
、sendMessageDelayed()
等等。post 一類的方法發送的是 Runnable 對象,send 一類的方法發送的是 Message 對象,但實際上 Runnable 對象最后都會封裝成 Message 對象。
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
// 將Runnable封裝成Message對象
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// 將Runnable設為Message的callback變量
m.callback = r;
return m;
}
再看 sendMessage 類方法的源碼:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
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) {
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) {
// 核心代碼,sendMessage一路調用到此,讓Message持有當前Handler的引用
// 當消息被Looper線程輪詢到時,可以通過target回調Handler的handleMessage方法
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 將Message加入MessageQueue中,並根據消息延遲排序
return queue.enqueueMessage(msg, uptimeMillis);
}
dispatchMessage()
、handleMessage()
Handler 的另外一個主要功能就是處理消息。處理消息的核心代碼即是 Looper.loop()
中的msg.target.dispatchMessage(msg)
以及 Handler 子類重寫實現的 handleMessage()
回調方法。源碼如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
// 處理消息,該方法由Looper的loop方法調用
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 處理Runnable類的消息
handleCallback(msg);
} else {
if (mCallback != null) {
// 這種方法允許讓Activity等來實現Handler.Callback接口
// 避免了自己定義Handler重寫handleMessage方法
if (mCallback.handleMessage(msg)) {
return;
}
}
// 根據Handler子類實現的回調方法處理消息
handleMessage(msg);
}
}
// 處理Runnbale類消息的方法
private static void handleCallback(Message message) {
// 直接調用Message中封裝的Runnable的run方法
message.callback.run();
}
// 由Handler子類實現的回調方法
public void handleMessage(Message msg) { }
通過上面對 Handler 核心源碼的分析,我們可以看到其在異步線程消息處理機制中的重要作用。任何持有 Handler 引用的其他線程都可以發送消息,這些消息都發送到 Handler 內部關聯的 MessageQueue 中,而 Handler 是在 Looper 線程中創建的(必須在 Looper.prepare()
調用之后,Looper.loop()
調用之前創建),所以 Handler 是在其關聯的 Looper 線程中處理取到的消息。
正是由於 Handler 這種機制解決了 Android 在非主線程(UI 線程)更新 UI 的問題。由於 Android 主線程也是一個 Looper 線程,在主線程創建的 Handler 默認將關聯到主線程的 MessageQueue。我們可以在 Activity 中創建 Handler,並將其引用傳遞給工作線程,在工作線程執行完任務后,通過 Handler 發送消息通知 Activity 更新 UI。
Message
在消息處理機制中,Message 扮演的角色就是消息本身,它封裝了任務攜帶的額外信息和處理該任務的 Handler 引用。
Message 雖然有 public 構造方法,但是還是建議使用 Message.obtain()
方法從一個全局的消息池中得到空的 Message 對象,這樣可以有效的節省系統資源。Handler 有一套 obtain 方法,其本質是其實是調用 Message 的一套 obtain 方法,最終都是調用 Message.obtain()
。
另外可以利用 message.what
來區分消息類型,以處理不同的任務。在需要攜帶簡單的 int 額外信息時,可以優先使用 message.arg1
和 message.arg2
,這比用 Bundle 封裝信息更省資源。
MessageQueue
MessageQueue.enqueueMessage()
這個方法是所有消息發送方法最終調用的終點,也就是說無論怎么發送消息,都會直接插入到對應的消息隊列中去。並且在插入后還會根據一些判斷,來決定是否喚醒阻塞的隊列。
boolean enqueueMessage(Message msg, long when) {
...
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) {
// 隊列里無消息,或插入消息的執行時間為0(強制插入隊頭),或插入消息的執行
// 時間先於隊頭消息,這三種情況下插入消息為新隊頭,如果隊列被阻塞則將其喚醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 根據執行時間將消息插入到隊列中間。通常我們不必喚醒事件隊列,除非
// 隊列頭部有消息屏障阻塞隊列,並且插入的消息是隊列中第一個異步消息
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;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// Native方法喚醒等待的線程
nativeWake(mPtr);
}
}
return true;
}
MessageQueue.next()
該方法可以從消息隊列中取出一個需處理的消息,在沒有消息或者消息還未到時時,該方法會阻塞線程,等待消息通過 MessageQueue.enqueueMessage()
方法入隊后喚醒線程。
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 通過native層的MessageQueue阻塞nextPollTimeoutMillis毫秒時間
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 嘗試檢索下一個消息,如果找到則返回該消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 被target為null的同步消息屏障阻塞,查找隊列中下一個異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一條消息尚未就緒。設置超時以在准備就緒時喚醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 從隊列中獲取一個要執行的消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 隊列中沒有消息,一直阻塞等待喚醒
nextPollTimeoutMillis = -1;
}
...
// 如果第一次遇到空閑狀態,則獲取要運行的IdleHandler數量
// 僅當隊列為空或者隊列中的第一條消息(可能是同步屏障)
// 還沒到執行時間時,才會執行IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 如果沒有IdleHandler需要執行,那么就阻塞等待下一個消息到來
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 運行IdleHandler
// 執行一次next方法只有第一次輪詢能執行這里的操作
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// 執行IdleHandler,返回是否保留IdleHandler
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
// 如果不需要保留,則移除這個IdleHandler
mIdleHandlers.remove(idler);
}
}
}
// 設置為0保證在再次執行next方法之前不會再執行IdleHandler
pendingIdleHandlerCount = 0;
// 當調用一個IdleHandler執行后,無需等待直接再次檢索一次消息隊列
nextPollTimeoutMillis = 0;
}
}
上面源碼里面的方法 nativePollOnce(ptr, nextPollTimeoutMillis)
是一個 Native 方法,實際作用就是通過 Native 層的 MessageQueue 阻塞 nextPollTimeoutMillis 毫秒的時間。
- 如果 nextPollTimeoutMillis = -1,一直阻塞不會超時。
- 如果 nextPollTimeoutMillis = 0,不會阻塞,立即返回。
- 如果 nextPollTimeoutMillis > 0,最長阻塞 nextPollTimeoutMillis 毫秒(超時),如果期間有程序喚醒會立即返回。
Q&A
1. ThreadLocal 是什么?
ThreadLocal 可以包裝一個對象,使其成為線程私有的局部變量,通過 ThreadLocal 的 get 和 set 方法來訪問這個線程局部變量,而不受到其他線程的影響。ThreadLocal 實現了線程之間的數據隔離,同時提升同一線程中該變量訪問的便利性。
2. post
和 sendMessage
兩類發送消息的方法有什么區別?
post
一類的方法發送的是 Runnable 對象,但是其最后還是會被封裝成 Message 對象,將 Runnable 對象賦值給 Message 對象中的 callback 變量,然后交由sendMessageAtTime()
方法發送出去。在處理消息時,會在dispatchMessage()
方法里首先被handleCallback(msg)
方法執行,實際上就是執行 Message 對象里面的 Runnable 對象的run
方法。而
sendMessage
一類的方法發送的直接是 Message 對象,處理消息時,在dispatchMessage
里優先級會低於handleCallback(msg)
方法,是通過自己重寫的handleMessage(msg)
方法執行。
3. 為什么要通過 Message.obtain()
方法獲取 Message 對象?
obtain
方法可以從全局消息池中得到一個空的 Message 對象,這樣可以有效節省系統資源。同時,通過各種 obtain 重載方法還可以得到一些 Message 的拷貝,或對 Message 對象進行一些初始化。
4. Handler 實現發送延遲消息的原理是什么?
我們常用
postDelayed()
與sendMessageDelayed()
來發送延遲消息,其實最終都是將延遲時間轉為確定時間,然后通過sendMessageAtTime()
->enqueueMessage
->queue.enqueueMessage
這一系列方法將消息插入到 MessageQueue 中。所以並不是先延遲再發送消息,而是直接發送消息,再借由 MessageQueue 的設計來實現消息的延遲處理。消息延遲處理的原理涉及 MessageQueue 的兩個靜態方法
MessageQueue.next()
和MessageQueue.enqueueMessage()
。通過 Native 方法阻塞線程一定時間,等到消息的執行時間到后再取出消息執行。
5. 為什么主線程不會因為 Looper.loop()
里的死循環卡死?
主線程確實是通過
Looper.loop()
進入了循環狀態,因為這樣主線程才不會像我們一般創建的線程一樣,當可執行代碼執行完后,線程生命周期就終止了。而對於 CPU 來說無論是進程還是線程都是一段可執行代碼,CPU 采用 CFS 調度算法,保證每個執行任務都盡可能公平的享有 CPU 時間片段。所以只要創建了其他的新線程處理事務,主線程的循環就不會導致系統卡死。這里有兩個問題:何時創建了其他的新線程運轉?主線程的循環會不會消耗大量的 CPU 資源?
何時創建了其他的新線程運轉?
實際上運行在主線程的 ActivityThread 的
main()
方法中就創建了新的 Binder 線程(即 ApplicationThread,用於接受系統服務 AMS 發來的事件):public static void main(String[] args) { ... Looper.prepareMainLooper(); ... ActivityThread thread = new ActivityThread(); // 這一句創建了一個Binder線程 // 這個線程可以通過 ActivityThread 里的 Handler 將消息發送給主線程 thread.attach(false, startSeq); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
一些 Activity 生命周期的消息就是通過這個機制在循環外執行起來的。比如一條暫停 Activity 的消息,首先是處於系統 system_server 進程的 ActivityManagerService(AMS)線程調用 ApplicationThreadProxy(ATP)線程,接着 ATP 線程通過 Binder 機制將消息傳輸到 APP 進程中的 ApplicationThread(AT)線程,最后 AT 線程通過 Handler 消息機制,將消息發送到了主線程的消息隊列中,主線程通過
Looper.loop()
遍歷得到該消息后,將消息分發給了 ActivityThread 對應的 Handler 中處理,最后調用到了 Activity 的onPause()
方法,方法處理完后,繼續主線程繼續循環下去。
主線程的循環會不會消耗大量的 CPU 資源?
這里就涉及到 Linux pipe/epoll 機制。在主線程的 MessageQueue 沒有消息時,便阻塞在
MessageQueue.next()
中的nativePollOnce()
方法里,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往 pipe 管道寫端寫入數據來喚醒主線程工作。這里采用的 epoll 機制,是一種 IO 多路復用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,即讀寫是阻塞的。所以主線程大多數時候都是處於休眠狀態,並不會消耗大量 CPU 資源。
6. 同步屏障 SyncBarrier 是什么?有什么作用?
Message 分為兩類:同步消息、異步消息。在一般情況下,兩類消息處理起來沒有什么不同。只有在設置了同步屏障后才會有差異。同步屏障從代碼層面上看是一個 Message 對象,但是其 target 屬性為 null,用以區分普通消息。在
MessageQueue.next()
中如果當前消息是一個同步屏障,則跳過后面所有的同步消息,找到第一個異步消息來處理。我們可以通過 MessageQueue 對象的
postSyncBarrier()
發送一個同步屏障,通過removeSyncBarrier(token)
移除同步屏障Android 應用框架中為了更快的響應 UI 刷新事件在
ViewRootImpl.scheduleTraversals()
中使用了同步屏障void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 設置同步障礙,確保mTraversalRunnable優先被執行 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 內部通過Handler發送了一個異步消息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
上面源碼中的任務 mTraversalRunnable 調用了
performTraversals()
執行measure()
、layout()
、draw()
等方法。為了讓 mTraversalRunnable 盡快被執行,在發消息之前調用 MessageQueue 對象的postSyncBarrier()
設置了一個同步屏障。
7. IdleHandler 是什么?有什么作用?
/** * 發現線程阻塞等待更多消息時,回調的接口 */ public static interface IdleHandler { /** * 當消息隊列沒有消息並等待更多消息時調用。 * 返回true以保持IdleHandler處於有效狀態,返回false則將其刪除。 * 如果隊列中仍有待處理的消息,但都未到執行時間時,也會調用此方法。 */ boolean queueIdle(); }
可以看出 IdleHandler 只有當消息隊列沒有消息時或者是隊列中的消息還未到執行時間時才會執行,IdleHandler 保存在 mPendingIdleHandler 隊列中。
queueIdle()
方法如果返回 false,那么這個 IdleHandler 只會執行一次,執行完后會從隊列中刪除,如果返回 true,那么執行完后不會被刪除,只要執行MessageQueue.next()
時消息隊列中沒有可執行的消息,即為空閑狀態,那么 IdleHandler 隊列中的 IdleHandler 還會繼續被執行。比如我們想實現一個 Android 繪制完成的回調方法,Android 本身提供的 Activity 框架和 Fragment 框架並沒有提供繪制完成的回調,如果我們自己實現一個框架,就可以使用 IdleHandler 來實現一個 onRenderFinished 這種回調了,可以有效的優化啟動時間等需求。
8. HandlerThread 是什么?
HandlerThread 繼承自 Thread ,所以本質上,它是一個 Thread。它與普通的 Thread 的差別在於其建立了一個線程的同時創建了一個含有消息隊列的 Looper,並對外提供這個 Looper 對象供 Handler 關聯,讓我們可以在該線程中分發和處理消息。當我們想讓一個線程和主線程一樣具備消息循環機制時,就可以使用這個類。
9. 能否在子線程更新 UI ?為什么 onCreate()
中的子線程更新 UI 有時可以成功?
因為 Android 的 UI 訪問是沒有加鎖的,這樣多線程操作 UI 是不安全的,會導致界面錯亂,所以 Android 系統限制了只能在主線程訪問 UI,其他線程直接操作 UI 會拋出異常。
如果在
onCreate()
中新建一個線程立刻執行操作 UI,結果卻可以正常運行,但如果延遲一段時間執行仍舊拋出異常。這是因為檢測子線程的方法checkThread()
是在 ViewRootImpl 中被調用,而 ViewRootImpl 在 ActivityThread 執行了handleResumeActivity()
時被添加,也就是對應的是onResume()
。所以在onCreate()
時根本不會執行checkThread()
方法做判斷。
10. 為什么非靜態類的 Handler 導致內存泄漏?如何解決?
首先,非靜態的內部類、匿名內部類、局部內部類都會隱式的持有其外部類的引用。也就是說在 Activity 中創建的 Handler 會因此持有 Activity 的引用。
當我們在主線程使用 Handler 的時候,Handler 會默認綁定這個線程的 Looper 對象,並關聯其 MessageQueue,Handler 發出的所有消息都會加入到這個 MessageQueue 中。Looper 對象的生命周期貫穿整個主線程的生命周期,所以當 Looper 對象中的 MessageQueue 里還有未處理完的 Message 時,因為每個 Message 都持有 Handler 的引用,所以 Handler 無法被回收,自然其持有引用的外部類 Activity 也無法回收,造成泄漏。
解決的辦法:
使用靜態內部類 + 弱引用的方式
靜態類不會持有外部類的的引用,當需要引用外部類相關操作時,可以通過弱引用來獲取到外部類。當一個對象只有弱引用時,是可以被垃圾回收掉的。
private Handler sHandler = new TestHandler(this); static class TestHandler extends Handler { private WeakReference<Activity> reference; TestHandler(Activity activity) { reference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = reference.get(); if (activity != null && !activity.isFinishing()) { switch (msg.what) { // 處理消息 } } } }
在外部類對象被銷毀時,將消息隊列清空
@Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }
參考資料
Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message 三者關系
Android 異步消息處理機制完全解析,帶你從源碼的角度徹底理解
Android 消息處理機制(Looper、Handler、MessageQueue、Message)