安卓開機自啟動管理:android延遲執行優化

首先,需要明確一點,Handler 延時消息機制不是延時發送消息,而是延時去處理消息;舉個例子,如下:

handler.postDelayed(() ->{
    Log.e("zjt", "delay runnable");
}, 3_000);

上面的 Handler 不是延時3秒後再發送消息,而是將消息插入消息隊列後等3秒後再去處理。

postDelayed 的方法如下:

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

其中的 getPostMessage 就是將 post 的 runnable 包裝成 Message,如下:

private static Message getPostMessage(Runnable r) {
    // 使用 Message.obtain() 避免重複創建實例對象,達到節約內存的目的
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

sendMessageDelayed 方法如下:

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // 延時的時間是手機的開機時間(不包括手機休眠時間)+ 需要延時的時間
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime 如下:

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

這裡面的代碼很好理解,就不說了,看看 enqueueMessage:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this; // 設置 msg 的 target 為Handler
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    // 非同步消息,這個需要配合同步屏障來使用,可以看我之前的文章,這裡不贅述
    if (mAsynchronous) { 
        msg.setAsynchronous(true);
    }
    // 插入到 MessageQueue 中
    return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue 的 enqueueMessage 的方法如下:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        // 判斷發送消息的進程是否還活著
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle(); // 回收消息到消息池
            return false;
        }
        msg.markInUse(); // 標記消息正在使用
        msg.when = when; 
        Message p = mMessages; // 獲取表頭消息
        boolean needWake;
        // 如果隊列中沒有消息 或者 消息為即時消息 或者 表頭消息時間大於當前消息的延時時間
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            // 表示要喚醒 Hander 對應的線程,這個後面解釋
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            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;
        }
        // 喚醒Handler對應的線程
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

舉個例子,假設我們消息隊列是空的,然後我發送一個延時10s的延時消息,那麼會直接把消息存入消息隊列。

從消息隊列中獲取消息是 通過 Looper.loop() 來調用 MessageQueue 的 next()方法,next()的主要代碼如下:

 Message next() {
     // Return here if the message loop has already quit and been disposed.
     // This can happen if the application tries to restart a looper after quit
     // which is not supported.
     final long ptr = mPtr;
     if (ptr == 0) {
         return null;
     }
     int pendingIdleHandlerCount = -1; // -1 only during first iteration
     int nextPollTimeoutMillis = 0;
     for (;;) {
         if (nextPollTimeoutMillis != 0) {
             Binder.flushPendingCommands();
         }
         // 表示要休眠多長時間,功能類似於wait(time)
         // -1表示一直休眠,
         // 等於0時,不堵塞
         // 當有新的消息來時,如果handler對應的線程是阻塞的,那麼會喚醒
         nativePollOnce(ptr, nextPollTimeoutMillis);
         synchronized (this) {
             // Try to retrieve the next message.  Return if found.
             final long now = SystemClock.uptimeMillis();
             Message prevMsg = null;
             Message msg = mMessages;
             if (msg != null && msg.target == null) {
                 // Stalled by a barrier.  Find the next asynchronous message in the queue.
                 do {
                     prevMsg = msg;
                     msg = msg.next;
                 } while (msg != null && !msg.isAsynchronous());
             }
             if (msg != null) {
                 if (now < msg.when) {
                     // 計算延時消息的剩餘時間
                     nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 } else {
                     // Got a message.
                     mBlocked = false;
                     if (prevMsg != null) {
                         prevMsg.next = msg.next;
                     } else {
                         mMessages = msg.next;
                     }
                     msg.next = null;
                     if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                     msg.markInUse();
                     return msg;
                 }
             } else {
                 // No more messages.
                 nextPollTimeoutMillis = -1;
             }

             .......
             // 判斷是否有 idle 任務,即主線程空閑時需要執行的任務,這個下面說

             if (pendingIdleHandlerCount <= 0) {
                 // 這裡表示所有到時間的消息都執行完了,剩下的如果有消息一定是延時且時間還沒到的消息; 
                 // 剛上面的 enqueueMessage 就是根據這個變數來判斷是否要喚醒handler對應的線程
                 mBlocked = true; 
                 continue;
             }       

        ......
     }
 }

其實,從這裡就可以看出來,Handler 的延時消息是如何實現的了。

比方說 發送一個延時10s的消息,那麼在 next()方法是,會阻塞 (10s + 發送消息時的系統開機時間 – 執行next()方法是系統的開機時間),到達阻塞時間時會喚醒。或者這時候有新的消息來了也會 根據 mBlocked = true來喚醒。

IdleHandler是什麼?

在 MessageQueue 類中有一個 static 的介面 IdleHanlder:

public static interface IdleHandler {
    boolean queueIdle();
}

MessageQueue中無可處理的Message時回調; 作用:UI線程處理完所有事務後,回調一些額外的操作,且不會堵塞主進程;

介面中只有一個 queueIdle() 函數,線程進入堵塞時執行的額外操作可以寫這裡, 返回值是true的話,執行完此方法後還會保留這個IdleHandler,否則刪除。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/221071.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-09 12:47
下一篇 2024-12-09 12:47

相關推薦

發表回復

登錄後才能評論