Handler的使用
我們知道,Android中,不允許應用程序在子線程中更新UI,UI的處理必須在UI線程中進行,這樣Android定製了一套完善的線程間通信機制——Handler通信機制。Handler作爲Android線程通信方式,高頻率的出現在我們的日常開發工作中,我們常用的場景包括:使用異步線程進行網絡通信、後臺任務處理等,Handler則負責異步線程與UI線程(主線程)之間的交互。
我們來看Handler的使用示例:
public static final int LOAD_COM = 1;//加載任務的id標誌
private Handler mHandler = new MyHandler(MainActivity.this);
private static class MyHandler extends Handler{
private final WeakReference<MainActivity> mActivity;
private MyHandler(MainActivity activity) {
this.mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {//ui線程中,負責消息返回的處理邏輯
super.handleMessage(msg);
switch (msg.what){
case LOAD_COM:
Log.d("TestHandler", msg.obj.toString());
MainActivity mainActivity = mActivity.get();
if (mainActivity != null){
mainActivity.mTextView.setText(msg.obj.toString());
}
break;
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_load:
new Thread(){
@Override
public void run() {//後臺線程中執行邏輯
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.what = LOAD_COM;
message.obj = "加載完成";
mHandler.sendMessage(message);//從後臺線程中,發送消息給UI線程
}
}.start();
break;
}
}
demo邏輯解析:
- 在Activity中,創建了一個Handler對象。
- 當按鈕start_load點擊時,啓動一個後臺線程,模擬一個後臺加載過程(線程休眠1秒)。
- 後臺任務完成後,使用Handler對象的sendMessage方法發送消息(一個Messaage對象)給UI線程。
- UI線程中,Handler對象的handleMessage方法負責處理消息的返回。
Demo中的例子是我們在Android開發中經常使用到的方式,Handler非常簡單的幫助我們實現了UI線程和後臺線程之間的通信。那麼Handler是如何做到線程間通信的呢?
接下來我們以源碼來分析Handler的實現機制。
Handler的實現機制&源碼分析
Handler機制介紹
Handler機制是Android通信機制的一個重要組成部分。在Android中,應用程序是消息驅動的,每個應用程序的UI線程(主線程)都會維護一個消息隊列,並且會不斷的從這個消息隊列中取出消息進行處理。Handler機制實現了消息在線程之間的通信。
Handler消息處理機制包括了3個組成部分:消息循環、消息發送、消息處理,這其中涉及到幾個重要類:Handler、Message、Looper和MessageQueue。
一個線程,想要處理Hander發送的消息,必須做好以下幾點準備:
- 首先要有一個消息循環,也就是要創建一個Looper對象。
- 將Looper對象綁定到當前線程上,也就是必須要調用Looper.prepare()方法。
- 開啓Looper的循環,也就是要調用Looper的loop方法。
我們接下來以Handler創建、消息的發送、消息循環、消息處理的順序來展開分析。
Handler對象的創建
要想使用Handler進行通信,首先要創建一個Handler對象。
Handler的構造函數:
public Handler() {//版本1
this(null, false);
}
public Handler(@Nullable Callback callback) {//版本2
this(callback, false);
}
public Handler(@NonNull Looper looper) {//版本3
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {//版本4
this(looper, callback, false);
}
@UnsupportedAppUsage
public Handler(boolean async) {//版本5。不支持App使用
this(null, async);
}
public Handler(@Nullable Callback callback, boolean async) {//版本6。主要實現邏輯在這裏
if (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());
}
}
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;
}
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {//版本7。不支持App直接使用,系統內部使用
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler的構造函數有多個重載版本,但邏輯實現都在版本6和版本7中。在版本7中,只是簡單的屬性賦值,這裏我們來分析版本6的實現。
邏輯解析:
- Handler有多個重載版本,但可供我們使用版本的async參數都是默認值false。
- 首先根據布爾值FIND_POTENTIAL_LEAKS判斷是否需要進行泄漏的檢測,默認是false。
- 將Looper.myLooper()賦值給mLooper對象。
- 將looper.mQueue賦值給mQueue對象。
- 將參數callback、async賦值給相應屬性。
Looper.myLooper()返回一個Looper對象,我們來看它的實現。
Looper.myLooper()方法
myLooper方法(位置:android.os.Looper.java):
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
這裏直接返回了當前線程的一個線程本地對象(線程本地對象的變量,在每個線程中都是獨立存儲的)。
我們來看sThreadLocal的初始化:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//線程的本地對象,存儲當前線程對應的Looper
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//prepare方法只能調用一次
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//創建Looper對象並綁定到當前線程。
}
邏輯解析:
- sThreadLocal是一個線程本地對象,存儲了當前線程對應的Looper對象。
- Looper類對外提供了prepare()靜態方法來初始化Looper,prepare無參方法調用了prepare的有參方法。
- prepare方法的參數quitAllowed:表示是否允許MessageQueue退出循環,默認參數是true,表示允許退出。
- 每個線程中只能調用一次prepare方法。
- prepare方法最終會創建一個Looper對象,並使用線程本地對象sThreadLocal,綁定到當前線程中。
prepare方法負責Looper對象的創建以及將Looper對象綁定到當前線程,所以要想使用Handler機制,Looper.prepare方法必須在使用前調用。
Looper的構造函數:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
構造函數做了2件事:
- 初始化MessageQueue對象,MessageQueue負責管理消息隊列。MessageQueue使用一個單鏈表的數據結構來管理消息的添加和刪除。
- 獲取當前線程的引用。
小結:
- Handler有多個重載版本,但可供我們使用版本的async參數都是默認值false。
- Handler機制要想正常使用,必須在初始化時,調用Looper.prepare()方法。
- Handler構造函數完成後,我們也就準備好了Looper對象,以及MessageQueue對象。
消息的發送
在Demo中,我們創建完成了Handler對象之後,實現了handleMessage方法來接收後臺線程發來的消息。消息的發送是由Handler對象的sendMessage方法完成的,我們先來分析它的實現。
sendMessage方法:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
這裏直接調用了sendMessageDelayed方法,第二個參數爲0,表示不作延遲。sendEmptyMessage等發送消息的方法,最終也會調用sendMessageDelayed方法。
sendMessageDelayed方法:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {//矯正延遲時間
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
這裏調用了sendMessageAtTime方法,參數1:msg;參數2:SystemClock.uptimeMillis() + delayMillis,其中SystemClock.uptimeMillis()表示從開機到現在的毫秒數。
這裏將延遲時間delayMillis,轉換爲了絕對時間,傳遞給了sendMessageAtTime方法。
sendMessageAtTime方法:
public boolean sendMessageAtTime(@NonNull 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);
}
邏輯解析:
- 這裏有2個參數,msg表示要發送的消息,uptimeMillis表示要處理消息的時間(這裏是絕對時間)。
- 獲取消息隊列,消息隊列不允許未初始化。
- 調用enqueueMessage方法,讓消息進入消息隊列中。
enqueueMessage方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
邏輯解析:
- 將當前對象賦給msg的target屬性。
- 將調用者的uid賦給msg的workSourceUid屬性。
- 判斷是否是異步消息,如果是異步,則調用setAsynchronous方法。我們在Handler初始化中得知,我們通常的應用使用的都是默認同步的,最後調用queue.enqueueMessage。
MessageQueue的enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//msg.target不能爲null。
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {//msg不能是正在使用中
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) {//如果隊列頭消息爲空,或者當前消息是插隊的消息(time值爲0時),或者當前消息的執行時間比隊列頭的要早。這裏會優先執行當前消息。
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;//表示是否需要阻止喚醒loop循環
} 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();//如果隊列頭的消息是異步的,並且target爲空,並且當前線程處理是阻塞的,則表示需要喚醒。
Message prev;
for (;;) {//這裏將當前消息插入到隊列中的合適位置
prev = p;
p = p.next;
if (p == null || when < p.when) {//如果消息爲空或者未到執行時間,則退出循環,找到了合適的位置了
break;
}
if (needWake && p.isAsynchronous()) {//如果需要喚醒屬性是true並且隊列頭消息是異步的,則不需要喚醒。
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;//插入當前消息
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {//如果需要喚醒,則調用nativeWake方法
nativeWake(mPtr);
}
}
return true;
}
邏輯解析:
- 消息的target爲空,或者msg正在處理,則拋出error錯誤。
- 接下來進行同步塊的處理。
- 當消息循環正在退出時,將當前消息回收,並退出。
- 對msg進行處理,設置msg的when,將msg狀態置位正在使用。
- 將隊列頭消息取出。
- 如果隊列頭消息爲空,或者當前消息是插隊的消息(time值爲0時),或者當前消息的執行時間比隊列頭的要早,將消息插入到隊列頭。
- 如果消息不需要立即執行,則需要插入到消息隊列中。
- for循環中,將當前消息插入到隊列中的合適位置。
- 如果需要喚醒線程,則調用nativeWake方法(nativeWake是一個native方法)。
小結:
- 我們使用Handler對象的sendMessage()等方法進行消息的發送。
- 最終消息會發送到MessageQueue的enqueueMessage方法中。
- 如果消息不需要立即執行,則把消息添加到MessageQueue的消息隊列中的合適位置。消息隊列的順序是按執行時間(絕對時間)順序排列的。
- 如果消息需要立即執行,則將消息添加到隊列頭,並賦值給mMessages(隊列頭指針)。
- 判斷線程是否需要喚醒,如果需要,則調用nativeWake(mPtr)執行喚醒。
消息循環
在消息的發送部分,消息發送後,會將消息添加到消息隊列中,如果滿足立即執行條件,則喚醒線程執行。
那麼到了這裏,你也許會問,消息的處理在哪進行的呢?將消息添加到消息隊列之後,接下來怎麼處理呢?
不要着急,消息的處理邏輯不在Handler裏面,而是在Looper的loop方法中進行處理的,Looper和MessageQueue負責我們的消息循環部分。
Looper的loop方法:
消息循環是在調用了Looper.loop方法之後開始的,我們來看:
public static void loop() {
final Looper me = myLooper();//獲取當前線程的Looper對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//獲取MessageQueue對象
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();//清空遠程調用端的uid和pid,用當前本地進程的uid和pid替代
……
for (;;) {
Message msg = queue.next(); // 從消息隊列中獲取消息
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);//執行消息處理,將消息發送給Handler對象進行處理。這裏的target,是當前消息的Handler對象
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
邏輯解析:
- 獲取當前線程的Looper對象。
- 清空遠程調用端的uid和pid。
- 消息處理是在一個無限循環的for循環中處理的。
- 在for循環中,首先從消息隊列中獲取消息。
- 若消息爲null,則表示消息循環隊列已退出,則結束循環。
- 否則,執行性能相關的日誌輸出,我們通過logging可以實現message處理的性能檢測,我們將在後續文章中分析它的實現。
- 調用msg.target.dispatchMessage(msg)執行消息處理,將消息發送給Handler對象進行處理。這裏的target,是當前消息的Handler對象。
- 最後將消息對象進行回收處理。
loop方法調用Handler對象的dispatchMessage進行消息的處理,消息處理部分我們稍後分析,我們先來看消息獲取的queue.next()方法。
MessageQueue的next方法
MessageQueue是一個消息隊列,內部是使用單向鏈表來實現的,Message對象的next屬性保存列表中的下一個,MessageQueue對象的mMessages屬性是鏈表頭,也就是當前要處理的消息。
MessageQueue的next方法:
@UnsupportedAppUsage
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 閒置任務的數量
int nextPollTimeoutMillis = 0;//消息循環,休眠時間
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);//設置休眠時間
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//表示添加了一個消息屏障。這個屏障之後的所有同步消息都不會被執行,即使時間已經到了也不會執行。
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//該循環會忽略同步消息,查找到異步第一個異步消息
}
if (msg != null) {
if (now < msg.when) {//沒到執行時間,則設置休眠時間
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {//有消息需要處理,則返回將要處理的消息
// Got a message.
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 {//表示隊列中已經沒有消息需要處理了,可以進行無限休眠
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {//如果消息循環已退出,則進行資源銷燬
dispose();
return null;
}
……
//進行閒置時間的任務處理。
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
邏輯解析:
MessageQueue的next方法負責了從消息隊列中取出消息,如果沒有要執行的消息,則進行休眠,直到有消息需要執行爲止。
-
for循環內首先調用nativePollOnce(ptr, nextPollTimeoutMillis),這是一個native方法,通過Native層的MessageQueue實現休眠。
nextPollTimeoutMillis表示休眠時間:
- nextPollTimeoutMillis == -1,一直休眠不會超時。
- nextPollTimeoutMillis == 0,不會休眠。
- nextPollTimeoutMillis > 0,最長休眠時間不超過nextPollTimeoutMillis毫秒,如果期間有程序喚醒會立即返回。
-
如果msg.target爲null,則表示添加了一個消息屏障。這個屏障之後的所有同步消息都不會被執行,即使時間已經到了也不會執行。
-
如果存在消息屏障,則該循環會忽略同步消息,查找到異步第一個異步消息,並進行處理。
-
如果消息存在,並且當前消息沒到執行時間,則設置休眠時間。否則消息需要處理,則返回將要處理的消息。
-
如果隊列中已經沒有消息需要處理了,可以進行無限休眠,等待喚醒,而喚醒操作是調用native方法nativeWake()來實現的。
-
如果當前隊列中沒有任務需要處理,則可以利用空閒資源處理一些非緊急任務(閒置任務),以執行一些非緊急的任務。
消息處理
我們來分析消息的處理,消息處理邏輯是在Looper中的loop方法調用Handler的dispatchMessage方法中進行的。
Handler的dispatchMessage方法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果Message對象的callback屬性存在,則執行callback的run方法,否則,調用handleMessage進行消息處理。
Handler的handleMessage方法
public void handleMessage(@NonNull Message msg) {
}
Handler的handleMessage方法是一個空方法,需要我們使用時,在子類中實現,就像demo中一樣。
UI線程Handler消息機制的初始化
經過Handler的原理分析,我們瞭解到Handler機制在初始化階段,需要調用Looper的prepare方法進行Looper初始化操作,並且需要調用Lopper.loop()開啓消息循環。
但是我們在UI線程中使用時,並沒有進行這些初始化操作,那麼UI線程中,Handler機制是如何進行初始化的呢?
在Looper的方法中,我們發現有一個叫prepareMainLooper()的方法,貌似是執行UI線程Handler初始化的地方?
prepareMainLooper()方法:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
這裏直接調用了prepare()方法,並且參數爲false,表示不允許消息隊列循環退出;然後進行了線程校驗,如果是非ui線程則會報錯;最後將Looper對象賦值給sMainLooper。
那麼該方法是在什麼地方?什麼時候調用的呢?
ActivityThread的main方法
我們來看源碼:
public static void main(String[] args) {
……
Looper.prepareMainLooper();
……
Looper.loop();
……
}
ActivityThread的main方法是在一個進程被創建的時候調用的,也即是說,進程創建過程中,會執行Handler的相關初始化工作。
這裏調用了Looper.prepareMainLooper()方法,初始化了Looper,並關聯了UI線程。
調用了Looper.loop()開啓了Looper的循環。
Handler的性能問題
在使用Handler的時候,我們通常會因爲使用時的不小心,造成一些內存泄漏等方面的性能問題。
下面來進行原因分析及如何來避免。
Handler的內存泄漏
原因
Handler的內存泄漏問題,通常是因爲對象存活的生命週期不一致問題導致的,具體原因如下:
- Handler持有了外部Activity的引用。
- 在Activity中啓動了一個後臺線程,執行耗時任務,後臺線程持有Handler的引用,當任務執行時間很長時。
- Activity在耗時任務執行期間結束執行了,這時,因爲後臺線程中持有了Handler對象,Handler對象持有Activity的引用,所以就會造成Activity在GC時,回收不掉的問題。
- 造成了內存泄漏。
思路
內存泄漏的原因是因爲我們在執行異步任務時,持有了Activity的引用,從而導致內存泄漏問題。
那麼如果我們的Handler不持有外部類的引用不就可以了嗎?那麼我們如何做到不持有外部Activity的引用呢?
解決方法一
解決方法參考本章開頭的Demo:
- Handler一定要實現一個自定義的靜態內部類,這裏爲什麼是靜態內部類呢?因爲非靜態內部類會持有外部類的引用,而靜態內部類是不持有外部類引用的。
- Handler的handleMessage方法中,如果需要用到Activity的屬性或對象,這時我們不得不持有Activity的引用,怎麼辦呢?那就是像上面我們的Demo一樣,使用一個弱引用來保存Activity,原理就是,弱引用在GC時,當內存緊張時會進行回收。
- Activity在Handler中使用時,一定要判斷是否爲null(是否已經回收)。
解決方法二
如果我們在Activity進行退出時,把相應的異步消息都進行註銷,這樣Handler都成爲可回收狀態後,我們的Activity自然也就是可回收的了,也就不會內存泄漏了。
- 我們可以在Activity的onDestory中調用Handler對象的removeCallbacksAndMessages()方法,參數一定要傳null。
- 注意,需要每一個Handler對象都要執行該方法。
Handler的removeCallbacksAndMessages()方法
public final void removeCallbacksAndMessages(@Nullable Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
這裏調用了MessageQueue的removeCallbacksAndMessages方法。
MessageQueue的removeCallbacksAndMessages方法
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
該方法會清除掉MessageQueue隊列中的所有消息,並且對Message進行回收。
Message的recycleUnchecked方法
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;//移除了handler的引用
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
該方法對Message對象進行資源回收。
總結
- Android中的線程通信機制使用了Handler來實現的。
- Android機制的初始化,需要調用Looper.prepare()方法進行Looper的創建及關聯到當前線程,然後調用Looper.loop()方法實現消息的循環。
- 消息的發送是通過Handler的sendMessage()或post()等方法執行的,然後調用了MessageQueue的enqueueMessage()方法來把消息添加到消息隊列中,如果需要喚醒操作,則調用native方法nativeWake進行喚醒。
- MessageQueue是消息隊列,內部採用單鏈表的數據結構來存儲消息列表,鏈表的頭是mMessages對象。
- MessageQueue實現了消息隊列的添加、消息的獲取、消息循環的休眠、消息循環的喚醒等。
- 消息循環的啓動是通過Looper.loop()來實現的,它啓動了一個無限循環來進行消息的處理。
- Looper.loop()每次循環都會調用MessageQueue的next()方法來實現消息的獲取,當然如果當前沒有消息需要執行,則會在next方法中調用native方法nativePollOnce()進行休眠。
- MessageQueue的next()方法中實現了消息屏障機制,可以通過添加消息屏障來阻塞消息隊列中同步消息的執行。
- MessageQueue的next()方法中也實現了空閒任務執行機制,當線程空閒時,我們可以執行一些非緊急任務。
- 我們可以調用Looper.quit()或Looper.quitSafely()方法來退出消息循環,但是UI線程不可退出消息循環。
- UI線程是在ActivityThread的main方法中執行的Handler機制的初始化工作,調用了Looper.prepareMainLooper()方法進行初始化和Looper.loop()方法開啓消息循環。
- Handler使用不當常常會導致內存泄漏等性能問題。Handler內存泄漏的原因通常是因爲引用的持有及生命週期不一致導致的。
- 我們可以通過弱引用+靜態內部類的方式來解決Handler內存泄漏的問題,或者在Activity結束時,調用Handler的removeCallbacksAndMessages(null)方法來回收Handler及消息。
- 另外,Message對象過多的創建也會導致內存過大,GC頻繁等問題,我們可以通過Message類提供的obtain()方法來獲取Message對象,obtain內部使用了一個對象池來減少新對象的創建。
通過上面的分析,我們已經對如何使用Handler消息機制和它的原理有了很清晰的瞭解。但是,在Looper循環的時候,loop()方法中的無限循環是如何做到不佔用系統資源的呢?它的休眠和喚醒又是什麼原理實現的呢?我們將在下一章進行分析~