Handler 訊息機制以及記憶體洩漏

劉二瓜發表於2020-04-05

1. 訊息機制

1.1 post系列

通過檢視原始碼可知,post(Runnable r)postDelayed(Runnable r, long delayMillis)最終呼叫的都是sendMessageDelayed方法:

// post
 public final boolean post(Runnable r){
    return sendMessageDelayed(getPostMessage(r), 0);
}
// postDelayed
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
複製程式碼

1.2 postAtTime

postAtTime(Runnable r, long uptimeMillis)最終呼叫的是sendMessageAtTime方法:

// postAtTime
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
複製程式碼

這裡面都有一個共同的方法getPostMessage

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製程式碼

m.callback = r這句可以看出:getPostMessage就是把傳入的 Runnable 賦值給 Message 物件的 callback 屬性

1.3 sendEmptyMessage

sendEmptyMessage最終指向sendEmptyMessageDelayed函式:

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
複製程式碼

msg.what = what這句可以看出:sendEmptyMessageDelayed就是把 what 賦值給 Message 的 what 屬性。

1.4 sendMessage(msg : Message)

至於常用的sendMessage(msg : Message)就不用細說了,這是直接傳入 Message 型別的引數。

綜合以上這幾點來說,各種傳送訊息的方法最終都是把訊息賦值給 Message 物件(或者 Message 的屬性),而且這些方法最終呼叫的都是 MessageQueue 中的enqueueMessage方法,就是把訊息加入訊息佇列

1.5 enqueueMessage方法

方法較長,我們看看關鍵的幾行:

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;
複製程式碼

用一個無限迴圈將訊息加入到訊息佇列中(連結串列的形式),到這裡把訊息發出去並加入佇列這兩步算是完成了,接下來就是取出並處理訊息。

1.6 Looper 取出訊息

Looper 中有一個死迴圈(Looper.loop())用來不斷從佇列中取出訊息:

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next();
        ...程式碼省略
        msg.target.dispatchMessage(msg);
        ...程式碼省略
        msg.recycleUnchecked();
    }
}
複製程式碼

queue.next()每次取出一條 Message 訊息,然後交由msg.targer.dispatchMessage(msg)處理,從上篇文章中可以知道,msg.targer就是發出訊息的 Handler,所以我們只需要關注dispatchMessage(msg)

1.7 dispatchMessage(msg)處理訊息

dispatchMessage(msg)在 Handler 類中

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製程式碼
  1. msg 的 callback 不為空,呼叫handleCallback方法(message.callback.run())
  2. mCallback 不為空,呼叫mCallback.handleMessage(msg)
  3. 最後如果其他都為空,執行 Handler 自身的handleMessage(msg)方法

第 1 點就是上面的 【1.1 post系列】 和 【1.2 postAtTime】,第 3 點就是我們最常見的handleMessage方法。需要注意一下就是callback.run()這裡直接呼叫執行緒的 run 方法,相當於普通方法呼叫,不會開啟新的執行緒

現在談談第 2 點,Handler 有很多種構造方法,除了上一篇文章提到的public Handler(Looper looper)Handler()等,還有一個:

public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
複製程式碼

Callback 是這樣的:

public interface Callback {
        public boolean handleMessage(Message msg);
    }
複製程式碼

需要重寫handleMessage,這不就是 Handler 裡面的handleMessage嗎?其實兩者是有區別的:

// Handler
public void handleMessage(Message msg) {}


// Callback
public boolean handleMessage(Message msg);
複製程式碼

Callback 裡面的handleMessage返回值是 Boolean 型別的,那麼接下來分別返回 true 和 false 看看效果吧:

class MainActivity : AppCompatActivity() {
    var handler: Handler? = null
    var looper: Looper? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        looper = Looper.getMainLooper()
        val callback = object: Handler.Callback{
            override fun handleMessage(msg: Message?): Boolean {
                Log.e("abc","--- Callback:threadName ---" + Thread.currentThread().name
                )
                return true
            }
        }
        val thread = object : Thread() {
            override fun run() {
                super.run()
                handler = object : Handler(looper, callback) {
                    override fun handleMessage(msg: Message?) {
                        super.handleMessage(msg)
                        Log.e("abc","--- handleMessage:threadName ---" + Thread.currentThread().name
                        )
                    }
                }
            }
        }

        thread.start()

        myBtn.setOnClickListener {
            handler?.sendEmptyMessage(4)
        }
    }
}

// Log 列印情況
--- Callback:threadName ---main

複製程式碼

如果返回值型別改成 false:

val callback = object: Handler.Callback{
            override fun handleMessage(msg: Message?): Boolean {
                Log.e("abc", "--- Callback:threadName ---" + Thread.currentThread().name
                )
                return false
            }
        }
        
// Log 列印情況
--- Callback:threadName ---main
--- handleMessage:threadName ---main
複製程式碼

所以,Callback 中的handleMessage返回 true 就不繼續執行 Handler 中的handlerMessage了,反之則兩個handleMessage都執行。其實這些從dispatchMessage方法中可以看出來(返回 true 則 return 終止,否則繼續執行 handleMessage):

 if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
        return;
    }
}
handleMessage(msg);
複製程式碼

總結

上面主要講了訊息的傳送和取出,大概知道了 Handler 訊息機制的工作流程:

  1. Handler 物件通過post(postDelayedpostAtTime)或者sendMessagesendEmptyMessage)把訊息(Message)交給 MessageQueue
  2. MessageQueue.enqueueMessage方法將 Message 以連結串列的形式放入訊息佇列中
  3. Looper.loop()迴圈呼叫 MessageQueue 的next()取出訊息,交給 Handler 的dispatchMessage方法處理訊息
  4. dispatchMessage()中分別判斷msg.callback和建構函式傳入的mCallback是否為空,不為空則執行它們的回撥,為空則執行 Handler 自身的handlerMessage方法。

2. Handler 記憶體洩漏問題

2.1 引起記憶體洩漏的原因

下面這樣寫會有記憶體洩漏風險:

val mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
        }
    }
複製程式碼

Android Studio 會標黃警告,滑鼠放在 handler 程式碼塊部分還會彈出提示,大概意思就是建議你用靜態模式或者弱引用。上面這種寫法相當於定義了一個匿名內部類,非靜態的匿名內部類預設是持有外部類(對應 Activity 等)引用的。如果發訊息的 handler 所線上程還在執行,當前 Activity 就被 finish 了,那麼該 Handler 的匿名內部類持有 Activity 的引用,所以 Activity 物件是無法被 GC 機制回收的。即:執行了 finish 程式碼,但 Activity 物件還在記憶體中(記憶體洩漏)。這種物件如果越來越多,就會有 OOM(記憶體溢位)的可能。

2.2 解決辦法

kotlin 中沒有靜態類這個概念,這裡用 java 靜態內部類舉例:

static class MyHandler extends Handler {
        WeakReference<MyActivity> weakActivity;

        MyHandler(MyActivity activity) {
            weakActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MyActivity activity = weakActivity.get();
            // activity.text = "......"
        }
    }
複製程式碼
  1. 靜態內部類不持有外部類引用,所以不會導致 Activity 物件洩漏(java 中 「靜態的」等於「類的」,靜態內部類如果能持有外部類引用,那說明外部類的引用就是內部「類的」,這不符合邏輯,這樣寫編譯都不通過)。

  2. 但該靜態內部類必須使用外部類的引用(比如操作 UI),此時就可以用弱引用的方式。上面程式碼用的是把 Activity 的弱引用在 Handler 建構函式中初始化,這樣如果需要操作 UI,可以使用activity.text = "test"這種方式。

參考文章:

  1. 從Handler.post(Runnable r)再一次梳理Android的訊息機制(以及handler的記憶體洩露
  2. Handler記憶體洩露及解決方案

相關文章