0.前言
最近在重構我的視訊播放器,專案有點點複雜,不可能全面的記錄
接下來,挑一些要點來記錄一下,上下文鋪設比較繁瑣,所以用虛擬碼
本文可以算是對Android點將臺:烽火狼煙[-Handler-]的補充
功能需求:
如果皮膚未顯示,點選螢幕 [顯示皮膚], 5s後[自動隱藏皮膚]
如果皮膚已顯示,點選皮膚[隱藏皮膚]
複製程式碼
1.使用Handler傳送延遲訊息
可能平時Handler都是post用來切換執行緒,它的本職是收發訊息
這裡很容易想到使用Handler傳送延遲訊息
override fun showPanel() {
isShow = true
//TODO 顯示皮膚處理邏輯
//使用Handler五秒鐘之後隱藏皮膚
mHandler.sendEmptyMessageDelayed(100, 5000)
}
private val mHandler = Handler {
//TODO 顯示皮膚處理邏輯 hidePanel()
false
}
複製程式碼
2.問題來了
在用的時候總感覺哪裡不對勁,後來想想Handler的模型,應該是上一個訊息的鍋
我畫了一個圖,應該很明顯,在第四秒點選時,第五秒仍會觸發上一次的資訊
也就導致了第四秒的顯示皮膚只停留了1s,所以體驗很不好,既然發現問題了,那就解決一下唄
override fun showPanel() {
isShow = true
//TODO 顯示皮膚處理邏輯
//使用Handler五秒鐘之後隱藏皮膚
mHandler.removeMessages(100)//移除訊息
mHandler.sendEmptyMessageDelayed(100, 5000)
}
複製程式碼
解決起來很簡單,顯示皮膚時把訊息移除就行了
本次記錄結束,為了感覺不那麼水。瞟一眼原始碼吧
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自身的回撥
---->[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)
}
複製程式碼
好了,這篇小記就到這裡