iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
1
自我挑戰組

Android 菜鳥村-開發基礎 30篇系列 第 6

[Day 4] Handler + Runnable Timer 實作

  • 分享至 

  • xImage
  •  

Runnable 介紹

Runnable 基本上也是 Message, 當調用 postDelayed() 調度某個 Runnable 的時候, 會將其包裝成一個 Message,然後再使用 sendMessageDelayed()這個方法進行發送 。不論是 post() 還是 send() 這類的方法,他們最後都是經過 Handler 的 sendMessageAtTime() 方法來加入到 MessageQueue , 最終經 由 發送他們的 Handler 做處理。

Handler 的源碼 , 當 Handler 透過 postDelayed() 調度某個 Runnable , 其實還是被包裝成 Message 經由 sendMessageDelayed 這個方法進行發送


    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

如下:由下我們可以得知 , 不論是 sendEmptyMessage , sendEmptyMessageDelayed 抑或是sendMessageDelayed , 最終都會導到 sendMessageAtTime() 方法來將 Message 加入到 MessageQueue



public final boolean sendEmptyMessage(int what)
{
    return sendEmptyMessageDelayed(what, 0);
}

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

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime() 方法 會執行 enqueueMessage()


    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);
    }

Handler 是如何處理自己發送的訊息的呢?

上一篇有提到 Message 會關聯 送出他的 Handler , 當 輪到這個 Message , Looper會把它取出 , 送出它的 Handler 會把它處理掉。looper 拿到 Message 後 透過 msg.target 找到了當初送出他的 Handler ,這 Handler 會利用 dispatchMessage 將 Message 處理掉。

Handler 處理訊息的路徑有三

  1. 如果這個 message 本身帶有 callback , Runnable 處理 即是走這條
  2. 如果我們有傳 callback 給 Handler ,會是走這條 ,昨天的實作即是走這條路徑
  3. 以上兩者就非 , 我們自己寫一個 Handler class ,覆寫 Handler 內的 handleMessage 方法

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
   // 1.
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
    // 2. 
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
     // 3.   
        handleMessage(msg);
    }
}


handleCallback 方法


private static void handleCallback(Message message) {
    message.callback.run();
}

Handler 內的 handleMessage 方法

public void handleMessage(Message msg) {
}

實作 Handler

今天只要實例化一個 Handler() , 我們會走上述第二條路 , 不傳 callback 給 Handler


handler = Handler()

實作 Runnable

dispatchMessage() 方法中 在處理 第二條路的 Message , 會調用 handleCallback ()
Runnable 的 run 方法會得到 回調。 在 run 方法 執行我們想執行的程式碼。

下面在 run() 方法內 , handler.postDelayed(runnable,1000) 這一行 , 我們又送出了 Message , 這行
是今天實作的重點。在處理完Message之後 又再送出 Message , 此時會變成一個循環。

runnable = object :Runnable{

        override fun run() {
            currentms++
            setCurrentTime()
            handler.postDelayed(runnable,1000)
        }


}

postDelayed 方法 需傳入的兩個參數 第一個 是這個 Runnable , 第二個是 Delay 幾秒才送出訊息

Handler 送出 Message

Handler 透過 postDelayed 將 Runnable 包裝成 Message 送出 , 開始計時。
Runnable 的 run 方法會獲得 回調 時 , Handler 在調用 同樣的方法 ,送出 Message 。


handler.postDelayed(runnable,1000)

Handler 移除 Message

當我們按下 停止按鈕時 , 調用 removeCallbacks()去移除 MessageQueue 上 runnable 包裝成的 Message , 此時 run 方法不再獲得回調 , 停止計時


 handler.removeCallbacks(runnable)

為何要 Delay ? 不直接post () 送出就好

今天 實作的 Timer 透過 postDelayed , 延遲 1 s 才執行 增加一秒 , 如果 post () 就沒有時間間隔了

實作 Timer 的原理

Timer 透過 postDelayed , 過了 1 s 之後 , 變數自動 加 1 , 這樣實作 出計時的功能


 currentms++

實作成果

今天的源碼

https://github.com/liyiwin/Day4_Timer


上一篇
[Day 3 ] Thread
下一篇
[Day 5 ] Kotlin class(類別) 與 繼承
系列文
Android 菜鳥村-開發基礎 30篇32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言