目錄
Handler的使用
Handler初始化
發送消息
處理消息
MessageQueue的阻塞和喚醒
阻塞
喚醒
Handler對我們開發者的啟發
亮點一
亮點二
Looper什麼時候推出
Handler常見面試題
前言
對于一名開發者來說,閱讀源碼是一項必修的課程。在學習源碼的過程中,我們可以了解到設計模式與源代碼開發者的開發習慣。而在閱讀源碼的過程中,我一直秉承着郭霖大神的那句話“抽絲剝繭、點到即止”,我們沒有必要完全深入每一行代碼,通常我們可能隻需要知道這一行代碼的作用就行了。
大家有沒有發現,我們在Android開發過程中,很少遇到過多線程并發的問題,這個就得益于Android為我們提供的線程間通信工具 Handler了,是以我們要了解它是怎麼實作跨線程通信的。
Handler的使用
我們首先知道Handler是怎麼用的,再去剖析其核心源碼。
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
@Override
protected void onStart() {
super.onStart();
new Thread(() -> {
try {
// 模拟耗時操作
Thread.sleep(5000);
// 擷取 Message 執行個體對象
Message msg = Message.obtain();
// 設定 Message 對象的識别内容
msg.what = Constants.MSG_UPDATE_TEXT;
// 通過 Handler 把消息發送出去
handler.sendMessage(msg);
} catch (InterruptedException e) {
Log.e(TAG, "onStart: InterruptedException");
}
}).start();
}
private void initView() {
mTvMain = findViewById(R.id.tv_main);
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case Constants.MSG_UPDATE_TEXT:
mTvMain.setText("已完成更新操作");
}
}
};
}
接下來我們就按照上面代碼案例去剖析Handler源碼,順序依次是Handler初始化、
Handler初始化

最終會調用如下方法:
/*
* 将此标志設定為 true 以檢測擴充此 Handler 類的匿名類、本地類或成員類。 這種類可能會造成洩漏。
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
// 如果我們沒有調用Looper.prepare(),sThreadLocal.get() 将傳回null
// @UnsupportedAppUsage 不讓我們開發者使用
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final Looper mLooper;
final MessageQueue mQueue;
// @UnsupportedAppUsage 不讓我們開發者使用
final Handler.Callback mCallback;
final boolean mAsynchronous;
public Handler(@Nullable Handler.Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 如果我們建立的Handler類對象是匿名類,或者是成員類(内部類)、或者局部類(方法中建立的類)并且該類不是靜态的
// 就有記憶體洩漏的風險。
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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
/**
* 傳回與目前線程關聯的 Looper 對象。 如果調用線程未與 Looper 關聯,則傳回 null。
*
* @return Looper對象
*/
public static @android.annotation.Nullable
Looper myLooper() {
return sThreadLocal.get();
}
這裡我們重點關注一下 mLooper = Looper.myLooper(); 這行代碼,myLooper的實作是:sThreadLocal.get();這裡我們就要說說 ThreadLocal了,可參考我的另一篇博文:并發程式設計基礎(二)—— ThreadLocal及CAS基本原理剖析
其實說白了,ThreadLocal 正如其名,它就是一個線程本地副本,我們的線程内部會有一個ThreadLocal.ThreadLocalMap的成員:
再來看看具體的get方法:
/**
* 傳回此線程中 ThreadLocalMap成員以ThreadLocal為key對應的值(Object類型),如果ThreadLocalMap成員為null,
* 則首先将其初始化,調用 {@link #initialValue} 。
*
* @return 傳回目前線程的中 ThreadLocal 對應的值
*/
public T get() {
// 擷取目前線程
Thread t = Thread.currentThread();
// 擷取目前線程的 ThreadLocal.ThreadLocalMap 成員
ThreadLocalMap map = getMap(t);
// 如果map不為空
if (map != null) {
// 通過 ThreadLocal 擷取Entry
ThreadLocalMap.Entry e = map.getEntry(this);// 這裡的this就是目前線程副本 ThreadLocal 的執行個體
// 如果 Entry 不為空,傳回它的 value 屬性的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
//否則設定初始值
return setInitialValue();
}
上面的代碼包含了 ThreadLocalMap.Entry:
我們發現 Entry 的構造函數包含 ThreadLocal 和 Object,而它又是 ThreadLocalMap 的内部類,那我們就可以了解為 ThreadLocalMap 是以 ThreadLocal 為 key,任意對象為 value 的鍵值對結構(Map結構),即是一一對應的。
既然有 get 方法,那就有 set 方法了,我們發現在 Looper.prepare() 調用了 set:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
// 如果已經給目前線程的 ThreadLocal 設定過 一個Looper,則抛出異常,這就保證了 一個 ThreadLocal 隻對應一個 Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 否則 new 一個 Looper對象,并設定給目前線程的 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 的構造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
如上代碼通過判斷 sThreadLocal.get() 是否為 null 的邏輯,保證了每一個線程的 sThreadLocal 隻會調用一次 set 方法把 sThreadLocal 和Looper 綁定起來,而 sThreadLocal 又是static final的,那也就是說一旦線程調用了 Looper 的方法, sThreadLocal 就會被指派,并且一旦指派就不可更改,說明 Looper 與 ThreadLocal 是一一對應的。而且我們在 Android 源碼中全局搜尋 MessageQueue 的構造方法,發現隻有在 Looper 的構造方法中調用了
new MessageQueue(quitAllowed),并且 MessageQueue 的構造方法是包管理權限,也就是說我們普通開發者是不能調用的,那也就說明了通過 Looper 構造方法唯一建立 MessageQueue 對象,實作了 Looper 和 MessageQueue 的一一對應。再結合上面我們對 ThreadLocal 的學習,我們知道了 一個線程對應唯一的 ThreadLocal , 那麼就說明 Android的 Handler 機制中,Thread、ThreadLocal、Looper、MessageQueue這四者是一一對應。
發送消息
Handler的主要方法
調用流程:
Handler.sendMessage() --> Handler.sendMessageDelayed()--> Handler.sendMessageAtTime()--> Handler.enqueueMessage()--> MessageQueue.enqueueMessage()
可以看到最終會調用 MessageQueue.enqueueMessage() 将 Message 插入隊列。
Handler工作流程圖
那麼接下來我們剖析enquque方法:
Message next; // Message的成員
Message mMessages;// MessageQueue 的成員,可以看作是隊列的隊頭
boolean enqueueMessage(Message msg, long when) {
// 省略不是很重要的代碼
// 加鎖保證線程安全,防止多個線程同時給MessageQueue中插入消息
synchronized (this) {
// 省略不是很重要的代碼
// 給要插入消息隊列(MessageQueue)中的消息指定延遲時間,也就是在多久之後處理此消息
msg.when = when;
// 把消息隊列的隊頭指派給 p
Message p = mMessages;
// 是否喚醒消息隊列
boolean needWake;
// 最開始,消息隊列肯定是空的。那如果目前消息隊列中沒有消息,或者我們插入的消息的等待時間是0,那我們就把消息插入,
// 并讓其指向null;
// 或者消息隊列中有一個即将處理的消息,而我們插入的消息等待時間小于即将要處理的消息,那就把我們插入的消息放在其前面。
if (p == null || when == 0 || when < p.when) {
// 插入的消息的下一個消息指向p,p可能為null
msg.next = p;
// 把我們要插入的消息指派給消息隊列的隊頭,實作插入隊列操作
mMessages = msg;
// 當消息隊列中沒有消息時,就會阻塞,此時 mBlocked為 true
needWake = mBlocked;
}
// 這種情況是消息隊列中有 N多個消息了
else {
// 插入隊列中間。 通常我們不必喚醒消息隊列,除非隊列頭部有屏障,并且消息是隊列中最早的異步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 通過死循環,不停地比較我們要插入的消息與隊列中已有的消息
for (;;) {
// 目前消息隊列中用于作比較的消息作為前一個消息
prev = p;
// 然後讓 prev指向 p(p指向p.next)
p = p.next;
// 從消息隊列中的第一個消息開始周遊,直到消息隊列的末尾即null值,方可跳出循環;
// 或者要插入的消息等待時間小于目前我們正在做比較的消息,此時也跳出循環。
if (p == null || when < p.when) {
break;
}
}
// 插入消息
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
Message入隊示意圖
處理消息
入隊我們講完了,下來該講出隊了。出隊我們是通過 Looper.loop()實作:
/**
* 在目前線程中運作消息隊列。 請務必調用 {@link #quit()} 來結束循環。以下代碼省略不必要(看不懂)的代碼進行解析
*/
public static void loop() {
// 擷取目前線程的 Looper 對象
final Looper me = myLooper();
if (me == null) {
// 這個異常在我們初學者會經常遇到,因為初學者總是忘記在子線程調用 Looper.prepare()
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 擷取目前線程的 MessageQueue 對象
final MessageQueue queue = me.mQueue;
for (; ; ) {
// 取出消息 --- 出隊
Message msg = queue.next(); // 可能會阻塞,當MessageQueue沒有消息是,會調用nativePollOnce(ptr, -1);一直挂起
if (msg == null) {
// 沒有消息表示消息隊列正在推出
return;
}
try {
// target是Message中的 Handler 成員
msg.target.dispatchMessage(msg);
} catch (Exception exception) { }
finally { }
// 回收可能正在使用的消息
msg.recycleUnchecked();
}
}
loop方法中包含一個死循環,通過 MessageQueue.next() 不停地取出消息:
/**
* 消息出隊,同樣省略 N多行不是很重要的(看不懂的)代碼
*
* 嘗試檢索下一條消息, 如果找到傳回。
*
* @return 傳回即将要被處理的消息
*/
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 調用 native 方法睡眠 nextPollTimeoutMillis 這麼多毫秒,如果該值為-1,則會無限等待
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 自啟動以來的非睡眠正常運作時間毫秒數。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;// mMessages可以看作是消息隊列的隊頭
if (msg != null && msg.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
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 傳回一個消息等待處理
return msg;
}
} else {
// 沒有消息的情況
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// mIdleHandlers是一個ArrayList,通過 addIdleHandler 添加,一般我們不會調用此方法,
// 是以大多數情況下 mIdleHandlers.size()是0
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 沒有要運作的空閑 Handler ,循環并等待更多時間 ,是以大多情況下是一直阻塞的,這也就解釋了為什麼我們的
// Activity 顯示出來之後,我們隻要一直亮屏,它就不會結束,因為ActivityThread的main方法調用了Looper.loop,
// 使得程式一直挂起。
mBlocked = true;
continue;
}
}
}
}
最終調用 Handler.dispatchMessage(),而 Handler.dispatchMessage() 就會回調 handleMessage()。
從對MessageQueue的源碼剖析過程中,我們可以得出它是一個由單連結清單構成的具有優先級的隊列。為什麼這麼說呢?
單連結清單:Message的next成員也是Message,形成一個單連結清單。
優先級:展現在每次消息入隊的時候,我們最終調用 enqueueMessage(Message msg, long when) 方法,這裡的 when 指的是消息等待處理的時間或者叫做延遲時間,而消息的處理同樣會根據這個等待時間的大小順序來。
隊列:滿足先進先出規則。
MessageQueue的阻塞和喚醒
阻塞
其實在上面的代碼示例中,我已經講過在調用 MessageQueue.next() 時,會調用 nativePollOnce(ptr, nextPollTimeoutMillis) 實作阻塞,當 nextPollTimeoutMillis 是-1時,會一直阻塞。沒明白的同學再好好看看上面的代碼(一定要看注釋)。
喚醒
在MessageQueue.enqueueMessage() 中有這樣一段邏輯:
// 是否需要喚醒
boolean needWake;
// 如果消息隊列沒有消息,那麼 mBlocked 就為 true,那我插入消息時就得喚醒消息隊列
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 如果 mBlocked 為true,即隊列阻塞,則需要喚醒
needWake = mBlocked;
}
if (needWake) {
// 調用 native 方法喚醒隊列
nativeWake(mPtr);
}
// 沒有 Handler 要處理時,或者可以了解為消息隊列中沒有消息時,消息隊列就會阻塞
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
上面的代碼意思是說,在 MessageQueue 中沒有 Message 時,它就會阻塞,此時我們插入 Message,就會喚醒 Message。
Handler對我們開發者的啟發
亮點一
/**
* 在此處處理系統消息。
*/
public void dispatchMessage(@NonNull Message msg) {
// 如果 Message 自己有callback,就調用 Message.callback.run()
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果我們建立 Handler 時給 Callback 指派了,就走這裡
if (mCallback != null) {
// 當 Callback 處理完消息之後,我們可以根據傳回值确定,要不要走最後面的 handleMessage(msg);
// 這個就與我們的View事件分發機制有些相似點了,根據傳回值決定事件是否繼續往下分發
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
/**
* 可以在執行個體化 Handler 時使用回調接口,以避免必須實作自己的 Handler 子類。
*/
public interface Callback {
/**
* @param msg 一個{@link android.os.Message Message} 對象執行個體
* @return 如果不需要進一步處理,則為 true
*/
boolean handleMessage(@NonNull Message msg);
}
上面代碼就展現面向對象的封裝思想,Message 封裝了自己的 Callback ,如果 Message 是設定了它自己的 Callback,就回調自己的 callback.run();同樣 Handler 封裝了自己的 Callback ,如果 Handler 是設定了它自己的 Callback, 就回調自己的 handleMessage(),并且我們可以根據傳回值決定要不要執行 Handler 子類重寫的 handleMessage()。這樣使得程式很靈活,有點像責任鍊模式。
亮點二
大家有沒有想過,Handler 調用了 dispatchMessage 之後,就把消息出來完了,那消息是怎麼回收的?
其實在 Looper.loop() 中,最終會調用 Message.recycleUnchecked() 進行所謂的消息回收(其實消息并未被回收),我們來看源碼:
public static final Object sPoolSync = new Object();
/**
* 回收可能正在使用的消息。 處理排隊的消息時,由 MessageQueue 和 Looper 在内部使用。
*/
@UnsupportedAppUsage
void recycleUnchecked() {
// 将消息标記為正在使用,同時保留在回收對象池中。清除所有其他詳細資訊。
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
// 加鎖,避免多個線程回收消息時,消息池的消息混亂
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
// 消息池的隊頭
next = sPool;
// 把目前處理完的消息指派給隊頭
sPool = this;
// 消息池的消息量加一
sPoolSize++;
}
}
}
然後再看看Message.obtain():
/**
* 從消息池中傳回一個 Message 執行個體。 避免我們在很多情況下建立新的消息對象。
*/
public static Message obtain() {
synchronized (sPoolSync) {
// 消息池隊頭不為空
if (sPool != null) {
// 隊頭指派給要傳回的消息對象
Message m = sPool;
// 隊頭指向 m 的下一個節點
sPool = m.next;
// m 的下一個節點置空
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
我們發現上述代碼跟我們登機之前做安檢的場景很像,Message 就好比是放行李的盒子,Message的成員what、arg1、arg2、objtct就相當于是行李,當我們過完安檢之後,盒子不會被丢棄,而是放在安檢門口以備下一個過安檢的人使用,這好比我們的消息池,其實這裡所用的就是享元模式。
那麼這樣做有什麼好處,從記憶體優化的角度思考,通過 Message.obtain() 擷取消息大大減小了 new Message() 的調用,也就減少了連續記憶體空間被過度破壞,即不至于過度碎片化,也就是記憶體中連續空間更多了,那OOM出現的機率就小了。可能有同學說GC是幹什麼吃的,那大家有沒有想過,既然 GC會幫我們回收垃圾,釋放記憶體,為什麼還會出現OOM。其實 GC即便用了标記整理算法,使得記憶體空間連續,但是GC線程工作的時候,會STW(stop the world),即其他所有線程都得挂起。而且我們不斷地new 對象,又不斷地觸發 GC,會産生記憶體抖動,進而導緻卡頓,是以從性能優化的角度來講,我們盡量避免不必要的記憶體開銷。
Looper什麼時候推出
我們如果在子線程中建立 Looper 經常會有記憶體洩漏的問題,因為大部分同學都沒有釋放 Looper。那怎麼辦釋放呢?通過調用 Looper.quitSafely() 或者 Looper.quit()
/**
* 安全退出Looper。
* 處理完消息隊列中所有剩餘的消息後,立即終止 {@link #loop} 方法。 但是,在 loop 終止之前,将不會傳遞未來到期的待處理的延
* 遲消息。在要求 Looper 退出後,任何向隊列釋出消息的嘗試都将失敗。 例如,{@link Handler#sendMessage(Message)} 方法
* 将傳回 false。
*/
public void quitSafely() {
mQueue.quit(true);// 安全退出傳的參數是 true,而Looper.quit()傳的參數是 false
}
最終會調用到MessageQueue.quit():
/**
* 退出Looper
*
* @param safe 是否需要安全退出
*/
void quit(boolean safe) {
// 如果不允許退出,會抛異常,ActivityThread中的Looper就不允許退出。
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
// 如果正在退出,則return
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
// 安全退出
removeAllFutureMessagesLocked();
} else {
// 不安全退出
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
安全退出的實作:
private void removeAllFutureMessagesLocked() {
// 擷取目前時間
final long now = SystemClock.uptimeMillis();
// 消息隊列的隊頭指派給 p
Message p = mMessages;
// 如果隊頭存在
if (p != null) {
// 如果隊頭延遲時間大于目前時間,移除所有消息
if (p.when > now) {
removeAllMessagesLocked();
} else {// 繼續判斷,取隊列中所有大于目前時間的消息
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
// 将所有所有大于目前時間的消息回收,延遲時間小于目前時間的消息即使消息隊列退出了,仍然會繼續被取出執行
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
不安全退出的實作:
/**
* 移除消息隊列所有消息,包括延遲時間小于目前時間的消息
*/
private void removeAllMessagesLocked() {
Message p = mMessages;
// 輪循隊列中所有 Message對象,一一緩存到消息池中
while (p != null) {
Message n = p.next;
// 緩存到消息池中
p.recycleUnchecked();
p = n;
}
// 隊頭置空
mMessages = null;
}
Handler常見面試題
1.一個線程有幾個 Handler?
在一個線程中我們可以建立多個 Handler。
2.一個線程中有幾個 Looper?是如何保證的?
一個線程中隻有一個 Looper;
我們線上程中使用 Looper之前,先要調用 Looper.preprare,而prepare 中用到了 ThreadLocal,ThreadLocal是線程本地副本,是每個線程獨有的成員,線程就是通過它實作線程資料隔離的,并且 ThreadLocal 存儲資料是通過其内部類 ThreadLocalMap中的内部類 Entry 存儲的,Entry 的構造方法中包含兩個成員,ThreadLocal 和 Object,是以在 Handler機制中,可以把 ThreadLocalMap 了解為以 ThreadLocal 為 key,Looper為 value的鍵值對。然後我們發現 Looper中的 ThreadLocal 成員是被 static final 修飾的,那也就是說 Looper 被加載時,它的成員 ThreadLocal 就被初始化了,且不可更改。那就保證了 ThreadLocal 的唯一性,那我隻要再保證,Looper 的唯一性,就可以說明在 Handler機制中,Thread、Looper、ThreadLocal三者是一一對應的 。我們發現 prepare 方法中會先判斷是否給 ThreadLocal 已經設定過值,如果沒有設定過,new Looper()設定給它,否則會抛出異常。這就說明了 ThreadLocal 和 Looper 也是一一對應的了。
3.Handler記憶體洩漏原因? 為什麼其他的内部類沒有說過有這個問題?
4.為何主線程可以new Handler?如果想要在子線程中new Handler 要做些什麼準備?
5.子線程中維護的Looper,消息隊列無消息的時候的處理方案是什麼?有什麼用?
6.既然可以存在多個 Handler 往 MessageQueue 中添加資料(發消息時各個 Handler 可能處于不同線程),那它内部是如何確定線程安全的?取消息呢?
很簡單,加 synchronized 鎖
7.我們使用 Message 時應該如何建立它?
Message.obtain();因為它采用享元模式,重複利用回收的消息,大大減少new Message() 的機率,從記憶體優化的角度看,減少不必要的記憶體開銷,可以有效避免記憶體過度碎片化,進而降低出現OOM的機率。