理解VSync-4-应用申请与接收VSync(上)

1. 前言

忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。

-- 服装学院的IT男

本篇为 VSync 系列的第四篇,上一篇介绍了一个应用是如何把自己的链接添加到 "app" 这个 EventThread 下的 mDisplayEventConnections 中的。

本篇分析应用端是如何请求 VSync 并接收的。

本系列为之前学习 SurfaceFlinger 整理的一些笔记,现在分享出来,希望能帮助到有需要的同学。代码基于 Android 13,虽然很多逻辑与最新源码有所不同,但总体思路依然不变,不影响对 VSync 整体逻辑的理解。

VSync 系列目录:

理解VSync-1-软件VSync及节拍器

理解VSync-2-app,appsf sf注册回调

应用层请求 VSync 一般执行的是 Choreographer::postCallback ,比如在 ViewRootImpl::scheduleTraversals 方法下的这种调用:

csharp 复制代码
# ViewRootImpl.java
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

下面开始分析:

1. Java层请求 VSync

java 复制代码
# Choreographer
    public void postCallback(int callbackType, Runnable action, Object token) {
        // 没有延迟,则第4个参数为0
        postCallbackDelayed(callbackType, action, token, 0);
    }

    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    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, new Throwable());
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                // 无延迟走这
                scheduleFrameLocked(now);
            } else {
                // 需要延迟则通过Handler处理
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

最终进入 Choreographer::scheduleFrameLocked 方法。

csharp 复制代码
# Choreographer

    private final FrameDisplayEventReceiver mDisplayEventReceiver;

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            // 默认true
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    ......
                }
            } else {
                ......
            }
        }
    }

    private void scheduleVsyncLocked() {
        try {
            // 还加了trace
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
            // 主流程
            mDisplayEventReceiver.scheduleVsync();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            ......
        }
    }

流程来到 FrameDisplayEventReceiver, 它是 Choreographer 的内部类, 又是 DisplayEventReceiver 的子类,这里调用的 scheduleVsync 方法定义在父类中。

csharp 复制代码
# DisplayEventReceiver.java
    // 当前对象在 native 的指针
    private long mReceiverPtr;

    public void scheduleVsync() {
            if (mReceiverPtr == 0) {
                Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                        + "receiver has already been disposed.");
            } else {
                nativeScheduleVsync(mReceiverPtr);
            }
        }

1.1 堆栈

Java 层的调用比较简单,调用链如下:

arduino 复制代码
Choreographer::postCallback
    Choreographer::postCallbackDelayed
        Choreographer::postCallbackDelayedInternal
            Choreographer::scheduleFrameLocked
                Choreographer::scheduleVsyncLocked
                    DisplayEventReceiver::scheduleVsync
                        DisplayEventReceiver::nativeScheduleVsync   -- 进入native层

核心流程在后面的 Native 层。

2. native 层请求VSync

对应的 JNI 文件为 android_view_DisplayEventReceiver.cpp

scss 复制代码
# android_view_DisplayEventReceiver.cpp

static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
    // 根据指针拿到native层的 receiver 对象
    sp<NativeDisplayEventReceiver> receiver =
            reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
    // 申请Vsync
    status_t status = receiver->scheduleVsync();
    ......
}

之前说过,这里的 NativeDisplayEventReceiver 父类是 DisplayEventDispatcherscheduleVsync 方法也是定义在父类中的。

scss 复制代码
# DisplayEventDispatcher.cpp

status_t DisplayEventDispatcher::scheduleVsync() {
    if (!mWaitingForVsync) {
        ALOGV("dispatcher %p ~ Scheduling vsync.", this);
        ......
        // 申请
        status_t status = mReceiver.requestNextVsync();
        if (status) {
            ALOGW("Failed to request next vsync, status=%d", status);
            return status;
        }

        mWaitingForVsync = true;
        mLastScheduleVsyncTime = systemTime(SYSTEM_TIME_MONOTONIC);
    }
    return OK;
}

这里的 mReceiverDisplayEventReceiver 对象。 DisplayEventReceiver 是在构造 DisplayEventDispatcher 时创建的。

scss 复制代码
# DisplayEventReceiver.cpp

status_t DisplayEventReceiver::requestNextVsync() {
    if (mEventConnection != nullptr) {
        // 属性的APP 链接
        mEventConnection->requestNextVsync();
        return NO_ERROR;
    }
    return mInitError.has_value() ? mInitError.value() : NO_INIT;
}

这里的 mEventConnection 在应用连接 EventThread 时被创建,类型是 EventThreadConnection,它代表着一个应用

rust 复制代码
# EventThread.cpp
    // 无参
    binder::Status EventThreadConnection::requestNextVsync() {
        ATRACE_CALL();
        ATRACE_BEGIN(((impl::EventThread*)mEventThread)->toNameString());
        // 继续调用有参,传递自身链接
        mEventThread->requestNextVsync(this);
        ......
    }
    // 继续调用
    void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
        ......

        if (connection->vsyncRequest == VSyncRequest::None) {
            // 设置为 Single
            connection->vsyncRequest = VSyncRequest::Single;
            // 并唤醒等待的线程
            mCondition.notify_all();
        } else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
            // 设置为 Single,不需要唤醒
            connection->vsyncRequest = VSyncRequest::Single;
        }
    }

流程来到 EventThread。如果是首次请求,vsyncRequest 的状态肯定是 VSyncRequest::None, 所以会执行第一个 if 分支,将 vsyncRequest 设置为 VSyncRequest::Single 并唤醒等待的线程。 这里的 connection 参数就是上一篇中应用添加到 mDisplayEventConnections 集合中的连接,用来区分是哪个应用发起的 VSync 请求。

这部分的调用链为:

arduino 复制代码
DisplayEventDispatcher::scheduleVsync
    DisplayEventReceiver::requestNextVsync
        EventThreadConnection::requestNextVsync
            EventThread::requestNextVsync

这里出现了一个类:VSyncRequest,先来了解一下。

2.1 VSyncRequest

kotlin 复制代码
# EventThread.h
    enum class VSyncRequest {
        None = -2,
        // Single wakes up for the next two frames to avoid scheduler overhead
        Single = -1,
        // SingleSuppressCallback only wakes up for the next frame
        SingleSuppressCallback = 0,
        Periodic = 1,
        // Subsequent values are periods.
    };

    class EventThreadConnection : public gui::BnDisplayEventConnection {
        public:
            ......
        // 请求类型
        VSyncRequest vsyncRequest = VSyncRequest::None;

    }

EventThreadConnection 代表一个应用请求接收 VSYNC-app 的连接,内部的 vsyncRequest 变量表示该应用对 VSYNC-app 的请求类型。

VSyncRequest 是一个枚举类:

  • None: 默认状态,表示没有请求 VSync
  • Single:会为该应用连接提供接下来两帧的 VSync 信号。
  • SingleSuppressCallback:会为该应用连接提供接下来一帧的 VSync 信号。
  • Periodic: 表示请求周期性的 VSync 唤醒,适用于那些需要持续跟踪每一帧更新的场景。(暂时忽略)

应用 VSYNC-app 请求类型会按如下顺序转换: Single -> SingleSuppressCallback -> None。 从上面的代码看到,如果应用持续申请,会在 SingleSuppressCallback 状态时切换为 Single,所以完整的转换也可以理解成这样:

Single -> SingleSuppressCallback -> (Single -> SingleSuppressCallback) -> None

2.1.1 个人理解 VSyncRequest

SW-VSYNC 是按需产生的,对于单个应用也是如此。VSyncRequest 用于区分各个应用对 VSYNC-app 的请求类型。

这里举一个可能不太恰当的例子: 今年是龙年,很多龙宝宝。小兔崽子要喂饭,一天吃几顿,饿了就吃。 喂饭没有只喂一口的吧,第一口饭喂进去后,妈妈一般就会准备再舀一勺子准备为第二口。所以我理解成小孩子"啊"一张口的时候,妈妈最少要准备喂2次饭。(这个时候理解为VSyncRequest::Single 即:最少还要吃2口饭)。 小孩子也总有吃饱的时候,所以当小兔崽子吃完第一口后闭着嘴了,可是这个时候老妈勺子里已经有之前准备好的一勺子饭了,根据我的童年记忆,老妈肯定是要把这口饭塞进嘴里,然后这次喂饭才算结束的。 (所以我理解为这个时候的状态就是 VSyncRequest::SingleSuppressCallback 即:还要再吃一口饭) 喂饭结束后,小孩子吃饱了也不会再申请吃饭了,(这个时候就是 VSyncRequest::None 老妈也就可以去睡觉了(休眠状态))。

也就是说当小孩子主动张嘴"啊"申请要吃饭的时候,对老妈来说知道他饿了,则心里知道最少要喂2口(None - > Single),小孩子吃到第一口饭闭上嘴的时候,老妈会准备好下一口(Single - > SingleSuppressCallback)。

小孩吃完这口有两种可能:

    1. 没吃饱继续张口"啊",妈妈就知道还要准备2口(SingleSuppressCallback - > Single)
    1. 吃饱了,但是妈妈现在这勺子还得喂完才结束 (SingleSuppressCallback - > None)

这个例子可能不是很恰当,但是可以帮助建立一个初步的概念。实际上,应用请求 VSYNC-app 时的状态切换,还是以后面的代码分析为主。

2.2 EventThread::requestNextVsync 小结

回顾一下代码:

rust 复制代码
# EventThread.cpp

    void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
        ......

        if (connection->vsyncRequest == VSyncRequest::None) {
            // 设置为 Single
            connection->vsyncRequest = VSyncRequest::Single;
            // 并唤醒等待的线程
            mCondition.notify_all();
        } else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
            // 设置为 Single,不需要唤起
            connection->vsyncRequest = VSyncRequest::Single;
        }
    }

现在对 VSyncRequest 有了个大概的概念,可以发现这段代码无论执行哪个分支,都会将 vsyncRequest 设置为 VSyncRequest::Single。区别在于是否需要"唤醒线程"。

如果原来的状态是 VSyncRequest::None,说明没有请求 VSYNC-app,线程可能处于等待状态,需要唤醒。对应到例子里,就是小孩子饿了要吃饭,开始的时候妈妈在睡觉,需要先"唤醒"。而下面的情况则不需要唤醒。

3. VSYNC-app 核心处理

目前为止,我们只看到了应用发起请求,真正的逻辑还在后面。

这里唤醒了 "app" 线程,在构建 EventThread 的时候会创建这个线程,并执行 EventThread::threadMain 方法。

3.1 Vsync-app 线程处理状态

在看这个方法之前,先了解一下 EventThread 的几个执行状态:

c 复制代码
# EventThread.h
    enum class State {
        Idle, // 空闲状态: 表示事件循环当前无特定任务执行。进入等待
        Quit, // 退出状态: 事件循环准备结束
        // 合成VSYNC状态: 在此状态下,系统可能因为某些原因(如无实际显示器连接)
        // 而生成模拟的VSYNC信号,以维持程序逻辑的连续性。
        // 暂时可忽略
        SyntheticVSync,
        VSync, // 当前正等待或已接收到真实的显示器VSYNC信号
    };
    // 当前状态
    State mState GUARDED_BY(mMutex) = State::Idle;

有 4 个状态,正常情况下我们只需关注空闲状态 (Idle) 和请求状态 (VSync)。

3.2 EventThread::threadMain 方法

这个方法是 VSYNC-app 中的核心方法,代码量较多,逻辑也比较复杂,执行时的具体逻辑取决于当前的条件。 我目前还无法通过文字的形式让读者完全理解这部分逻辑。我自己在学习这部分内容时,花了一周时间,反复结合代码、日志、trace,并最终画出流程图,才算理解了这部分内容。 下面的代码中,我会尽量加上注释和解释,并附上整理好的流程图。

rust 复制代码
# EventThread.cpp

    void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
        // 定义一个要消费 Vsync 事件的集合
        DisplayEventConsumers consumers;

        // 只要状态不是退出,则一直循环执行(没任务的时候内部会等待)
        while (mState != State::Quit) {
            std::optional<DisplayEventReceiver::Event> event;
            
            // Determine next event to dispatch.
            // 1. 确认是否有 Vsync 事件,有的话则赋值给event
            // 如果有 Vsync 事件就会放在 mPendingEvents 
            if (!mPendingEvents.empty()) {
                event = mPendingEvents.front();
                mPendingEvents.pop_front();
                // 这部分可忽略,正常情况不影响主流程
                switch (event->header.type) {
                    ......
                }
            }
            // 是否有应用请求了 vsync
            bool vsyncRequested = false;

            // 2. 开始遍历mDisplayEventConnections的各个链接
            // Find connections that should consume this event.
            auto it = mDisplayEventConnections.begin();
            while (it != mDisplayEventConnections.end()) {
                if (const auto connection = it->promote()) {
                    // 只要应用链接的集合里有一个应用申请了, syncRequested 就为true
                    vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
                    // 是否要消耗event
                    if (event && shouldConsumeEvent(*event, connection)) {
                        // 消耗则添加到 consumers 集合
                        consumers.push_back(connection); 
                    }

                    ++it;
                } else {
                    it = mDisplayEventConnections.erase(it);
                }
            }
            
            if (!consumers.empty()) {
                // 3. 如果有消费事件的应用,则派发时间到应用端
                dispatchEvent(*event, consumers);
                // 置空
                consumers.clear();
            }

            {
                // 4. 状态处理
                // 定义当前线程的下一个状态
                State nextState;
                if (mVSyncState && vsyncRequested) {
                    // 4.1 有应用申请的话则 nextState = State::VSync
                    nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
                } else {
                    ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected");
                    // 4.2 没有人申请,则 nextState = SIdle ,下面就会进入等待
                    nextState = State::Idle;
                }
    
                // 第一次执行的时候,mState = Idle ,nextState = VSync
                // 后面执行 mState 被设置为 VSync
                if (mState != nextState) {
                    if (mState == State::VSync) {
                        // 5.1 关闭 Vsync-app
                        mVSyncSource->setVSyncEnabled(false);
                    } else if (nextState == State::VSync) {
                        // 5.2 打开 Vsync-app
                        mVSyncSource->setVSyncEnabled(true);
                    }
                    // 状态同步
                    mState = nextState;
                }
    
                if (event) {
                    // 6. 有事件则重头开始执行
                    continue;
                }

                // Wait for event or client registration/request.
                if (mState == State::Idle) {
                    // 当状态为Idle时,无期限等待
                    mCondition.wait(lock);
                } else {
                    // Generate a fake VSYNC after a long timeout in case the driver stalls. When the
                    // display is off, keep feeding clients at 60 Hz.
                    // 根据当前状态设置超时时间:若为合成VSYNC状态则设置较短超时(16毫秒),其他情况设置为1000毫秒
                    const std::chrono::nanoseconds timeout =
                            mState == State::SyntheticVSync ? 16ms : 1000ms;
                    if (mCondition.wait_for(lock, timeout) == std::cv_status::timeout) {
                        ......// 超时处理,暂时忽略
                        
                    }
                    ......// 超时处理,暂时忽略
                }
            }
        }
    }
    1. 检查是否有 VSync 事件需要处理(如果 VsyncDispatch 产生了 VSync,会放到 mPendingEvents 集合中)。
    1. 遍历 mDisplayEventConnections 集合,其中存放着各个应用的连接。检查哪个应用申请了 VSync,并将其添加到 consumers 集合。
    1. 如果 consumers 集合中有元素,则将 VSync 事件派发给连接对应的应用**。
    1. 确定下一个状态。如果有应用请求了 VSync,则 nextStateState::VSync;如果没有,则为 State::Idle
    1. 状态切换处理。如果当前状态和下一个状态不相等,会进入这里。通常有两种情况:
    • 5.1 当前状态 mStateState::VSync,而 nextStateState::Idle,表示线程要进入等待状态。此时需要关闭 VSync-app 信号的派发,执行 mVSyncSource->setVSyncEnabled(false)
    • 5.2 当前状态 mStateState::Idle,而 nextStateState::VSync,表示应用当前处于空闲状态,现在被唤醒,需要申请 VSync。此时需要打开 VSync-app 信号的派发,执行 mVSyncSource->setVSyncEnabled(true)
    1. 事件处理。如果有事件,则从头开始执行循环;如果没有事件,则进入等待逻辑。
    • 首次执行时,肯定没有 event,此时 mStateState::VSync,所以会进入 7.2,进入超时等待。
    • 如果有事件,事件会被分发,然后重新回到循环开始的地方,获取 mPendingEvents 中的下一个元素(如果有的话)。
    1. 等待
    • 7.1 无限期等待 (当 mState == State::Idle 时)。
    • 7.2 超时等待 (其他情况)。

对应的流程图如下:

要完全理解这个方法,还需要结合它调用的其他逻辑。

参考

<千里马学框架-SurfaceFlinger>

blog.csdn.net/tkwxty/arti...

mp.weixin.qq.com/s/gAcBEqjYA...

source.android.com/docs/core/g...

www.jianshu.com/p/5e9c558d1...

相关推荐
恋猫de小郭1 小时前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
匹马夕阳6 小时前
(十八)安卓开发中的后端接口调用详讲解
android
Pigwantofly8 小时前
鸿蒙ArkTS实战:从零打造智能表达式计算器(附状态管理+路由传参核心实现)
android·华为·harmonyos
Gracker9 小时前
Android Weekly #202514
android
binderIPC9 小时前
Android之JNI详解
android
林志辉linzh9 小时前
安卓AssetManager【一】- 资源的查找过程
android·resources·assetmanger·安卓资源管理·aapt·androidfw·assetmanger2
_一条咸鱼_11 小时前
大厂Android面试秘籍:Activity 权限管理模块(七)
android·面试·android jetpack
lynn8570_blog11 小时前
通过uri获取文件路径手机适配
android·kotlin·android studio
JKIT沐枫12 小时前
PHP如何能获取网站上返回的数组指南
android·大数据
懋学的前端攻城狮13 小时前
Android一些基础-06-一个列表的基本写法
android