侧边栏
首页代码

Handler屏障消息

Handler 屏障消息

Handler Message 种类

HandlerMessage种类分为3种:

  • 普通消息
  • 屏障消息
  • 异步消息

同步消息

我们默认用的都是同步消息,即前面讲Handler里的构造函数参数的async参数默认是false,同步消息在MessageQueue里的存和取完全就是按照时间排的,也就是通过msg.when来排的。

异步消息

异步消息就是在创建Handler如果传入的asynctrue或者发送来的Message通过msg.setAsynchronous(true);后的消息就是异步消息,异步消息的功能要配合下面要讲的屏障消息才有效,否则和同步消息是一样的处理。

// Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    // 这个mAsynchronous就是在创建Handler的时候传入async参数
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

Barrier(屏障)消息

屏障消息又称为同步屏障。

屏障(Barrier)是一种特殊的Message,它最大的特征就是targetnull(只有屏障的target可以为null,如果我们自己设置Messagetargetnull的话会报异常),并且arg1属性被用作屏障的标识符来区别不同的屏障。屏障的作用是用于拦截队列中同步消息,放行异步消息。

屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

那么屏障消息是怎么被添加和删除的呢?我们可以看到在MessageQueue里有添加和删除屏障消息的方法:

添加消息屏障

// MessageQueue.java
private int postSyncBarrier(long when) {
    synchronized (this) {
        //屏障消息和普通消息的区别是屏障消息没有tartget。
        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) {
            // 这里是说如果p指向的消息时间戳比屏障消息小,说明这个消息比屏障消息先进入队列,
            // 那么这个消息不应该受到屏障消息的影响(屏障消息只影响比它后加入消息队列的消息),找到第一个比屏障消息晚进入的消息指针
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        // 上面找到第一个比屏障消息晚进入的消息指针之后,把屏障消息插入到消息队列中,也就是屏障消息指向第一个比它晚进入的消息p,
        // 上一个比它早进入消息队列的prev指向屏障消息,这样就完成了插入。
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
        // 如果prev是null,说明上面没有经过移动,也就是屏障消息就是在消息队列的头部了。
            msg.next = p;
            mMessages = msg;
        }
        //返回一个序号,通过这个序号可以撤销屏障
        return token;
    }
}

postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单。

  1. 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
  2. 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
  3. postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  4. postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。、

移除消息屏障

// MessageQueue.java
public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        // 前面在插入屏障消息后会生成一个token,这个token就是用来删除该屏障消息用的。
        // 所以这里通过判断target和token来找到该屏障消息,从而进行删除操作
        // 找到屏障消息的指针p
        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;
        // 上面找到屏障消息的指针p后,把前一个消息指向屏障消息的后一个消息,这样就把屏障消息移除了
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();
 
        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

移除一个消息屏障,做了以下几件事:

  1. 移除次序列号的token消息
  2. 如果主线程是阻塞状态,则唤醒线程

取出消息屏障

说完了屏障消息的插入和删除,那么屏障消息在哪里起作用的?它跟前面提到的异步消息又有什么关联呢?我们可以看到MessageQueuenext方法里有这么一段:

// MessageQueue.java
Message next() {
    ......
    // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
    // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。 
    // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时) 
    // 如果期间有程序唤醒会立即返回。
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ......
        //1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();//获取系统开机到现在的时间
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {//2、遇到屏障  msg.target == null
                do {
                    prevMsg = msg;
                    msg = msg.next;
                    // 这里的isAsynchronous方法就是前面设置进msg的async参数,通过它判断如果是异步消息,则跳出循环,把该异步消息返回
                    // 否则是同步消息,把同步消息阻塞。
                } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
            }
            if (msg != null) {
                //4、如果找到异步消息
                if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间) nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else { //异步消息到了处理时间,就从链表移除,返回它。
                    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 {
                // 如果没有异步消息nextPollTimeoutMillis复位,一直休眠等待被唤醒
                nextPollTimeoutMillis = -1;
            }
        ......
    }
}

可以看到,当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。

屏障消息的实际应用

屏障消息的作用是把在它之后入队的同步消息阻塞,但是异步消息还是正常按顺序取出执行,那么它的实际用途是什么呢?我们看到ViewRootImpl.scheduleTraversals()用到了屏障消息和异步消息。

TraversalRunnable的run(),在这个run()中会执行doTraversal(),最终会触发View的绘制流程:measure(),layout(),draw()。为了让绘制流程尽快被执行,用到了同步屏障技术。

// ViewRootImpl.java
//ViewRootImpl的requestLayout开启绘制流程
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();//检查是否在主线程
        mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
        scheduleTraversals();//重要函数
    }
}
    
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 这里先将主线程的MessageQueue设置了个消息屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 这里发送了个异步消息mTraversalRunnable,这个mTraversalRunnable最终会执行doTraversal(),也就是会触发View的绘制流程
        // 也就是说通过设置屏障消息,会把主线程的同步消息先阻塞,优先执行View绘制这个异步消息进行界面绘制。
        // 这很好理解,界面绘制的任务肯定要优先,否则就会出现界面卡顿。
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}


private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }
 
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
 
        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);
        }
    }
}

屏障消息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(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) {
                        Log.e("HandlerActivity","currentThread:"+Thread.currentThread().getName());
                        //super.handleMessage(msg);
                        if (msg.what == MESSAGE_TYPE_SYNC){
                            Log.d("HandlerActivity","收到普通消息");
                        }else if (msg.what == MESSAGE_TYPE_ASYN){
                            Log.d("HandlerActivity","收到异步消息");
                        }
                    }
                };
                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();
            }
        });
    }

    private void sendSyncBarrier() {
        Log.d("HandlerActivity","插入同步屏障");
        MessageQueue queue = mHandler.getLooper().getQueue();
        try {
            Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
            method.setAccessible(true);
            token = (int) method.invoke(queue);//1
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void removeSyncBarrier() {
        Log.d("HandlerActivity","移除屏障");
        MessageQueue queue = mHandler.getLooper().getQueue();
        try {
            Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
            method.setAccessible(true);
            method.invoke(queue,token);//2
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendSyncMessage() {
        Log.d("HandlerActivity","插入普通消息");
        Message message = Message.obtain();
        message.what = MESSAGE_TYPE_SYNC;
        mHandler.sendMessageDelayed(message,1000);
    }

    private void sendAsynMessage() {
        Log.d("HandlerActivity","插入异步消息");
        Message message=Message.obtain();
        message.what = MESSAGE_TYPE_ASYN;
        message.setAsynchronous(true);//3
        mHandler.sendMessageDelayed(message,1000);
    }
}

运行结果

只发送一个同步消息

2021-12-21 11:11:19.847 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:11:20.847 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到普通消息

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

只发一个异步消息

2021-12-21 11:12:39.279 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:12:40.280 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息

先插入同步屏障,再发送同步和异步消息

2021-12-21 11:13:18.215 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入同步屏障
2021-12-21 11:13:24.706 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:13:28.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:13:29.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息

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

先插入同步屏障,再发送同步和异步消息。再移除同步屏障

2021-12-21 11:13:18.215 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入同步屏障
2021-12-21 11:13:24.706 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:13:28.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:13:29.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息
2021-12-21 11:14:14.882 7138-7138/com.zxj.myhandler D/HandlerActivity: 移除屏障
2021-12-21 11:14:14.886 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到普通消息

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

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

Handler中的同步屏障

源码深度解析 Handler 机制及应用

Android Handler 机制 屏障消息(同步屏障)

Android Handler拾遗 - 屏障消息

揭秘 Android 消息机制之同步屏障:target==null ?

posted @ 2023-02-13 10:54  咸鱼Jay  阅读(242)  评论(0)    收藏  举报
页脚HTML代码