Handler同步屏障機制的原理和使用場景

什麼是Handler的同步屏障

Handler中的Message可以分爲兩類:同步消息、異步消息。消息類型可以通過以下函數得知

//Message.java
public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}

一般情況下這兩種消息的處理方式沒什麼區別,在設置了同步屏障時纔會出現差異。

Handler設置同步屏障之後可以攔截Looper對同步消息的獲取和分發。加入同步屏障之後,Looper只會獲取和處理異步消息,如果沒有異步消息會進入阻塞狀態。

怎樣設置同步屏障

同步屏障可以通過MessageQueue.postSyncBarrier函數來設置

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

方法分析

可以看到,該函數僅僅是創建了一個 Message 對象並加入到了消息鏈表中。乍一看好像沒什麼特別的,但是這裏面有一個很大的不同點是該 Message 沒有 target

我們通常都是通過Handler發送消息的,Handler中發送消息的函數有postXXX、sendEmptyMessageXXX以及sendMessageXXX等函數,而這些函數最終都會調用enqueueMessage函數

//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    //...
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到 enqueueMessage 爲 msg 設置了 target 字段。

所以,從代碼層面上來講,同步屏障就是一個 Message,一個 target 字段爲空的 Message。

同步屏障的工作原理

同步屏障只在Looper死循環獲取待處理消息時纔會起作用,也就是說同步屏障在MessageQueue.next函數中發揮着作用。

next 函數相關代碼

Message next() {
    //...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        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 while循環遍歷消息鏈表
                // 跳出循環時,msg指向離表頭最近的一個異步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //...
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        //將msg從消息鏈表中移除
                        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;
            }

            //...
        }

        //...
    }
}

可以看到,當設置了同步屏障之後,next函數將會忽略所有的同步消息,返回異步消息。換句話說就是,設置了同步屏障之後,Handler只會處理異步消息。再換句話說,同步屏障爲Handler消息機制增加了一種簡單的優先級機制,異步消息的優先級要高於同步消息。

如何發送異步消息

通常我們使用 Handler 發消息時,這些消息都是同步消息,如果我們想發送異步消息,那麼在創建 Handler 時使用以下構造函數中的其中一種( async傳true )

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

然後通過該Handler發送的所有消息都會變成異步消息

同步屏障的應用

Android4.1之後增加了Choreographer機制,用於同 Vsync 機制配合,統一動畫、輸入和繪製時機。

ViewRootImpl的requestLayout開啓繪製流程:

	@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();//檢查是否在主線程
            mLayoutRequested = true;//mLayoutRequested 是否measure和layout佈局。
            //重要函數
            scheduleTraversals();
        }
    }

Android 應用框架中爲了更快的響應UI刷新事件在 ViewRootImpl.scheduleTraversals 中使用了同步屏障

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //設置同步障礙,確保mTraversalRunnable優先被執行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //內部通過Handler發送了一個異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

mTraversalRunnable 調用了 performTraversals 執行measure、layout、draw

爲了讓mTraversalRunnable儘快被執行,在發消息之前調用MessageQueue.postSyncBarrier設置了同步屏障。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章