主頁 > 移動端開發 > 萬字復盤 Handler 中各式 Message 的使用和原理

萬字復盤 Handler 中各式 Message 的使用和原理

2021-09-26 13:43:26 移動端開發

我們會經常使用 Handler 的 send 或 post 去安排一個延時、非延時或插隊執行的 Message,但對于這個 Message 到底什么時候執行以及為什么是這樣,鮮少細究過,

本文將一 一盤點并起底個中原理!

同時針對大家不太熟悉的異步 Message 和 IdleHandler,進行演示和原理普及,篇幅較大,慢慢享用,

非延時執行 Message

先在主執行緒創建一個 Handler 并復寫 Callback 處理,

    private val mainHandler = Handler(Looper.getMainLooper()) { msg ->
        Log.d(
            "MainActivity",
            "Main thread message occurred & what:${msg.what}"
        )
        true
    }

不斷地發送期望即刻執行的 Message 和 Runnable 給主執行緒的 Handler,

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
    }

    private fun testSendNoDelayedMessages() {
        Log.d("MainActivity","testSendNoDelayedMessages() start")
        testSendMessages()
        testPostRunnable()
        Log.d("MainActivity","testSendNoDelayedMessages() end ")
    }

    private fun testSendMessages() {
        Log.d("MainActivity","startSendMessage() start")
        for (i in 1..10) {
            sendMessageRightNow(mainHandler, i)
        }
        Log.d("MainActivity","startSendMessage() end ")
    }

    private fun testPostRunnable() {
        Log.d("MainActivity","testPostRunnable() start")
        for (i in 11..20) {
            mainHandler.post { Log.d("MainActivity", "testPostRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostRunnable() end ")
    }

什么時候執行?

公布下日志前,大家可以猜測下運行的結果,Message 或 Runnable 在 send 或 post 之后會否立即執行,不是的話,什么時候執行?

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ...
 D MainActivity: testPostRunnable() run & i:20

答案可能跟預想的略有出入,單一細想好像又是合理的:發送完的 Message 或 Runnable 不會立即執行,MessageQueue 的喚醒和回呼需要等主執行緒的其他作業完成之后才能執行,

為什么?

非延時的 sendMessage()post() 最終仍然是呼叫 sendMessageAtTime() 將 Message 放入了 MessageQueue,只不過它的期待執行時間 when變成了 SystemClock.uptimeMillis(),即呼叫的時刻,

// Handler.java
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // when 等于當前時刻
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis);
    }

這些 Message 會按照 when 的先后排隊進入 MessageQueue 中,當 Message 滿足了條件會立即呼叫 wake,反之只是插入佇列而已,所以,上述的 send 或 post 回圈,會按照呼叫的先后挨個進入佇列,第一個 Message 會觸發 wake,

// MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {
        ...
        // 鑒于多執行緒往 Handler 里發送 Message 的情況
        // 在向佇列插入 Message 前需要上鎖
        synchronized (this) {
            ...
            msg.markInUse(); // Message 標記正在使用
            msg.when = when; // 更新 when 屬性
            Message p = mMessages; // 拿到佇列的 Head 
            boolean needWake;
            // 如果佇列為空
            // 或者 Message 需要插隊(sendMessageAtFrontOfQueue)
            // 又或者 Message 執行時刻比 Head 的更早
            // 該 Message 插入到隊首
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 執行緒是否因為沒有可執行的 Message 正在 block 或 wait
                // 是的話,喚醒
                needWake = mBlocked;
            } else {
                // 如果佇列已有 Message,Message 優先級又不高,同時執行時刻并不早于隊首的 Message
                // 如果執行緒正在 block 或 wait,或建立了同步屏障(target 為空),并且 Message 是異步的,則喚醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 遍歷佇列,找到 Message 目標插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 如果已經遍歷到隊尾了,或 Message 的時刻比當前 Message 要早
                    // 找到位置了,退出遍歷
                    if (p == null || when < p.when) {
                        break;
                    }
                    
                    // 如果前面決定需要喚醒,但佇列已有執行時刻更早的異步 Message 的話,先不喚醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 將 Message 插入佇列的目標位置
                msg.next = p;
                prev.next = msg;
            }

            // 需要喚醒的話,喚醒 Native 側的 MessageQueue
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

總結來講:

  • 第一次 send 的 Message 在 enqueue 進 MessageQueue 的隊首后,通知 Native 側 wake
  • 后續發送的其他 Message 或 Runnable 挨個 enqueue 進佇列
  • 接著執行主執行緒的其他任務,比如日志的列印
  • 空閑后 wake 完畢并在 next() 的下一次回圈里將隊首 Message 移除和回傳給 Looper 去回呼和執行
  • 之后 loop() 開始讀取 MessageQueue 當前隊首 Message 的下一次回圈,當前時刻必然晚于 send 時候設定的when,所以佇列里的 Message 挨個出隊和回呼

結論

非延時 Message 并非立即執行,只是放入 MessageQueue 等待調度而已,執行時刻不確定,

MessageQueue 會記錄請求的時刻,按照時刻的先后順序進行排隊,如果 MessageQueue 中積攢了很多 Message,或主執行緒被占用的話,Message 的執行會明顯晚于請求的時刻,

延時執行 Message

延時執行的 Message 使用更為常見,那它又是何時執行呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendDelayedMessages()
    }

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 發送 Delay 2500 ms 的 Message
        sendDelayedMessage(mainHandler, 1)
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }

28:58.186 發送 Message,29:00.690 Message 執行,時間差為 2504ms,并非準確的 2500ms,

09-22 22:28:57.964 24980 24980 D MainActivity: onCreate()
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() start
// 發送 Message
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() end
// Message 執行
09-22 22:29:00.690 24980 24980 D MainActivity: Main thread message occurred & what:1

如果連續發送 10 個均延時 2500ms 的 Message 會怎么樣?

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 連續發送 10 個 Delay 2500 ms 的 Message
        for (i in 1..10) {
            sendDelayedMessage(mainHandler, i)
        }
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }

第 1 個 Message 執行的時間差為 2505ms(39:56.841 - 39:54.336),第 10 個 Message 執行的時間差已經達到了 2508ms(39:56.844 - 39:54.336),

09-22 22:39:54.116 25104 25104 D MainActivity: onCreate()
09-22 22:39:54.336 25104 25104 D MainActivity: testSendDelayedMessages() start
09-22 22:39:54.337 25104 25104 D MainActivity: testSendDelayedMessages() end
09-22 22:39:56.841 25104 25104 D MainActivity: Main thread message occurred & what:1
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:2
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:3
..
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:8
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:9
09-22 22:39:56.844 25104 25104 D MainActivity: Main thread message occurred & what:10

為什么?

延時 Message 執行的時刻 when 采用的是發送的時刻和 Delay 時長的累加,基于此排隊進 MessageQueue,

// Handler.java
    public final boolean postDelayed(Runnable r, int what, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {  
    	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis); 
    }

Delay Message 尚未抵達的時候,MessageQueue#next() 會將讀取佇列的時刻與 when 的差值,作為下一次通知 Native 休眠的時長,進行下一次回圈前,next() 還存在其他邏輯,導致 wake up 的時刻存在滯后,此外由于 wake up 后執行緒存在其他任務導致執行更加延后,

// MessageQueue.java
	Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                
                if (msg != null) {
                    // 計算下一次回圈應當休眠的時長
                    if (now < msg.when) {
                        nextPollTimeoutMillis
                            = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ...
                    }
                } else {
                    ...
                }
                ...
            }
            ...
        }
    }

結論

由于喚醒時長的計算誤差和回呼的任務可能占用執行緒,導致延時執行 Message 不是時間到了就會執行,其執行的時刻必然晚于 Delay 的時刻,

插隊執行 Message

Handler 還提供了 Message 插隊的 API:sendMessageAtFrontOfQueue()postAtFrontOfQueue()

在上述的 send 和 post 之后同時呼叫 xxxFrontOfQueue 的方法,Message 的執行結果會怎么樣?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
        testFrontMessages() // 立馬呼叫 FrontOfQueue 的方法
    }

分別呼叫 sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API,

    private fun testFrontMessages() {
        Log.d("MainActivity","testFrontMessages() start")
        testSendFrontMessages()
        testPostFrontRunnable()
        Log.d("MainActivity","testFrontMessages() end ")
    }

    private fun testSendFrontMessages() {
        Log.d("MainActivity","testSendFrontMessages() start")
        for (i in 21..30) {
            sendMessageFront(mainHandler, i)
        }
        Log.d("MainActivity","testSendFrontMessages() end ")
    }

    private fun testPostFrontRunnable() {
        Log.d("MainActivity","testPostFrontRunnable() start")
        for (i in 31..40) {
            mainHandler.postAtFrontOfQueue() { Log.d("MainActivity", "testPostFrontRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostFrontRunnable() end ")
    }

當主執行緒的列印日志按序輸出后,Message 開始逐個執行,按照預想的一樣,FrontOfQueue 的 Message 會先執行,也就是最后一次呼叫這個 API 的最早回呼,

Front 的 Message 逆序執行完畢之后,普通的 Message 才按照請求的順序執行,

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: testFrontMessages() start
 D MainActivity: testSendFrontMessages() start
 D MainActivity: testSendFrontMessages() end
 D MainActivity: testPostFrontRunnable() start
 D MainActivity: testPostFrontRunnable() end
 D MainActivity: testFrontMessages() end
 D MainActivity: testPostFrontRunnable() run & i:40
 ...
 D MainActivity: testPostFrontRunnable() run & i:31
 D MainActivity: Main thread message occurred & what:30
 ...
 D MainActivity: Main thread message occurred & what:21
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ... D MainActivity: testPostRunnable() run & i:20

怎么實作的?

原理在于 sendMessageAtFrontOfQueue() 或 postAtFrontOfQueue() 發送的 Mesage 被記錄的 when 屬性被固定為 0

// Handler.java
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        return enqueueMessage(queue, msg, 0); // 發送的 when 等于 0
    }

    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

從入隊函式可以看出,when 為 0 的 Message 會立即插入隊首,所以總會先得到執行,

// MessageQueue.java
    enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            // 如果 Message 需要插隊(sendMessageAtFrontOfQueue)
            // 則插入隊首
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                ...
            }
            ...
        }
        return true;
    }

結論

sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API 通過將 when 預設為 0 進而插入 Message 至隊首,最終達到 Message 先得到執行的目的,

但需要注意的是,這將造成本來先該執行的 Message 被延后調度,對于存在先后關系的業務邏輯來說將可能造成順序問題,謹慎使用!

異步執行 Message

Handler 發送的 Message 都是同步的,意味著大家都按照 when 的先后進行排序,誰先到誰執行,

如果遇到優先級高的 Message 可以通過 FrontQueue 發送插隊 Message即可,但如果是希望同步的佇列停滯只執行指定 Message 的話,即 Message 異步執行,現有的 API 是不夠的,

事實上 Android 提供了同步屏障的機制來實作這一需求,不過主要面向的是系統 App 或 系統,App 可以通過反射來使用,

通過異步 Handler 實作

除了一般使用的 Handler 建構式以外,Handler 還提供了創建發送異步 Message 的專用建構式,通過該 Handler 發送的 Message 或 Runnable 都是異步的,我們將其稱為異步 Handler

// Handler.java
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @NonNull
    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }

我們啟動一個 HandlerThread 來測驗一下同步屏障的使用:分別構建一個普通 Handler異步 Handler

    private fun startBarrierThread() {
        val handlerThread = HandlerThread("Test barrier thread")
        handlerThread.start()

        normalHandler = Handler(handlerThread.looper) { msg ->
            Log.d(...)
            true
        }

        barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
            Log.d(...)
            true
        }
    }

啟動 HandlerThread 并向兩個 Handler 各發送一個 Message,

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        startBarrierThread()
        testNormalMessage()
        testSyncBarrierByHandler()
    }
    
    private fun testNormalMessage() {
        sendMessageRightNow(normalHandler, 1)
    }

    private fun testSyncBarrierByHandler() {
        sendMessageRightNow(barrierHandler, 2)
    }

是異步 Handler 的 Message 先執行嗎?非也,因為我們還沒有通知 MessageQueue 建立同步屏障!

09-24 23:02:19.032 28113 28113 D MainActivity: onCreate()
09-24 23:02:19.150 28113 28141 D MainActivity: Normal handler message occurred & what:1
09-24 23:02:19.150 28113 28141 D MainActivity: Barrier handler message occurred & what:2

除了發送異步Handler 發送異步 Message 以外,需要通過反射事先建立起同步屏障,

注意:建立同步屏障必須早于需要屏蔽的同步 Message,否則無效,后面的原理會提及,

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        startBarrierThread()
        // 建立一個同步屏障
        postSyncBarrier(barrierHandler.looper)
        testNormalMessage()
        testSyncBarrierByHandler()
    }

    private fun postSyncBarrier(looper: Looper) {
        Log.d(...)
        val method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier")
        barrierToken = method.invoke(looper.queue) as Int
    }

這樣子便可以看到,異步 Message 執行了,而且同步 Message 永遠得不到執行,

09-24 23:11:36.176 28600 28600 D MainActivity: onCreate()
09-24 23:11:36.296 28600 28600 D MainActivity: Add sync barrier
09-24 23:11:36.300 28600 28629 D MainActivity: Barrier handler message occurred & what:2

原因在于建立的同步屏障尚未移除,永遠只處理佇列里的異步 Message,想要讓同步 Message 恢復執行的話 remove 同步屏障即可,同樣也需要反射!

我們在異步 Handler 執行結束后移除同步屏障,

    private fun startBarrierThread() {
        ...
        barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
            Log.d(...)
            // 移除同步屏障
            removeSyncBarrier(barrierHandler.looper)
            true
        }
    }

    fun removeSyncBarrier(looper: Looper) {
        Log.d(...)
        val method = MessageQueue::class.java
            .getDeclaredMethod("removeSyncBarrier", Int::class.javaPrimitiveType)
        method.invoke(looper.queue, barrierToken)
    }

可以看到同步 Message 恢復了,

09-24 23:10:31.533 28539 28539 D MainActivity: onCreate()
09-24 23:10:31.652 28539 28568 D MainActivity: Barrier handler message occurred & what:2
09-24 23:10:31.652 28539 28568 D MainActivity: Remove sync barrier
09-24 23:10:31.653 28539 28568 D MainActivity: Normal handler message occurred & what:1

通過異步 Message 實作

沒有專用的異步 Handler 的時候,可以向普通 Handler 發送一個 isAsync 屬性為 true 的 message,效果和異步 Handler 是一樣的,當然這種方式仍舊需要建立同步屏障,

在原有的發送 Message 的函式里加入 isAsync 的多載引數,

    private fun sendMessageRightNow(handler: Handler, what: Int, isAsync: Boolean = false) {
        Message.obtain().let {
            it.what = what
            it.isAsynchronous = isAsync
            handler.sendMessage(it)
        }
    }

向普通 Handler 發送異步 Message,

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testNormalMessage()
        // 改用 Message 方式發送異步 Message
        testSyncBarrierByMessage()
    }

    private fun testSyncBarrierByMessage() {
        sendMessageRightNow(normalHandler, 2, true)
    }

同樣記得在異步 Message 收到后移除同步屏障,

    private fun startBarrierThread() {
        ...
        normalHandler = Handler(handlerThread.looper) { msg ->
            Log.d(...)
            if (2 == msg.what) removeSyncBarrier(barrierHandler.looper)
            true
        }
    }

結果和異步 Handler 的方式一致,

09-24 23:58:05.801 29040 29040 D MainActivity: onCreate()
09-24 23:58:05.923 29040 29040 D MainActivity: Add sync barrier
09-24 23:58:05.923 29040 29070 D MainActivity: Normal handler message occurred & what:2
09-24 23:58:05.924 29040 29070 D MainActivity: Remove sync barrier
09-24 23:58:05.924 29040 29070 D MainActivity: Normal handler message occurred & what:1

原理

先來看一下同步屏障是怎么建立的,

// MessageQueue.java
	// 默認是呼叫的時刻開始建立屏障
	public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    // 同步屏障支持指定開始的時刻
	// 默認是呼叫的時刻,而 0 表示?
	private int postSyncBarrier(long when) {
        synchronized (this) {
            // 同步屏障可以建立多個,用計數的 Token 變數識別
            final int token = mNextBarrierToken++;

            // 獲取一個屏障 Message
            // 其 target 屬性為空
            // 指定 when 屬性為屏障的開始時刻
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            // 將 Token 存入屏障 Message
            // 用以識別對應的同步屏障
            msg.arg1 = token;

            // 按照 when 的先后
            // 找到屏障 Message 插入佇列的適當位置
            // 所以,如果同步屏障的建立呼叫得晚
            // 那么在它之前的 Message 無法阻攔
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }

            // 將屏障 Message 插入
            if (prev != null) {
                msg.next = p;
                prev.next = msg;
            } else {
                // 如果佇列尚無 Message
                // 或隊首的 Message 時刻
                // 都比屏障 Message 要晚的話
                // 將屏障 Message 插入隊首
                msg.next = p;
                mMessages = msg;
            }
            
            // 回傳上面的 Token 給呼叫端
            // 主要用于移除對應的屏障
            return token;
        }
    }

再來看下異步 Message 如何執行,

// MessageQueue.java
	Message next() {
        ...
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 隊首是屏障 Message 的話
                // 遍歷找到下一個異步 Message
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                
                // 沒有建立同步屏障且隊里有 Message
                // 或者
                // 建立了同步屏障下且找到了異步 Message
                if (msg != null) {
                    // 如果當前時間尚早于目標執行時刻
                    if (now < msg.when) {
                        // 更新下次回圈應當休眠的超時時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        
                        // Message 找到了并出隊
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }

                        // Message 回傳
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 隊里尚無 Message
                    // 或建立了同步屏障,但尚無異步 Message
                    // 無限休眠
                    nextPollTimeoutMillis = -1;
                }
                ...
            }
            ...
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

最后看一下同步屏障如何移除,

// MessageQueue.java
	// 需要傳入 add 時回傳的 Token
	public void removeSyncBarrier(int token) {
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            // 遍歷佇列直到找到 token 吻合的屏障 Message
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            
            // 如果沒找到會拋出例外
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            
            // 將屏障 Message 移除
            
            // 如果屏障 Message 不在隊首的話
            // 無需喚醒
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                // 屏障 Message 在隊首
                // 且新的隊首存在且不是另一個屏障的話
                // 需要立即喚醒
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // 喚醒以立即處理后面的 Message
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

對原理進行簡單的總結:

  1. 同步屏障的建立:按照呼叫的時刻 when 在合適的位置放入一個屏障 Message(target 屬性為 null)來實作,同時得到標識屏障的計數 token 存入屏障 Message
  2. 讀取佇列的時候發現存在屏障 Message 的話,會遍歷佇列并回傳最早執行的異步 Message
  3. 同步屏障的移除:按照 token 去佇列里找到匹配的屏障 Message 進行出隊操作,如果出隊后隊首存在 Message 且非另一個同步屏障的話,立即喚醒 looper 執行緒

結論和應用

結論:

  • 可以通過異步 Handler,也可以通過異步 Message 兩種方式向 MessageQueue 添加異步 Message
  • 但都需要事先建立同步屏障,屏障的建立時間必須在阻攔的 Message 發出之前
  • 可以建立多個同步屏障,將按照指定的時刻排隊,通過計數 Token 進行識別
  • 同步屏障使用完之后記得移除,否則后續的 Message 永遠阻塞

和插隊執行 Message 的區別:

  • 插隊 Message 只能確保先執行,完了后續的 Message 還得執行
  • 異步 Message 則不同,同步屏障一旦建立將保持休眠,直到異步 Message 抵達,只有同步屏障被撤銷,后續 Message 才可恢復執行

應用:

AOSP 系統中使用異步 Message 最典型的地方要屬螢屏重繪,重繪的 Message 不希望被主執行緒的 Message 佇列阻塞,所以在發送重繪 Message 之前都會建立一個同步屏障,確保重繪任務優先執行,

// ViewRootImpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

	final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

屏障建立之后發送異步 Message,

// Choreographer.java
	private void postCallbackDelayedInternal(...) {
        synchronized (mLock) {
            ...

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

IdleHandler ”Message“

MessageQueue 提供的 IdleHandler 可以讓佇列在空閑的時候回呼(queueIdle())指定的邏輯,它本質上不是 Message 型別,但它在 MessageQueue 里調度的時候類似于 Message 的邏輯,姑且將它也理解成一種特殊的 ”Message“,

使用上很簡單,呼叫 MessageQueue 的 addIdleHandler() 添加實作即可,執行完之后無需再度執行的話需要呼叫 removeIdleHandler() 移除,或在回呼里回傳 false,

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        testIdleHandler()
    }

    private fun testIdleHandler() {
        Log.d("MainActivity","testIdleHandler() start")
        mainHandler.looper.queue.addIdleHandler {
            Log.d("MainActivity", "testIdleHandler() queueIdle callback")
            false
        }
        Log.d("MainActivity","testIdleHandler() end ")
    }

可以看到 addIdleHandler 呼叫之后并沒有立即執行,而是過了幾百 ms,queueIdle() 才得到了執行,

09-23 22:56:46.130  7732  7732 D MainActivity: onCreate()
09-23 22:56:46.281  7732  7732 D MainActivity: testIdleHandler() start
09-23 22:56:46.281  7732  7732 D MainActivity: testIdleHandler() end
09-23 22:56:46.598  7732  7732 D MainActivity: testIdleHandler() queueIdle callback

如果在 addIdleHandler 呼叫之后接著發送一串非延時 Message,queueIdle() 是先執行還是后執行呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        testIdleHandler()
        testSendMessages()
    }

結果顯示一堆 Message 執行完了之后,仍舊過了幾百 ms,queueIdle() 才得到了執行,

09-23 23:07:50.639  7926  7926 D MainActivity: onCreate()
09-23 23:07:50.856  7926  7926 D MainActivity: testIdleHandler() start
09-23 23:07:50.856  7926  7926 D MainActivity: testIdleHandler() end
09-23 23:07:50.856  7926  7926 D MainActivity: startSendMessage() start
09-23 23:07:50.857  7926  7926 D MainActivity: startSendMessage() end
09-23 23:07:50.914  7926  7926 D MainActivity: Main thread message occurred & what:1
...
09-23 23:07:50.916  7926  7926 D MainActivity: Main thread message occurred & what:10
09-23 23:07:51.132  7926  7926 D MainActivity: testIdleHandler() queueIdle callback

上述結果也可以理解,MessageQueue 里仍有一堆 Message 等待處理,并非空閑狀態,所以需要執行完之后才有機會回呼 queueIdle() ,

那如果發送的是延時 Message 呢?

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    testIdleHandler()
    testSendDelayedMessages()
}

因為發送的是延時 Message,MessageQueue 暫時是空閑的,會先將 IdleHandler 取出來處理,

09-23 23:21:36.135  8161  8161 D MainActivity: onCreate()
09-23 23:21:36.339  8161  8161 D MainActivity: testIdleHandler() start
09-23 23:21:36.340  8161  8161 D MainActivity: testIdleHandler() end
09-23 23:21:36.340  8161  8161 D MainActivity: testSendDelayedMessages() start
09-23 23:21:36.340  8161  8161 D MainActivity: testSendDelayedMessages() end
09-23 23:21:36.729  8161  8161 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:21:38.844  8161  8161 D MainActivity: Main thread message occurred & what:1
...
09-23 23:21:38.845  8161  8161 D MainActivity: Main thread message occurred & what:10

上面的 queueIdle() 回傳了 false 確保處理后 Handler 得到了移除,

但如果回傳 true 且沒有呼叫 removeIdleHandler() 的話,后續空閑的時候 Handler 還會被執行,這點需要留意!

    private fun testIdleHandler() {
        mainHandler.looper.queue.addIdleHandler {
            ...
            true // false
        }
    }

queueIdle() 因為沒被移除的緣故被回呼了多次,源自于 Looper 沒執行完一次 Message 后發現尚無 Message 的時候都會回呼一遍 IdleHandler,直到佇列一直沒有 Message 到來,

09-23 23:24:04.765  8226  8226 D MainActivity: onCreate()
09-23 23:24:05.010  8226  8226 D MainActivity: testIdleHandler() start
09-23 23:24:05.011  8226  8226 D MainActivity: testIdleHandler() end
09-23 23:24:05.368  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.370  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.378  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.381  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.459  8226  8226 D MainActivity: testIdleHandler() queueIdle callback

那如果 add 完不移除的 IdleHandler 之后,發送一個延時 Message,那便會導致空閑訊息多執行一遍,

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    testIdleHandler()
    sendDelayedMessage(mainHandler, 1)
}
09-23 23:31:53.928  8620  8620 D MainActivity: onCreate()
09-23 23:31:54.042  8620  8620 D MainActivity: testIdleHandler() start
09-23 23:31:54.042  8620  8620 D MainActivity: testIdleHandler() end
09-23 23:31:54.272  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.273  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.278  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.307  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.733  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:56.546  8620  8620 D MainActivity: Main thread message occurred & what:1
09-23 23:31:56.546  8620  8620 D MainActivity: testIdleHandler() queueIdle callback

為什么?

queueIdle() 的回呼由 MessageQueue#next() 回呼,

// MessageQueue.java
	Message next() {
        ...
        // 回圈的初次將待處理 IdleHandler 計數置為 -1
        // 保證第一次可以檢查 Idle Handler 的存在和呼叫
        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 隊首的 Message 且建立了同步屏障的話,尋找下一個異步 Message
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                
                // 找到了合適的 Message
                if (msg != null) {
                    // 如果當前時間尚早于目標執行時刻
                    // 設定休眠的超時時間,即當前時間與目標時刻的差值
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        
                        // 時間條件滿足 Message 出隊
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }

                        // 并回傳 Message
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 隊里尚無合適的 Message
                    // 進入無限休眠
                    nextPollTimeoutMillis = -1;
                }

                // 如果正在退出 Looper,結束回圈并回傳 null
                // 將促使 loop() 退出
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 如果沒有合適的 Message 且 Looper 沒有退出
                // 檢查是否有 Idle Handler 需要處理

                // 讀取 Idle Handler 串列
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                // 如果暫時沒有 Idle Handler 需要處理,則進入下一次回圈
                // 為使下次回圈如果出現新的 Idle Handler 能有機會執行
                // 不重置計數器,仍為初始值 -1
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }

                // 如果 IdleHandler 存在則拷貝到待處理串列
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍歷待處理 Idle Handlers
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null;

                boolean keep = false;
                try {
                    // 逐個回呼 queueIdle()
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                // 回呼回傳 false,則將其移除出 Idle 串列
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 處理完之后重置 IdleHandler 的計數
            // 保證下次回圈不會重復處理 IdleHandler
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

有幾點細節需要留意:

  1. next() 回圈的第一次將 count 置為 -1,確保佇列空閑的時候必然有機會處理 IdleHandler
  2. 如果暫無 IdleHandler 可以處理直接進入下一次回圈,并且保留 count 的處置,確保下次回圈可以檢查是否有新的 IdleHandler 加入進來
  3. IdleHandler 正常處理結束之后,避免下次回圈重復處理,會將 count 置為 0,保證下次不再檢查,注意:是下次回圈,不是永久不檢查

結論和應用

結論:
IdleHandler 可以實作 MessageQueue 空閑狀態下的任務執行,比如做一些啟動時的輕量級初始化任務,但由于其執行的時機依賴于佇列的 Message 狀態,不太可控,謹慎使用!

應用:AOSP 原始碼里有不少地方使用了 IdleHandler 機制,比如 ActivityThread 使用它在空閑的狀態下進行 GC 回收處理,

// ActivityThread.java
	final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }
	
	void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void unscheduleGcIdler() {
        if (mGcIdlerScheduled) {
            mGcIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

術語總結

非延時、延時以及插隊執行這幾種 Message 大家使用較多,無需贅述,但其他幾個冷僻的 Message 術語需要總結一下,供大家快速對比和加深理解,

Message 術語介紹
異步 MessageisAsync 屬性為 true 需要異步執行的 Message,需要配合同步屏障使用
異步 Handler專門發送異步 Message 的 Handler
屏障 Messagetarget 為空并持有 token 資訊的 Message 實體放入佇列,作為同步屏障的起點
同步屏障在 MessageQueue 指定時刻插入屏障 Message 確保只有異步 Message 執行的機制
空閑 IdleHandler用于在 MessageQueue 空閑的時候回呼的處理介面,若不移除每次佇列空閑了均會執行

結語

上述對于各種 Message 和 IdleHandler 做了演示和原理闡述,相信對于它的細節有了更深的了解,

下面來進行一個簡單的總結:

  • 非延時執行 Message:并非立即執行,而是按照請求的時刻進行排隊和調度,最終取決于佇列的順序和主執行緒是否空閑
  • 延時執行 Message:也并非在 Delay 的時刻立即執行,執行時刻受喚醒誤差和執行緒任務阻塞的影響必然晚于 Delay 時刻
  • 插隊執行 Mesage:同樣并非立即執行,而是每次都將任務放在了隊首,達到先執行的目的,但打亂了執行順序存在邏輯隱患
  • 異步 Message:系統使用居多,App 則需反射,通過這種機制可插隊執行同時確保其他 Message 阻塞,學習一下
  • IdleHandler “Message”:系統多有使用,實作 MessageQueue 空閑狀態下的任務執行,但執行時機不可控,最好執行完之后便移除,謹慎使用

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/303077.html

標籤:其他

上一篇:Android 從零開始了解Google訂閱服務(一)

下一篇:??【Android精進之路-05】怎么創建Activity,如何啟動另一個Activity,干貨滿滿,建議收藏??

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more