[- Video篇 -]:記一次Handler的使用

funnyok發表於2021-09-09
0.前言

最近在重構我的視訊播放器,專案有點點複雜,不可能全面的記錄
接下來,挑一些要點來記錄一下,上下文鋪設比較繁瑣,所以用虛擬碼
本文可以算是對Android點將臺:烽火狼煙[-Handler-]的補充

[- Video篇 -]:記一次Handler的使用

功能需求:
如果皮膚未顯示,點選螢幕 [顯示皮膚], 5s後[自動隱藏皮膚]
如果皮膚已顯示,點選皮膚[隱藏皮膚]
複製程式碼

1.使用Handler傳送延遲訊息

可能平時Handler都是post用來切換執行緒,它的本職是收發訊息
這裡很容易想到使用Handler傳送延遲訊息

[- Video篇 -]:記一次Handler的使用

    override fun showPanel() {
        isShow = true
        //TODO 顯示皮膚處理邏輯
        //使用Handler五秒鐘之後隱藏皮膚
        mHandler.sendEmptyMessageDelayed(100, 5000)
    }
    
    private val mHandler = Handler {
        //TODO 顯示皮膚處理邏輯 hidePanel()
        false
    }
複製程式碼

2.問題來了

在用的時候總感覺哪裡不對勁,後來想想Handler的模型,應該是上一個訊息的鍋
我畫了一個圖,應該很明顯,在第四秒點選時,第五秒仍會觸發上一次的資訊
也就導致了第四秒的顯示皮膚只停留了1s,所以體驗很不好,既然發現問題了,那就解決一下唄

[- Video篇 -]:記一次Handler的使用

override fun showPanel() {
    isShow = true
    //TODO 顯示皮膚處理邏輯
    
    //使用Handler五秒鐘之後隱藏皮膚
    mHandler.removeMessages(100)//移除訊息
    mHandler.sendEmptyMessageDelayed(100, 5000)
}
複製程式碼

解決起來很簡單,顯示皮膚時把訊息移除就行了

[- Video篇 -]:記一次Handler的使用

本次記錄結束,為了感覺不那麼水。瞟一眼原始碼吧


3.根據what移除訊息原始碼
---->[Handler#removeMessages(int)]-------------------------
|-- 根據訊息的what標識刪除訊息,可見是MessageQueue光環-----------
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

---->[MessageQueue#removeMessages(Handler, int,Object)]-----------
void removeMessages(Handler h, int what, Object object) {
    if (h == null) {//Handler為空,直接返回
        return;
    }
    synchronized (this) {
         Message p = mMessages;//訊息佇列的第一個物件
        // Remove all messages at front.-----------移除開頭符合條件的訊息----------
        
        //如果條件為真下面的程式碼會執行,此時p節點是我們想要移除的
        // 貌似只是開頭為目標節點才會執行 ---------------
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;//臨時變數n 記錄下一節點
            mMessages = n;
            p.recycleUnchecked();//回收掉p訊息
            p = n;//此時的p是目標的下一節點
        }

        // Remove all messages after front. ---- 移除開頭之後符合條件的訊息 --------
        while (p != null) {//遍歷所有p之後的節點
            Message n = p.next; //目標節點的下一節點
            if (n != null) {
                if (n.target == h && n.what == what//現在n是我們想要移除的
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();//回收掉n訊息
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}
複製程式碼

4.根據訊息的Runnable移除訊息

這個和上面差不多,不過很少人用Message的Runnable吧,Message本身可以新增一個Runnable的可執行體
在Handler的dispatchMessage中會優先觸發訊息的callback,即Runnable物件,而不會執行Handler自身的回撥

[- Video篇 -]:記一次Handler的使用

---->[Handler#dispatchMessage]---------------
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//如果msg有回撥
        handleCallback(msg);//處理msg的callback 
    } else {
        if (mCallback != null) {//這個上面舉例說明過,不說了
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//回撥覆寫的handleMessage方法
    }
}

---->[Handler#handleCallback]---------------
private static void handleCallback(Message message) {
    message.callback.run();
}

---->[MessageQueue#removeMessages(Handler,Runnable,Object)]-----------
void removeMessages(Handler h, Runnable r, Object object) {
    if (h == null || r == null) {
        return;
    }
    synchronized (this) {
        Message p = mMessages;
        // Remove all messages at front.
        //注意這裡用的是 == ,也就是匹配的是物件的記憶體地址 ----------
        while (p != null && p.target == h && p.callback == r
               && (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 && n.callback == r
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

---->[Handler#removeCallbacks]---------------
|--- 根據訊息的Runnable移除訊息
public final void removeCallbacks(Runnable r){
    mQueue.removeMessages(this, r, null);
}
複製程式碼

所以,也可以根據message的回撥移除訊息

private val mHandler = Handler()
val callback = Runnable {
    //TODO 顯示皮膚處理邏輯 hidePanel()
}

override fun showPanel() {
    isShow = true
    //TODO 顯示皮膚處理邏輯
    //使用Handler五秒鐘之後隱藏皮膚
    mHandler.removeCallbacks(callback)
    mHandler.sendEmptyMessageDelayed(100, 5000)
}
複製程式碼

好了,這篇小記就到這裡

相關文章