從isAsynchronous方法來看看Handler異步消息與同步屏障(SyncBarrier)

前言

Android的消息機制之前有一篇文章有寫,里面具體講到了Handler怎么發(fā)送和處理消息的整個過程。感興趣的同學可以先跳轉過去看看 從Handler.post(Runnable r)再一次梳理Android的消息機制(以及handler的內存泄露)

在看消息機制的時候,不管是把消息加入隊列,還是取出隊列,Message有個isAsynchronous方法一直沒關注,今天來看看這個方法到底是做什么的。

    /**
     * Returns true if the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     *
     * @return True if the message is asynchronous.
     *
     * @see #setAsynchronous(boolean)
     */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

Handler同步屏障(SyncBarrier)

要理解這個方法的含義,我們要先了解一下Handler的同步屏障機制。通常我們使用Handler發(fā)消息的時候,都是用的默認的構造方法生成Handler,然后用send方法來發(fā)送消息,其實這時候我們發(fā)送的都是同步消息,發(fā)出去之后就會在消息隊列里面排隊處理。我們都知道,Android系統(tǒng)16ms會刷新一次屏幕,如果主線程的消息過多,在16ms之內沒有執(zhí)行完,必然會造成卡頓或者掉幀。那怎么才能不排隊,沒有延時的處理呢?這個時候就需要異步消息,在處理異步消息的時候,我們就需要同步屏障,讓異步消息不用排隊等候處理??梢岳斫鉃橥狡琳鲜且欢聣?,把同步消息隊列攔住,先處理異步消息,等異步消息處理完了,這堵墻就會取消,然后繼續(xù)處理同步消息。

怎么來使用這個同步屏障呢?在MessageQueue里面有postSyncBarrier方法:

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

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

其實很簡單,就是創(chuàng)建了一個消息,但是值得注意的是,這個消息沒有target,普通的消息的必須有target的(不然交給誰來處理消息呢?具體的可以看開頭的文章鏈接)。然后我們來看看怎么取出消息。

處理異步消息

來到MessageQueuenext方法:

    Message next() {
        省略代碼
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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());
                }
            省略代碼
        }
    }

這里可以看到有一個很關鍵的判斷,上面我們知道屏障消息的target為空,所以這里判斷為true,while循環(huán)直到取出異步消息終止。接下來的處理就跟同步消息一樣了,這里不贅述。

怎么發(fā)送異步消息

那怎么來發(fā)送異步消息呢?MessagesetAsynchronous方法可以直接設置為異步消息

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

還有就是可以看到Handler的構造方法

public Handler(boolean async)
public Handler(@Nullable Callback callback, boolean async)
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)

async參數(shù)可以控制是否發(fā)送異步消息,如果設置為true,Handler發(fā)送的都將是異步消息。

哪里有應用呢

我們平時要發(fā)送同步屏障postSyncBarrier需要反射才能使用

    public void postSyncBarrier() {
       Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
       token = (int) method.invoke(Looper.getMainLooper().getQueue());
   }

   public void removeSyncBarrier() {
       Method method = MessageQueue.class
    .     getDeclaredMethod("removeSyncBarrier", int.class);
        method.invoke(Looper.getMainLooper().getQueue(), token);}
   }

在Android系統(tǒng)里面為了更快響應UI刷新在ViewRootImpl.scheduleTraversals也有應用:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容