Handler同步屏障

2023-05-16

一、消息机制之同步屏障

消息机制的同步屏障,其实就是阻碍同步消息,只让异步消息通过。而开启同步屏障的方法就是调用下面的方法:

MessageQueue#postSyncBarrier()

源码如下:

@TestApi
public int postSyncBarrier() {
    // 这里传入的时间是从开机到现在的时间戳
    return postSyncBarrier(SystemClock.uptimeMillis());
}
/**
 * 这就是创建的同步屏障的方法
 * 同步屏障就是一个同步消息,只不过这个消息的target为null
 */
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++;
        // 从消息池中获取Message
        final Message msg = Message.obtain();
        msg.markInUse();
        // 初始化Message对象的时候,并没有给Message.target赋值,
        // 因此Message.target==null
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            // 这里的when是要加入的Message的时间
            // 这里遍历是找到Message要加入的位置
            while (p != null && p.when <= when) {
                // 如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步
                // 消息里有时间小于T,则prev也不为null
                prev = p;
                p = p.next;
            }
        }
        // 根据prev是否为null,将msg按照时间顺序插入到消息队列的合适位置
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

在这里可以看到,Message对象初始化的时候并没有给target赋值,因此target==null的来源就找得到了。这样就可以插入一条target==null的消息,这个消息就是一个同步屏障。
那么开启消息屏障后,所谓的异步消息又是如何处理的呢?
消息的最终处理其实都是在消息轮询器Looper#loop()中,而loop()循环中会调用MessageQueue#next()从消息队列中进行取消息。

Message next() {
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时
    // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回
    // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 获取系统开机到现在的时间戳
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // 取出target==null的消息
            // 如果target==null,那么它就是屏障,需要循环遍历,
            // 一直往后找到第一个异步消息,即msg.isAsynchronous()为true
            // 这个target==null的消息,不会被取出处理,一直会存在
            // 每次处理异步消息的时候,都会从头开始轮询
            // 都需要经历从msg.target开始的遍历
            if (msg != null && msg.target == null) {
                // 使用一个do..while循环
                // 轮询消息队列里的消息,这里使用do..while循环的原因
                // 是因为do..while循环中取出的这第一个消息是target==null的消息
                // 这个消息是同步屏障的标志消息
                // 接下去进行遍历循环取出Message.isAsynchronous()为true的消息
                // isAsynchronous()为true就是异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 如果有消息需要处理,先判断时间有没有到,如果没有到的话设置阻塞时间
                if (now < msg.when) {
                    // 计算出离执行时间还有多久赋值给nextPollTimeoutMillis
                    // 表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 获取到消息
                    mBlocked = false;
                    // 链表操作,获取msg并且删除该节点
                    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;
            }

            ...
        }
    }
}

从上面的MessageQueue.next方法可以看出,当消息队列开启同步屏障的时候(即标识为msg.target==null),消息机制在处理消息的时候,会优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

如果上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙(同步屏障--红色部分)。有了同步屏障的存在,msg_2和msg_M这两个异步消息可以被优先处理,而后面的msg_3等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?就需要先移除这个同步屏障,即调用MessageQueue#removeSyncBarrier()

@TestApi
public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        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;
        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);
        }
    }
}

二、同步屏障的应用场景

同步屏障一般在日常开发中比较少用,而在系统源码中就有使用。Android系统中的UI更新相关的消息即为异步消息,需要优先处理。
16ms左右刷新UI,而是60hz的屏幕,即1s刷新60次。
在Android中什么是异步消息?即给:

Message.setAsynchronous(true);

比如,在View更新时,draw、requestLayout、invalidate等很多地方都调用了。ViewRootImpl#scheduleTraversals()

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 发送异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

在这里,mChoreographer.postCallback最终会执行到了Choreographer#postCallbackDelayedInternal()

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

可以看到,这里就开启了同步屏障,并且发送了异步消息。由于UI相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。
最后,当然要移除同步屏障的时候,调用ViewRootImpl#unscheduleTraversals

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

在ViewRootImpl中的doTraversal()方法中也会移除同步屏障,这里移除是因为requestLayout或者invalidate的时候,刷新之后,在doTraversal()中就会移除同步屏障,因为此时消息已经发送并且处理了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Handler同步屏障 的相关文章

  • Mac OS查看和设置JAVA_HOME

    下载java https www java com zh CN download 1 查看JAVA版本 打开Mac电脑 xff0c 查看JAVA版本 xff0c 打开终端Terminal xff0c 通过命令行查看笔者的java版本 xff
  • Android源码编译–jdk版本查询

    2 1 Android源码所需JDK版本 根参考资料 1 的说明 xff0c 在android src build core main mk中对jdk的版本进行查询 xff0c 以确定当前系统是否安装了特定版本的jdk xff0c 因此可以
  • android源码编译 ninja: build stopped: subcommand failed.

    接着编译 make j8 线程加多少个具体看机器配置 xff0c 问题也最可能是这一步骤引起的 xff0c 如果是虚拟机的话 xff0c 建议不要加线程 xff0c 直接使用make执行
  • Ubuntu环境下完美安装python模块numpy,scipy,matplotlib

    不同的ubuntu版本安装过这三个模块几次了 xff0c 然而总是出现各种问题 xff0c 最近一次是在ubuntu 16 04 LTS server版本安装的 xff0c 总的来说安装的比较顺利 先把pip安装好 sudo apt get
  • prebuilts/misc/darwin-x86/bison/bison: Bad CPU type in executable

    方案一 cd external bison touch patch high sierra patch vim patch high sierra patch With format string strictness High Sierr
  • android源码编译 坑

    bash lunch command not found 先调用 build envsetup sh 再执行 lunch Can not find SDK Can not find SDK 10 6 at Developer SDKs Ma
  • 获取当前MacOSX SDK

    xcrun show sdk path 打印出 Library Developer CommandLineTools SDKs MacOSX sdk xcrun show sdk version 打印出 10 15 4 xcode sele
  • Mac OS10.12 编译Android源码8.1

    内容 介绍mac os10 12拉取android源码 xff0c 并且编译后 xff0c 刷入手机的过程 下载的rom是android 8 1 xff0c 手机是pixel 准备工作 硬盘大小 本人Mac磁盘空间只有256GB xff0c
  • android源码 xcode版本,【Android】AOSP源码下载及编译 for mac

    本文记录了AOSP在Mac系统上下载和编译的过程 采用的系统是 macOS 10 13 1 所使用的AOSP分支是 android 8 1 0 r7 系统预留空间 大于200G 一 环境配置 环境配置 xff0c 官网给出了非常全的教程 x
  • (Android 9.0)Activity启动流程源码分析

    前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质 xff0c 其重要程度就不多做描述了 同时 xff0c 知识栈应该不断的更新 xff0c 最新发布的Android 9 0版本相较于之前的几个版本也
  • Lifecycle 源码详解

    Lifecycle 是 Jetpack 整个家族体系内最为基础的内容之一 xff0c 正是因为有了 Lifecycle 的存在 xff0c 使得如今开发者搭建依赖于生命周期变化的业务逻辑变得简单高效了许多 xff0c 使得我们可以用一种统一
  • git常用命令

    1 拉取远程所有分支 git clone xxx git branch r grep v 39 gt 39 while read remote do git branch track 34 remote origin 34 34 remot
  • Android应用启动流程分析

    1 前言 网上看过很多Activity启动过程的源码解析 xff0c 很多文章会贴上一大段代码 xff0c 然后从startActivity 函数开始深究整个源码的调用栈 个人感觉这类文章代码细节太多 xff0c 反而容易迷失在源码调用之中
  • 从一个分支cherry-pick多个commit到其他分支

    在branch1开发 xff0c 进行多个提交 xff0c 这是切换到branch2 xff0c 想把之前branch1分支提交的commit都 复制 过来 xff0c 怎么办 xff1f 单个commit只需要git cherry pic
  • IntWritable详解

    1 Hadoop数据类型如下图 xff1a 由上图的Writable层次结构图可以看到绝大多数的数据类型都实现了Writable WritableComparable接口 xff0c 在此先分析一下这两个接口情况 自顶下下逐步分析 Writ
  • 线程池源码剖析

    线程池 xff08 英语 xff1a thread pool xff09 xff1a 一种线程使用模式 线程过多会带来调度开销 xff0c 进而影响缓存局部性和整体性能 而线程池维护着多个线程 xff0c 等待着监督管理者分配可并发执行的任
  • Java 设计模式之装饰者模式

    一 了解装饰者模式 1 1 什么是装饰者模式 装饰者模式指的是在不必改变原类文件和使用继承的情况下 xff0c 动态地扩展一个对象的功能 它是通过创建一个包装对象 xff0c 也就是装饰者来包裹真实的对象 所以装饰者可以动态地将责任附加到对
  • Java 设计模式之策略模式

    一 了解策略模式 1 1 什么是策略模式 策略模式 Strategy Pattern 是指对一系列的算法定义 xff0c 并将每一个算法封装起来 xff0c 而且使它们还可以相互替换 此模式让算法的变化独立于使用算法的客户 1 2 策略模式
  • Java 设计模式之适配器模式

    一 了解适配器模式 1 1 什么是适配器模式 适配器模式将一个类的接口 xff0c 转换成客户期望的另一个接口 适配器让原来接口不兼容的类可以合作无间 适配器模式有两种 xff1a 对象 适配器和 类 适配器 这个模式可以通过创建适配器进行
  • 责任链模式

    责任链模式的定义与特点 责任链模式的定义 xff1a 使多个对象都有机会处理请求 xff0c 从而避免请求的发送者和接受者之间的耦合关系 xff0c 将这个对象连成一条链 xff0c 并沿着这条链传递该请求 xff0c 直到有一个对象处理他

随机推荐

  • java设计模式-桥接模式

    桥接模式定义 桥接模式 xff08 Bridge Pattern xff09 xff0c 将抽象部分与它的实现部分分离 xff0c 使它们都可以独立地变化 更容易理解的表述是 xff1a 实现系统可从多种维度分类 xff0c 桥接模式将各维
  • java设计模式-状态模式

    1 状态模式的定义和特点 状态 xff08 State xff09 模式的定义 xff1a 对有状态的对象 xff0c 把复杂的 判断逻辑 提取到不同的状态对象中 xff0c 允许状态对象在其内部状态发生改变时改变其行为 状态模式是一种对象
  • java设计模式-命令模式

    18 xff0c 命令模式 18 1 命令模式的定义和特点 命令 xff08 Command xff09 模式的定义如下 xff1a 将一个请求封装为一个对象 xff0c 使发出请求的责任和执行请求的责任分割开 这样两者之间通过命令对象进行
  • java设计模式-代理模式

    17 xff0c 代理模式 17 1 代理模式的定义和特点 代理模式的定义 xff1a 由于某些原因需要给某对象提供一个代理以控制对该对象的访问 这时 xff0c 访问对象不适合或者不能直接引用目标对象 xff0c 代理对象作为访问对象和目
  • 工厂方法模式

    概念定义 工厂方法 Factory Method 模式 xff0c 又称多态工厂 Polymorphic Factory 模式或虚拟构造器 Virtual Constructor 模式 工厂方法模式通过定义工厂抽象父类 或接口 负责定义创建
  • TextFuseNet: Scene Text Detection with Richer Fused Features论文阅读

    TextFuseNet Scene Text Detection with Richer Fused Features 利用更丰富的特征融合进行场景文本检测 代码 xff1a https github com ying09 TextFuse
  • JUC原子类: CAS, Unsafe和原子类详解

    CAS 线程安全的实现方法包含 互斥同步 synchronized 和 ReentrantLock非阻塞同步 CAS AtomicXXXX无同步方案 栈封闭 xff0c Thread Local xff0c 可重入代码 什么是CAS CAS
  • OKHttp中的责任链模式

    一 什么是责任链模式 责任链 xff0c 顾名思义是将多个节点通过链条的方式连接起来 xff0c 每一个节点相当于一个对象 xff0c 而每一个对象层层相关 xff0c 直接或者间接引用下一个对象 xff08 节点 xff09 xff1b
  • android bugly关于混淆后如何知道正确代码

    bugly xff1a 腾讯自制 xff0c 是个4 xff0c 5句代码就能简单加入在线更新 捕获异常的好功能 xff0c 后台也是使用腾讯的 Android混淆 xff1a 启用一个配置 xff0c 把所有变量 类名改成 34 a 34
  • 大康Dacom Athlete+蓝牙耳机与手机配对上的原因及解决办法:

    1 原因 xff1a 蓝牙耳机没有进入配对模式 解决办法 xff1a 蓝牙耳机都有一个功能键 xff0c 长按听到开机提示音后不要松手 xff0c 继续长按 xff0c 直至听到进入配对模式提示音或者 滴 的提示音 xff0c 此时蓝红等交
  • android查看编译后的class文件

    其查看目录如下 然后在硬盘文件中打开 xff0c 可以看到详细的class文件列表
  • socket的shutdownInput和shutdownOutput

    虽然在大多数的时候可以直接使用Socket类或输入输出流的close方法关闭网络连接 xff0c 但有时我们只希望关闭OutputStream或InputStream xff0c 而在关闭输入输出流的同时 xff0c 并不关闭网络连接 这就
  • 使用广播接收器时,onReceive 会多次执行

    原因一 xff1a 没有在onDestory中调用解注册 unregisterReceiver 原因二 xff1a BroadcastReceiver变量所在的Activity或者Fragment被创建的多次 xff0c 形成多个对象
  • Android Studio自动生成单例代码

    AS中有可以自己设置代码模板 xff0c 使用起来简单方便 同样的 xff0c 单例类的代码样式统一 xff0c 除了类名外全部一致 所以使用模板更加方便 在设置中的Editor Live Template中新建模板 xff0c 然后把单例
  • android:excludeFromRecents 属性需要注意的小地方

    在 Android 系统中 xff0c 如果我们不想某个 Activity 出现在 Recent screens 中 xff0c 可以设置 lt activity gt 属性 android excludeFromRecents 为 tru
  • G.711编码原理

    目录 参考概述G 711原理总结 1 参考 1 wikipedia A law algorithm 2 github com quatanium foscam ios sdk 3 charybdis G711算法学习 2 概述 本文目的 x
  • RxJava 之Consumer和Action的使用

    在之前的RxJava中已经讲到创建观察者的代码如下 xff1a 创建观察者 Observer lt String gt observer 61 new Observer lt String gt 64 Override public voi
  • JAVA中枚举如何保证线程安全

    枚举类型到底是什么类呢 xff1f 是enum吗 xff1f 明显不是 xff0c enum就和class一样 xff0c 只是一个关键字 xff0c 他并不是一个类 xff0c 那么枚举是由什么类维护的呢 xff0c 首先写一个简单的枚举
  • Activity的启动流程

    总的流程图 xff1a 1 进程A与AMS的交互过程 此处以跨进程启动Activity分析一下源码流程 xff1a A调用startActivity时 xff0c 需要与AMS交互 xff0c 此时需要需要获取到AMS的代理对象Binder
  • Handler同步屏障

    一 消息机制之同步屏障 消息机制的同步屏障 xff0c 其实就是阻碍同步消息 xff0c 只让异步消息通过 而开启同步屏障的方法就是调用下面的方法 xff1a MessageQueue postSyncBarrier 源码如下 xff1a