美文网首页
Handler同步屏障

Handler同步屏障

作者: 小杨不想努力了 | 来源:发表于2022-02-19 23:34 被阅读0次

    参考链接:https://www.jianshu.com/p/bc79cc25829a

    同步屏障

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

    demo

    package com.example.testdemo;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.RequiresApi;
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.os.MessageQueue;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import com.example.testdemo.databinding.ActivityMainBinding;
    import java.lang.reflect.Method;
    
    public class MainActivity extends AppCompatActivity {
        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(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
            initView(binding);
            initHandler();
        }
    
        private void initView(ActivityMainBinding binding) {
            binding.button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    sendSyncMessage();
                }
            });
            binding.button2.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
                @Override
                public void onClick(View v) {
                    sendAsynMessage();
                }
            });
            binding.button3.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.M)
                @Override
                public void onClick(View v) {
                    sendSyncBarrier();
                }
            });
            binding.button4.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.M)
                @Override
                public void onClick(View v) {
                    removeSyncBarrier();
                }
            });
    
        }
    
        private void initHandler() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();
                    mHandler = new Handler(Looper.myLooper()) {
                        @Override
                        public void handleMessage(@NonNull Message msg) {
                            if (msg.what == MESSAGE_TYPE_SYNC) {
                                Log.d("MainActivity", "收到普通消息");
                            } else if (msg.what == MESSAGE_TYPE_ASYN) {
                                Log.d("MainActivity", "收到异步消息");
                            }
                        }
                    };
                    Looper.loop();
                }
            }).start();
        }
    
        @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();
            }
        }
    }
    

    打印log

    D/MainActivity: 插入普通消息
    D/MainActivity: 收到普通消息
    D/MainActivity: 插入异步消息
    D/MainActivity: 收到异步消息
        //
    D/MainActivity: 插入同步屏障
    W/xample.testdem: Accessing hidden method Landroid/os/MessageQueue;->postSyncBarrier()I (greylist,test-api, reflection, allowed)
    D/MainActivity: 插入普通消息
    D/MainActivity: 插入异步消息
    D/MainActivity: 收到异步消息
        //
    D/MainActivity: 移除屏障
    W/xample.testdem: Accessing hidden method Landroid/os/MessageQueue;->removeSyncBarrier(I)V (greylist,test-api, reflection, allowed)
    D/MainActivity: 收到普通消息
    

    源码分析

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

    @UnsupportedAppUsage
        @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所处理。

    如何保证优先取出异步队列?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同步屏障

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