Android 13 VSYNC重学习
Android 13 VSYNC重学习
引言
学无止境,一个字干就完事!
源码参考基于Android 13 aosp!
一. Android VSync模块开胃菜
在开始正式的分析之前,我们先简单对Android的Vsync模块简单介绍下,如下图所示,其中:
- HW_VSync是由屏幕产生的脉冲信号,用于控制屏幕的刷新
- VSync-app和VSync-sf统称为软件VSync,它们是由SurfaceFlinger通过模拟硬件VSync而产生的VSync信号量,再分发给app和sf用来控制它们的合成节奏


二. Android VSync小结
这里有几点需要补充:
-
VSync-sf是没有对应的EventThread和DispSyncSource
-
VSync-app和VSync-appSf各自都有对应的EventThread和DispSyncSource
-
VSync-sf和VSync-app以及Sync-appSf通过Scheduler的成员mVsyncSchedule指向的VSyncDispatchTimerQueue实例对象关联
Android下VSync设计,牵涉的核心关系图如下:

2.1 VSync信号的分类
VSync信号分为两种:硬件VSync信号HW-VSync和软件VSync信号SW-VSync。SW-VSync信号由SW-VSync模型产生。HW-VSync信号负责对SW-VSync模型进行校准。
2.2 HW-Vsync信号的开启
三种场景下会开启硬件VSync信号HW-VSync会对软件VSync信号SW-VSync进行校准
-
SurfaceFlinger初始化。
-
连续两次请求VSync-app信号的时间间隔超过750ms。
-
SurfaceFlinger合成后,添加FenceTime到VSyncTracker中导致模型计算误差过大。
2.3 SW-VSync模型与计算
谷歌官方采用一元线性回归分析预测法(最小二乘法),通过采样的HW-VSync信号样本(屏幕刷新率),计算对应的SW-VSync信号周期。最终得到一条y=bx+a的拟合曲线。其中,b称为回归系数,a称为截距。SW-VSync模型就是这这条曲线的回归系数和截距。
2.4 SW-VSync信号的分类
SW-VSync信号也分为两种,VSync-sf信号和Vsync-app信号。这两个信号,各司其职:
- VSync-sf信号用于控制SurfaceFlinger的Layer合成
- VSync-app信号用于控制App渲染UI
VSync-sf信号和VSync-app信号是在SW-VSync信号的基础上通过叠加不同的偏移量产生,这些偏移量被称为VSync相位偏移。由于偏移量不同VSync-sf信号和VSync-app信号的回调时机也不同。
三. VSync-sf的申请和分发
VSync-sf用于控制SurfaceFlinger合成和渲染一帧图像。当SurfaceFlinger上帧时(BufferQueue中有新的GraphicBuffer),SurfaceFlinger会触发MessageQueue的scheduleFrame方法。接下来我们看下,VSync-sf是如何完成从申请到分发的流程。
3.1 VSync-sf的申请
SurfaceFlinger::scheduleCommit(...)//请求上帧mScheduler->scheduleFrame()//MessageQueue.cppmVsync.registration->schedule()//这里的registration实现是VSyncCallbackRegistration,定义在Scheduler/VSyncDispatchTimerQueue.cppmDispatch.get().schedule()//这里的mDispatch指向VSyncDispatchTimerQueue对象/*** @brief * * @param token * @param scheduleTiming * @return ScheduleResult * 1)根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。* 2)遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间。* 3)对发射时间进行定时,等待下一次VSync信号的发送*/
ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,ScheduleTiming scheduleTiming) {...//根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。auto it = mCallbacks.find(token);auto& callback = it->second;//遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间result = callback->schedule(scheduleTiming, mTracker, now);//对发射时间进行定时,等待下一次VSync-sf信号的发送rearmTimerSkippingUpdateFor(now, it);VSyncDispatchTimerQueue::setTimer()void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t /*now*/) {mIntendedWakeupTime = targetTime;mTimeKeeper->alarmAt(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),mIntendedWakeupTime);mLastTimerSchedule = mTimeKeeper->now();
} /*** @brief * 1)遍历CallbackMap找到达到唤醒时间的VSyncDispatchTimerQueueEntry,并封装成Invocation,加入Invocation列表。* 2)遍历Invocation列表,通过Invocation获取VSyncDispatchTimerQueueEntry,并调用VSyncDispatchTimerQueueEntry的callback方法分发VSync信号。*///Scheduler/VSyncDispatchTimerQueue.cpp
void VSyncDispatchTimerQueue::timerCallback() {struct Invocation {std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;nsecs_t vsyncTimestamp;nsecs_t wakeupTimestamp;nsecs_t deadlineTimestamp;};std::vector<Invocation> invocations;{std::lock_guard lock(mMutex);auto const now = mTimeKeeper->now();mLastTimerCallback = now;for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {auto& callback = it->second;auto const wakeupTime = callback->wakeupTime();if (!wakeupTime) {continue;}auto const readyTime = callback->readyTime();auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {callback->executing();invocations.emplace_back(Invocation{callback, *callback->lastExecutedVsyncTarget(),*wakeupTime, *readyTime});}}mIntendedWakeupTime = kInvalidTime;rearmTimer(mTimeKeeper->now());}for (auto const& invocation : invocations) {invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,invocation.deadlineTimestamp);}
}}
3.2 VSync-sf的分发
那么VSync-df的callback是怎么注册到VSyncDispatchTimerQueue的呢,这个我们看下:
SurfaceFlinger::initScheduler(...)mScheduler->initVsync(...)//实现在Scheduler/MessageQueue.cpp中mVsync.registration = std::make_unique<scheduler::VSyncCallbackRegistration>(dispatch,std::bind(&MessageQueue::vsyncCallback, this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3),"sf");//这里的dispatch指向VSyncDispatchTimerQueue//Scheduler/VSyncDispatchTimerQueue.cppVSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,VSyncDispatch::Callback callback,std::string callbackName): mDispatch(dispatch),mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),mValidToken(true) {} VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(Callback callback, std::string callbackName) {std::lock_guard lock(mMutex);return CallbackToken{//最终注册到了mCallbacks中mCallbacks.emplace(++mCallbackToken,std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),std::move(callback),mMinVsyncDistance)).first->first};
}
所以最后VSync-sf的分发会调用到MessageQueue::vsyncCallback中,我们看下它的实现:
//Scheduler/MessageQueue.cpp
MessageQueue::vsyncCallback(...)mHandler->dispatchFrame(vsyncId, vsyncTime)mQueue.mLooper->sendMessage(this, Message())//Handle的handleMessage接收前面发过来的消息
void MessageQueue::Handler::handleMessage(const Message&) {mFramePending.store(false);const nsecs_t frameTime = systemTime();auto& compositor = mQueue.mCompositor;//这里的compositor实现类是SurfaceFlingerif (!compositor.commit(frameTime, mVsyncId, mExpectedVsyncTime)) {return;}compositor.composite(frameTime, mVsyncId);compositor.sample();
}
四. VSync-app的申请和分发
在开始后续的章节编写前,我们先重点申明下:
VSync-app用于控制App的UI渲染
VSync-app用于控制App的UI渲染
VSync-app用于控制App的UI渲染
4.1 VSync-app的申请
当Choreographer通过FrameDisplayEventReceiver调用scheduleVsync方法时,会触发VSync-app信号的申请。在FrameDisplayEventReceiver的scheduleVsync方法中,会调用nativeScheduleVsync方法。

FrameDisplayEventReceiver的nativeScheduleVsync方法对应的native实现为android_view_DisplayEventReceiver的nativeScheduleVsync函数。
在nativeScheduleVsync函数中,主要做了两件事:
-
获取native层的DisplayEventDispatcher。
-
调用DisplayEventDispatcher的scheduleVsync方法,请求VSync信号。

在DisplayEventDispatcher的scheduleVsync方法中,会调用DisplayEventReceiver的requestNextVsync方法。

在DisplayEventReceiver的requestNextVsync方法中,会调用IDisplayEventConnection的requestNextVsync方法。

IDisplayEventConnection是一个Binder类,对应bn端的实现类为BnDisplayEventConnection。而EventThreadConnection继承自BnDisplayEventConnection,因此实际调用的是EventThreadConnection的requestNextVsync方法。

在EventThreadConnection的requestNextVsync方法中,会调用EventThread的requestNextVsync方法。

在EventThread的requestNextVsync方法中,主要做了三件事:
-
开启硬件VSync信号对软件VSync信号进行校准。
-
标记EventThreadConnection的vsyncRequest,为后续信号分发做准备。
-
唤起EventThread对应的线程继续执行VSync信号的分发。

//Scheduler/EventThread.cpp
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {if (connection->resyncCallback) {/*** @brief * 调用到Scheduler::resync* 开启硬件Vsync信号对软件Vsync信号进行校准*/connection->resyncCallback();}std::lock_guard<std::mutex> lock(mMutex);if (connection->vsyncRequest == VSyncRequest::None) {connection->vsyncRequest = VSyncRequest::Single;mCondition.notify_all();//唤起EventThread中的线程} else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {connection->vsyncRequest = VSyncRequest::Single;}
}
在EventThread的threadMain中,会通过VSyncCallbackRegistration请求或取消VSync信号。
如果是请求VSync信号,会调用VSyncCallbackRegistration的schedule方法。在VSyncCallbackRegistration的schedule方法,会调用VSyncDispatch的schedule方法。

void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {if (mState != nextState) {if (mState == State::VSync) {mVSyncSource->setVSyncEnabled(false);} else if (nextState == State::VSync) {mVSyncSource->setVSyncEnabled(true);}mState = nextState;}}
之后的流程与VSync-sf信号的申请流程相同。在VSyncDispatchTimerQueue的schedule方法中,会调用scheduleLocked方法。
在VSyncDispatchTimerQueue的scheduleLocked方法中,主要做了三件事:
-
根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。
-
遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间。
-
对发射时间进行定时,等待下一次VSync信号的发送。

4.2 VSync-app的分发
当定时时间到达时,TimerKeeper会回调VSyncDispatchTimerQueue的timerCallback方法。
在VSyncDispatchTimerQueue的timerCallback方法方法中,主要做了两件事:
-
遍历CallbackMap找到达到唤醒时间的VSyncDispatchTimerQueueEntry,并封装成Invocation,加入Invocation列表。
-
遍历Invocation列表,通过Invocation获取VSyncDispatchTimerQueueEntry,并调用VSyncDispatchTimerQueueEntry的callback方法分发VSync信号。

在VSyncDispatchTimerQueueEntry的callback方法中,会调用类型为CallbackRepeater::callbackk,然后在该方法中接着调用mCallback(vsyncTime, wakeupTime, readyTime)方法,而这里的mCallback(指向DispSyncSource::onVsyncCallback,最后回调EventThread的onVSyncEvent方法。
对于上述的分发流程是不是还有点懵逼,我们反过来看看VSync-app分发的注册,其核心是DispSyncSource和EventThread以及VSyncDispatchTimerQueue的各种回调callback流程:
//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(Callback callback, std::string callbackName) {std::lock_guard lock(mMutex);return CallbackToken{mCallbacks.emplace(++mCallbackToken,std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),std::move(callback),mMinVsyncDistance)).first->first};
}//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,VSyncDispatch::Callback callback,std::string callbackName): mDispatch(dispatch),mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),mValidToken(true) {}//Scheduler/DispSyncSource.cpp
class CallbackRepeater {
public:CallbackRepeater(VSyncDispatch& dispatch, VSyncDispatch::Callback cb, const char* name,std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,std::chrono::nanoseconds notBefore): mName(name),mCallback(cb),//VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);mRegistration(dispatch,std::bind(&CallbackRepeater::callback, this, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3),mName),mStarted(false),mWorkDuration(workDuration),mReadyDuration(readyDuration),mLastCallTime(notBefore) {}~CallbackRepeater() {std::lock_guard lock(mMutex);mRegistration.cancel();}void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) {std::lock_guard lock(mMutex);mStarted = true;mWorkDuration = workDuration;mReadyDuration = readyDuration;auto const scheduleResult = mRegistration.schedule({.workDuration = mWorkDuration.count(),.readyDuration = mReadyDuration.count(),.earliestVsync = mLastCallTime.count()});LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback");}void stop() {std::lock_guard lock(mMutex);LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped");mStarted = false;mRegistration.cancel();}void dump(std::string& result) const {std::lock_guard lock(mMutex);const auto relativeLastCallTime =mLastCallTime - std::chrono::steady_clock::now().time_since_epoch();StringAppendF(&result, "\t%s: ", mName.c_str());StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",mWorkDuration.count() / 1e6f, mReadyDuration.count() / 1e6f);StringAppendF(&result, "%.2fms relative to now (%s)\n", relativeLastCallTime.count() / 1e6f,mStarted ? "running" : "stopped");}private:void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {{std::lock_guard lock(mMutex);mLastCallTime = std::chrono::nanoseconds(vsyncTime);}mCallback(vsyncTime, wakeupTime, readyTime);{std::lock_guard lock(mMutex);if (!mStarted) {return;}auto const scheduleResult =mRegistration.schedule({.workDuration = mWorkDuration.count(),.readyDuration = mReadyDuration.count(),.earliestVsync = vsyncTime});LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback");}}const std::string mName;scheduler::VSyncDispatch::Callback mCallback;mutable std::mutex mMutex;VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);bool mStarted GUARDED_BY(mMutex) = false;std::chrono::nanoseconds mWorkDuration GUARDED_BY(mMutex) = 0ns;std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex) = 0ns;std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns;
};mAppConnectionHandle =mScheduler->createConnection("app" .....)Scheduler::createConnection()auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration)return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),mVsyncSchedule->getTracker(), workDuration,readyDuration, traceVsync, name);//std::unique_ptr<CallbackRepeater> mCallbackRepeater;mCallbackRepeater =std::make_unique<CallbackRepeater>(vSyncDispatch,std::bind(&DispSyncSource::onVsyncCallback, this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3),name, workDuration, readyDuration,std::chrono::steady_clock::now().time_since_epoch()); mVSyncSource->setCallback(this);//为DispVsyncSource设置回调void DispSyncSource::setCallback(VSyncSource::Callback* callback) {std::lock_guard lock(mCallbackMutex);mCallback = callback;} //最终整理出来的Vsync-app分发流程为,各种弯弯绕绕:VSyncDispatchTimerQueue::timerCallback()//Scheduler/VSyncDispatchTimerQueue.cppinvocation.callback->callback(...)//这里的callback指向VSyncDispatchTimerQueueEntry::callback,Scheduler/VSyncDispatchTimerQueue.cppmCallback(vsyncTimestamp, wakeupTimestamp, deadlineTimestamp)//这里的 mCallback指向CallbackRepeater::callback,实现在Scheduler/DispSyncSource.cpp mCallback(vsyncTime, wakeupTime, readyTime)//这里的callback指向DispSyncSource::onVsyncCallback。是现在Scheduler/DispSyncSource.cppcallback = mCallback;callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime})//这里的callback指向EventThread::onVSyncEvent
在EventThread的onVSyncEvent方法中,主要做了三件事:
-
调用makeVSync函数,创建Event。
-
将Event加入到vector<DisplayEventReceiver::Event> 中。
-
唤醒等待线程,执行threadMain方法。

void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {std::lock_guard<std::mutex> lock(mMutex);LOG_FATAL_IF(!mVSyncState);//包装为DisplayEventReceiver::Event对象,存入mPendingEvents尾部mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,vsyncData.expectedPresentationTime,vsyncData.deadlineTimestamp));//唤醒线程mCondition.notify_all();
}
我们接下来看EventThread是如何处理分发事件的:
//Scheduler/EventThread.cpp
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {DisplayEventConsumers consumers;while (mState != State::Quit) {std::optional<DisplayEventReceiver::Event> event;// Determine next event to dispatch.if (!mPendingEvents.empty()) {event = mPendingEvents.front();mPendingEvents.pop_front(); ...}// Find connections that should consume this event.auto it = mDisplayEventConnections.begin();while (it != mDisplayEventConnections.end()) {if (const auto connection = it->promote()) {vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;//用来在任务的循环执行中保存当前Vsync信号的消费者if (event && shouldConsumeEvent(*event, connection)) {consumers.push_back(connection);//这里的consumers就是待分发的目标}++it;} else {it = mDisplayEventConnections.erase(it);}} /*** @brief * 在该方法中,会循环分发信号,主要做了五件事情* 1) 从Vsync信息队列中获取消息* 2)收集监听Vsync信号的EventThreadConnection,并加入到consumers中* 3) 调用dispatchEvent方法来分发Vsync信号* 4)计算当前状态,根据状态请求或取消下一次VSync信号* 5)如果没有Vsync信号需要分发,线程进入等待状态*/if (!consumers.empty()) {dispatchEvent(*event, consumers);consumer->postEvent(copy)DisplayEventReceiver::sendEvents(...)consumers.clear();}
最终VSync-app分发的事件会被Choreographer模块接收,开始安排应用相关的渲染UI逻辑!
Andoid SurfaceFlinger(二) VSYNC的开始,连续,结束
VSYNC研究-最后的窗户纸
Android 12(S) 图像显示系统 - SurfaceFlinger之VSync-上篇(十六)
Android 12(S) 图像显示系统 - SurfaceFlinger 之 VSync - 中篇(十七)
深度详解 Android S(12.0)屏幕刷新机制之 Choreographer
View绘制流程3-Vsync信号是如何发送和接受的
Android R Vsync相关梳理
显示框架之深入Vsync原理
App/Sf的Vsync部分源码流程结合perfetto/systrace分析
Android-View绘制原理(02)-VSync原理之SurfaceFlinger篇
一文搞定Android VSync来龙机制去脉
VSync信号系统与SurfaceFlinger
SurfaceFlinger-Vsync信号
Android VSync事件分发过程源码分析
相关文章:
Android 13 VSYNC重学习
Android 13 VSYNC重学习 引言 学无止境,一个字干就完事! 源码参考基于Android 13 aosp! 一. Android VSync模块开胃菜 在开始正式的分析之前,我们先简单对Android的Vsync模块简单介绍下,如下图所示,其中: HW_VSync是…...
std::move和左值右值
引用:windows程序员面试指南 std::move std::move 是 C 标准库中的一个函数模板,用于将一个左值(左值引用)转化为右值引用,从而实现移动语义。 移动语义是一种可以将资源(如内存)从一个对象转…...
QT学习备份
2023年1月2日09:00:32 1.信号/槽编辑器 发送者:控件 信号:是控件发出的信号 接受者:包含控件的容器 槽:程序上用slot标识的方法 2.Q_OBJECT宏 只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用…...
【wiki知识库】03.前后端的初步交互(展现所有的电子书)
📝个人主页:哈__ 期待您的关注 目录 一、🔥今日目标 二、📂前端配置文件补充 三、🌏前端Vue的改造 四、💡总结 一、🔥今日目标 在上一篇文章当中,我已带大家把后端的一些基本工…...
AOP——学习
AOP(面向切面编程)是Spring框架的重要特性之一,用于分离关注点并处理横切关注点,如日志记录、安全性和事务管理。在面试中,AOP相关的问题通常会涉及基本概念、应用场景、实际使用、以及与其他编程范式的比较。以下是一…...
Linux静态库、共享动态库介绍、制作及使用
参考学习:Linux下的各种文件 、动态库基本原理和使用方法,-fPIC选项的来龙去脉 、Linux静态库和动态库分析 文章写作参考:Linux共享库、静态库、动态库详解 - sunsky303 - 博客园 (cnblogs.com) 一.Linux共享库、静态库、动态库详解 使用G…...
【Paddle】稀疏计算的使用指南 稀疏ResNet的学习心得 (2) + Paddle3D应用实例稀疏 ResNet代码解读 (1.6w字超详细)
【Paddle】稀疏计算的使用指南 & 稀疏ResNet的学习心得 Paddle3D应用实例稀疏 ResNet代码解读 写在最前面一、稀疏格式简介1. COO(Coordinate Format)2. CSR(Compressed Sparse Row Format) 二、Paddle稀疏张量支持1. 创建 C…...
Linux系统维护
1. 批量安装部署 2. 初始化配置 3. 禁用Selinux 永久更改 SELinux 配置: 编辑 SELinux 配置文件:使用文本编辑器打开 /etc/selinux/config 文件: 在配置文件中,找到 SELINUX… 的行。将其值更改为以下选项之一: e…...
经典文献阅读之--RenderOcc(使用2D标签训练多视图3D Occupancy模型)
0. 简介 3D占据预测在机器人感知和自动驾驶领域具有重要的潜力,它将3D场景量化为带有语义标签的网格单元。最近的研究主要利用3D体素空间中的完整占据标签进行监督。然而,昂贵的注释过程和有时模糊的标签严重限制了3D占据模型的可用性和可扩展性。为了解…...
蓝牙设备中的UUID
文章目录 一、Device UUID二、Service UUID 一、Device UUID Device UUID也可以被称作为DeviceID。 Android 设备上扫描获取到的 deviceId 为外围设备的 MAC 地址,相对固定。iOS 设备上扫描获取到的 deviceId 是系统根据外围设备 MAC 地址及发现设备的时间生成的 …...
网络之再谈体系结构
大家都知道的是网络的体系结构,现代软件常用的体系结构无非是TCP/IP协议栈,OSI因为实现复杂并且效率没有TCP/IP协议栈好,所以不用OSI,但是,最近在复习网络知识的时候,发现了一些奇怪的地方,那就…...
在flutter initState 方法,触发 setState导致循环执行
在Flutter中,如果你在initState中调用了一个方法,并且这个方法可能导致状态更新,这可能会引起无限循环,因为每次状态更新都会再次调用initState。 为了避免这种情况,你应该检查调用的方法是否会导致状态更新ÿ…...
JavaScript字符串方法
charAt() 方法:返回指定索引处的字符。 示例: let str "Hello World"; console.log(str.charAt(0)); // Output: "H" console.log(str.charAt(6)); // Output: "W"charCodeAt() 方法:返回指定索引处字符的 Un…...
YD/T 2698-2014 《电信网和互联网安全防护基线配置要求及检测要求 网络设备》标准介绍
编写背景 随着互联网技术的飞速发展,网络设备的安全问题日益凸显。为了加强电信网和互联网的安全防护,保障网络环境的稳定和用户信息的安全,YD/T 2698-2014标准应运而生。此标准旨在为网络设备提供一套基线配置要求,以及相应的安…...
QCC30XX如何查找本地地址码
查找本地地址段/********************************************************************** Copyright (c) 2016 - 2017 Qualcomm Technologies International, Ltd. FILE NAME sink_private_data.c DESCRIPTION This module works as a container for all private and common…...
基于 DCT 的图像滤波
需求分析 对于图像去噪这一需求,我们可以通过DCT(离散余弦变换)算法来实现。DCT是一种基于频域的变换技术,可以将图像从空间域转换为频域,然后通过滤波等处理方式进行去噪。 针对这一需求,我们需要进行以下…...
spdlog日志库源码:自定义异常类spdlog_ex
自定义异常类spdlog_ex 标准库异常类(std::exception)系列,能满足大多数使用异常的场景,但对系统调用异常及错误信息缺乏支持。spdlog通过继承std::exception,扩展对系统调用的支持,实现自定义异常类spdlo…...
3.每日LeetCode-数组类,爬楼梯(Go,Java,Python)
目录 题目 解法 Go Java Python 代码地址:leetcode: 每日leetcode刷题 题目 题号70. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入ÿ…...
单节点11.2.0.3参数文件恢复到RAC11.2.0.4启动失败
问题描述 通过pfile生成spfile失败,提示DATA磁盘不存在 SQL> create spfileDATA/DXJ/spfiledxj.ora from pfile/home/oracle/initdxj20240529.ora; create spfileDATA/DXJ/spfiledxj.ora from pfile/home/oracle/initdxj20240529.ora * ERROR at line 1: ORA-1…...
Windows电脑高颜值桌面便利贴,便签怎么设置
在这个看颜值的时代,我们不仅在衣着打扮上追求时尚与美观,就连电脑桌面也不愿放过。一张唯美的壁纸,几款别致的小工具,总能让我们的工作空间焕发出不一样的光彩。如果你也热衷于打造高颜值的电脑桌面,那么,…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
