Handler原始碼詳解及導致記憶體洩漏的分析
[TOC]
簡介
android的訊息處理有三個核心類:Looper,Handler和Message,
主要接受子執行緒傳送的資料, 並用此資料配合主執行緒更新UI。
部分圖片來至CodingMyWorld部落格,3Q
使用方法
public class LooperThread extends Thread {
@Override
public void run() {
// 將當前執行緒初始化為Looper執行緒
Looper.prepare();
// ...其他處理,如例項化handler
// 開始迴圈處理訊息佇列
Looper.loop();
}
}
通過上面兩行核心程式碼,你的執行緒就升級為Looper執行緒了,就具備訊息處理的功能!
Looper.prepare()
通過上圖可以看到,現在你的執行緒中有一個Looper物件,它的內部維護了一個訊息佇列MQ。注意,一個Thread只能有一個Looper物件,以下原始碼使用到ThreadLoacal,可以想象成一個執行緒的屬性/變數,想了解更多請點選連結
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//獲取當前執行緒對應執行緒變數:Looper,重複執行此方法會有如下報錯提示
//
//"Only one Looper may be created per thread"
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//將當前初始化的Looper物件儲存到當前執行緒變數中
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.loop()
呼叫loop方法後,Looper執行緒就開始真正工作了,它不斷從自己的MQ中取出隊頭的訊息(也叫任務)執行。
/**
* 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;
// 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();
//迴圈訊息體
for (;;) {
//去出單個訊息體
Message msg = queue.next(); // might block
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//向目標物件中分發當前迴圈到的訊息體,下面將會具體講解
msg.target.dispatchMessage(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();
}
}
對msg.target.dispatchMessage(msg)
解釋
去Message
類中查詢可以發現
/*package*/ Handler target;
其實target就是handler物件,那handler是如何和一個Message發生聯絡的,稍等?下面移步Handler原始碼分析
Handler的建立就已經獲取了當前執行緒的Looper和訊息佇列
public Handler(Callback callback, boolean async) {
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());
}
}
//獲取同一執行緒的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取到同一執行緒的訊息佇列
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
真正Message和Handler關聯的地方來了
在我們sendMessage()
的時候都是先用obtainMessage
來獲取一個Message
public final Message obtainMessage()
{
return Message.obtain(this);
}
移步Message的方法看詳細
public static Message obtain(Handler h) {
Message m = obtain();
//是不是so easy,真正的聯絡在這裡
m.target = h;
return m;
}
如果你是使用post(Runnable r)
來發送訊息的,那應該構造一個Message來發出去,不信可以看原始碼
Handler類中:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
*構造一個Message
*/
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
//與PostMessage不同的是這個runable是給了callback屬性
m.callback = r;
return m;
}
具體訊息處理:Handler處理訊息
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//處理Runable訊息
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//處理具體Message,這個是你在訊息處理的時候重寫的方法實現
handleMessage(msg);
}
}
總結
- Lopper–訊息的集合和訊息的迴圈
- Handler–訊息的管理介面和訊息的處理
- 主執行緒已經持有Looper,所以不需要Looper.prepare()
Activity原始碼檢視:
/*package*/ ActivityThread mMainThread;
ActivityThread原始碼中檢視:
final Looper mLooper = Looper.myLooper();
導致記憶體洩漏的分析
記憶體洩漏場景
- 在一個activity中post已個message
- 關閉這個activity
- 由於某些原因這個message開始執行或者正在執行(如上一個message比較耗時/當前message比較耗時),
更嚴重的是你傳送一個延時訊息前把activity關閉
參考程式碼
private Handler mLeakHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Logger.d(msg.toString());
}
};
@Override
protected void onResume() {
super.onResume();
//延遲10s來模擬場景
mLeakHandler.sendEmptyMessageDelayed(0x1,10000);
finish();
}
//省略其他程式碼
分析及修改方法
由於這個Handler作為內部類宣告在Activity內部,普通的內部類物件隱式地儲存了一個指向外部類物件的引用,所以這個Handler物件儲存了一個指向Activity物件的引用。而這個Handler物件的生命週期可能比Activity生命週期長,比如當有一個後臺執行緒持有該Handler,且該執行緒在執行一個長時間任務。Handler通過傳送Message與主執行緒互動,Message發出之後是儲存在MessageQueue中的,有些Message也不是馬上就被處理的。在Message中存在一個 target,是Handler的一個引用,如果Message在Queue中存在的時間越長,就會導致Handler無法被回收。如果Handler是非靜態的,則會導致Activity不會被回收。但是注意這個洩漏時臨時的!當這個訊息處理完引用關係也就不存在了,下次GC的時候也就能回收啦
修改方法:
private static class MyHandler extends Handler {
private WeakReference<Activity> reference;
public MyHandler(Activity activity) {
reference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakActivity activity = (LeakActivity) reference.get();
if (activity != null) {
Logger.d("activity != null"+activity.toString());
} else {
Logger.d("activity = null");
}
}
}
private final Handler mHandler = new MyHandler(this);
同時你需要在呼叫一下方法,避免不必要的回撥(雖然不會報錯了)
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}