美文网首页
Handler中的同步屏障

Handler中的同步屏障

作者: 风月寒 | 来源:发表于2020-09-14 22:06 被阅读0次
    同步屏障

    首先需要发送一个特殊消息作为屏障消息,当消息队列检测到了这种消息后,就会从这个消息开始,遍历后续的消息,只处理其中被标记为“异步”的消息,忽略同步消息(所以叫“同步屏障”),相当于给一部分消息开设了“VIP”优先通道。

    因为一个读者建议,在分析源码的时候最好是结合demo来讲,后面想想也是,源码这些东西枯燥无味,所以我虚心的接纳了意见,话不多说,先上demo。

    public class HandlerActivity extends AppCompatActivity {
        private Button button1,button2,button3,button4;
        public static final int MESSAGE_TYPE_SYNC=1;
        public static final int MESSAGE_TYPE_ASYN=2;
        private int token;
        private Handler mHandler;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler);
            initView();
            initHandler();
        }
    
        private void initHandler() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();
                    mHandler = new Handler(){
                        @Override
                        public void handleMessage(@NonNull Message msg) {
                            //super.handleMessage(msg);
                            if (msg.what == MESSAGE_TYPE_SYNC){
                                Log.d("MainActivity","收到普通消息");
                            }else if (msg.what == MESSAGE_TYPE_ASYN){
                                Log.d("MainActivity","收到异步消息");
                            }
                        }
                    };
                    Looper.loop();
                }
            }).start();
        }
    
        private void initView() {
            button1 = findViewById(R.id.send_syne);
            button2 = findViewById(R.id.remove_sunc);
            button3 = findViewById(R.id.send_message);
            button4 = findViewById(R.id.send_async_message);
            button1.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.M)
                @Override
                public void onClick(View v) {
                    sendSyncBarrier();
                }
            });
            button2.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.M)
                @Override
                public void onClick(View v) {
                    removeSyncBarrier();
                }
            });
            button3.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    sendSyncMessage();
                }
            });
            button4.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
                @Override
                public void onClick(View v) {
                    sendAsynMessage();
                }
            });
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
        private void sendAsynMessage() {
            Log.d("MainActivity","插入异步消息");
            Message message=Message.obtain();
            message.what=MESSAGE_TYPE_ASYN;
            message.setAsynchronous(true);//3
            mHandler.sendMessageDelayed(message,1000);
        }
    
        private void sendSyncMessage() {
            Log.d("MainActivity","插入普通消息");
            Message message= Message.obtain();
            message.what=MESSAGE_TYPE_SYNC;
            mHandler.sendMessageDelayed(message,1000);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.M)
        private void removeSyncBarrier() {
            try{
                Log.d("MainActivity","移除屏障");
                MessageQueue queue=mHandler.getLooper().getQueue();
                Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
                method.setAccessible(true);
                method.invoke(queue,token);//2
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
        /**
         * 插入同步屏障
         */
        @RequiresApi(api = Build.VERSION_CODES.M)
        private void sendSyncBarrier() {
            try{
                Log.d("MainActivity","插入同步屏障");
                MessageQueue queue=mHandler.getLooper().getQueue();
                Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
                method.setAccessible(true);
                token= (int) method.invoke(queue);//1
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    

    其实demo就是发送一个同步消息和发送一个异步消息,插入同步屏障和移除同步屏障四个操作。

    结果
    只发送一个同步消息
    2020-09-14 20:54:02.595 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
    2020-09-14 20:54:03.597 4960-5110/com.example.asyntask D/MainActivity: 收到普通消息
    

    因为代码中是延时1s,所以1S后收到普通消息。

    只发一个异步消息
    2020-09-14 20:55:58.091 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
    2020-09-14 20:55:59.093 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
    

    跟第一种一样。

    先插入同步屏障,再发送同步和异步消息
    2020-09-14 20:57:28.940 4960-4960/com.example.asyntask D/MainActivity: 插入同步屏障
    2020-09-14 20:57:32.187 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
    2020-09-14 20:57:33.472 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
    2020-09-14 20:57:34.474 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
    

    我们可以看到,只收到了异步消息,而同步消息没有收到

    先插入同步屏障,再发送同步和异步消息。再移除同步屏障
    2020-09-14 20:57:28.940 4960-4960/com.example.asyntask D/MainActivity: 插入同步屏障
    2020-09-14 20:57:32.187 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
    2020-09-14 20:57:33.472 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
    2020-09-14 20:57:34.474 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
    2020-09-14 20:58:59.713 4960-4960/com.example.asyntask D/MainActivity: 移除屏障
    2020-09-14 20:58:59.714 4960-5110/com.example.asyntask D/MainActivity: 收到普通消息
    

    同步消息和异步消息都有收到。

    从这验证可以看出,满足前面说的对同步屏障的定义。

    源码分析

    插入同步屏障和移除同步屏障同时MessageQueue里面得方法。

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

    可以看到,postSyncBarrier是public修饰得,为什么我们再demo中还要采用反射去获取这个方法,因为再注释中,该方法时隐藏的。

    同步消息与异步消息的区别就是是否有设置target.

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    可以看到,插入一个消息的时候,会把msg.target = this,this就是指当前的handler.因为message最终会被对应的target也就是handler所处理。

    而在插入同步屏障之后,target == null,然后将该message插入到队列中。

    而怎么发送一条异步消息为demo中的注释3所示。

    插入到队列中,我们该怎样保证优先取出异步队列了?

    得继续从next()方法中去寻找答案。

    next()
    Message next() {
            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();
                }
    
                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) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            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;
                    }
                .....
            }
        }
    

    从next()可以知道,首先会判断异步消息,判断得条件是target == null.然后做循环去消息,如果有消息,则判断是否到了时间。如果没有消息,则nextPollTimeoutMillis = -1;这个表示需要阻塞,得等到有消息取出时才唤醒。

    具体可以看我得另外一篇文章。

    Handler问题问答 link

    相关文章

      网友评论

          本文标题:Handler中的同步屏障

          本文链接:https://www.haomeiwen.com/subject/vmmtyktx.html