理解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...

相关推荐
小蜜蜂嗡嗡1 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi001 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil3 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你3 小时前
Android View的绘制原理详解
android
移动开发者1号6 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号6 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best11 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk11 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭16 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
aqi0016 小时前
FFmpeg开发笔记(七十七)Android的开源音视频剪辑框架RxFFmpeg
android·ffmpeg·音视频·流媒体