30分钟彻底了解Flutter整个渲染流程(超详细)

news/2024/5/20 21:04:26

30分钟彻底了解Flutter整个渲染流程[超详细]

  • 从运行第一行代码出发
    • WidgetsFlutterBinding初始化了一堆娃
  • 三个中流砥柱
    • SchedulerBinding
    • RendererBinding
    • WidgetsBinding
  • 申请Vsync流程
  • 下发Vsync
  • 承接Vsync

从运行第一行代码出发

void main() {runApp(const MyApp());
}void runApp(Widget app) {WidgetsFlutterBinding.ensureInitialized()..scheduleAttachRootWidget(app)..scheduleWarmUpFrame();
}

WidgetsFlutterBinding.ensureInitialized作用是初始化WidgetsFlutterBinding对象。

//...WidgetsFlutterBinding.ensureInitialized()
//..
static WidgetsBinding ensureInitialized() {if (WidgetsBinding.instance == null)WidgetsFlutterBinding();return WidgetsBinding.instance!;}

WidgetsFlutterBinding初始化了一堆娃

WidgetsFlutterBinding里面继承了BindingBase。他会初始化BindingBase的构造方法。并且
并且with了很多类,而这些类都继承了BindingBase.也就间接对这些类进行了初始化工作

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
//...
}

在BindingBase构造方法中,它调用了initInstances和initServiceExtensions。但是这里面只是做了一些debug模式一些初始化工作。由于WidgetsFlutterBinding所有with的类都是BindingBase的子类(如下图),这些子类如SchedulerBinding,RendererBinding,WidgetsBinding等他们都各自实现了initInstances, initServiceExtensions. 所以BindingBase构造函数本质是为了调用WidgetsFlutterBinding所有with的类里面的initInstances,initServiceExtensions
在这里插入图片描述

abstract class BindingBase {BindingBase() {//...initInstances();//...initServiceExtensions();}void initInstances() {//...//做debug模式的初始化活//...}void initServiceExtensions() {//...//做debug模式的初始化活//...}}

三个中流砥柱

在这我们重点关注SchedulerBinding, RendererBinding, WidgetsBinding

SchedulerBinding

SchedulerBinding在这个渲染环节中主要负责请求Vsync和接收Vsync回调的工作,并且回调会消费每一帧之前的事件任务,然后进行布局绘制。后面会介绍他是怎么被执行的。(不了解什么是Vsync看看我这篇文章2分钟带你了解什么是Vsync)
它的initInstances里面只是做了SchedulerBinding的instance单例初始化.

mixin SchedulerBinding on BindingBase {static SchedulerBinding? get instance => _instance;static SchedulerBinding? _instance;void initInstances() {super.initInstances();_instance = this;//..}void initServiceExtensions() {//...//做debug模式的初始化活//...}//申请vsyncvoid scheduleFrame() {//...ensureFrameCallbacksRegistered();window.scheduleFrame();}//下面的CALLBACK,每一帧都会在UI绘制之前执行void ensureFrameCallbacksRegistered() {//执行里面scheduleFrameCallback注册的回调,动画之类的事件window.onBeginFrame ??= _handleBeginFrame;//执行addPersistentFrameCallback和addPostFrameCallback中注册的回调window.onDrawFrame ??= _handleDrawFrame;}void _handleBeginFrame(Duration rawTimeStamp) {//...handleBeginFrame(rawTimeStamp);}void _handleDrawFrame() {//...handleDrawFrame();}

RendererBinding

RendererBinding主要是负责管理渲染的职能
在RendererBinding的initInstances中,他同样会初始化RendererBinding单例instance.并且初始化PipelineOwner和RenderView. 其中PipelineOwner负责管理绘制工作,RenderView是整个App的渲染树

static RendererBinding? get instance => _instance;
static RendererBinding? _instance;

void initInstances() {super.initInstances();_instance = this;_pipelineOwner = PipelineOwner(onNeedVisualUpdate: ensureVisualUpdate,onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,);	//...initRenderView();//...addPersistentFrameCallback(_handlePersistentFrameCallback);
}void initRenderView() {//..renderView = RenderView(configuration: createViewConfiguration(), window: window);renderView.prepareInitialFrame();}
void initServiceExtensions() {//...//做debug模式的初始化活//...
}

最后,他会调用addPersistentFrameCallback绑定绘制页面的callback.
也就是这个_handlePersistentFrameCallback被触发时候会调用drawFrame, drawFrame这个方法是对所有被标记需要刷新的页面进行布局和绘制。 这里不展开具体绘制过程。

void _handlePersistentFrameCallback(Duration timeStamp) {//drawFrame();//...
}//开始绘制void drawFrame() {//进行布局pipelineOwner.flushLayout();//进行绘制pipelineOwner.flushPaint();//...}

在这里插入图片描述

WidgetsBinding

WidgetsBinding主要用来挂载BuildOwner管理Element这棵树
他的initInstances里面会同样会初始化他的单例方法,并且会初始化携带BuildOwner。

  static WidgetsBinding? get instance => _instance;static WidgetsBinding? _instance;void initInstances() {super.initInstances();_instance = this;//..._buildOwner = BuildOwner();buildOwner!.onBuildScheduled = _handleBuildScheduled;//..}void initServiceExtensions() {//...//做debug模式的初始化活//...}

BuildOwner是整棵Element的树的管理类。每个Element都会有这个BuildOwner的唯一实例。BuildOwner在WidgetsBinding绑定了onBuildScheduled方法,也就是_handleBuildScheduled, 这个方法会调用ensureVisualUpdate,然后调用SchedulerBinding的 scheduleFrame方法,从而可以申请Vsync信号,从而获取下一帧的绘制。

_handleBuildScheduled(){//....ensureVisualUpdate();
}
void ensureVisualUpdate() {switch (schedulerPhase) {case SchedulerPhase.idle:case SchedulerPhase.postFrameCallbacks:scheduleFrame();return;case SchedulerPhase.transientCallbacks:case SchedulerPhase.midFrameMicrotasks:case SchedulerPhase.persistentCallbacks:return;}}

等到下一帧到来的时候,被标记的Element就会被遍历执行渲染对象的布局和绘制。怎么被标记?看看平时的setState方法,

State

  void setState(VoidCallback fn) {_element!.markNeedsBuild();}

Element

BuildOwner? _owner;
void markNeedsBuild() {//..._dirty = true;owner!.scheduleBuildFor(this);//..
}

BuildOwner

  void scheduleBuildFor(Element element) {//.._dirtyElements.add(element);element._inDirtyList = true;//..}

首先会被标记_dirty=true代表需要被更新的对象, 然后会放到_dirtyElements里面,并且标记_inDirtyList=true已经添加到element树里面

综上所述,WidgetsFlutterBinding.ensureInitialized()做了以下几件事情:

  1. 创建个WidgetsFlutterBinding实例
  2. 初始化, SchedulerBinding, RendererBinding, WidgetsBinding 等所有单例
  3. WidgetsBinding.instance=SchedulerBinding.instance=RendererBinding.instance=WidgetsFlutterBinding()
  4. 然后执行SchedulerBinding, RendererBinding, WidgetsBinding 所有类的initInstances,initServiceExtensions方法
  5. 完成SchedulerBinding, RendererBinding, WidgetsBinding 所有相关的callback绑定,完成渲染树,Element树的管理类的初始化

接下来我们看看…scheduleAttachRootWidget(app),做了什么

void runApp(Widget app) {WidgetsFlutterBinding.ensureInitialized()..scheduleAttachRootWidget(app)..scheduleWarmUpFrame();
}

scheduleAttachRootWidgets属于WidgetsBinding的方法,调用了attachRootWidget,这个方法作用是将MyApp作为Widget树的根结点绑定到_renderViewElement这个棵Element树上,并且将渲染树也绑定到上面,由于第一次执行,renderViewElement肯定是空的,所以会触发SchedulerBinding.instance!.ensureVisualUpdate(),在上面已经提过ensureVisualUpdate这个方法,这里是第一次执行,所以他会注册_handleBeginFrame,_handleDrawFrame的回调(先记住这里注册,后面会讲解怎么被系统执行的)。然后请求Vsync,将会得到下一帧的绘制回调(注意在这里,是第一帧)

WidgetsBinding

void scheduleAttachRootWidget(Widget rootWidget) {Timer.run(() {attachRootWidget(rootWidget);});}//这个方法将Widget,Render树都绑定在Element树上void attachRootWidget(Widget rootWidget) {final bool isBootstrapFrame = renderViewElement == null;_readyToProduceFrames = true;_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(container: renderView,debugShortDescription: '[root]',child: rootWidget,).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);//如果是第一次,这里会执行if (isBootstrapFrame) {//由于之前执行了WidgetsFlutterBinding.ensureInitialized()// SchedulerBinding.instance确保有实例的// 请求下一帧绘制,也就是第一帧绘制。SchedulerBinding.instance!.ensureVisualUpdate();}}

接下来是…scheduleWarmUpFrame(),这个方法是属于SchedulerBinding
主要是执行了handleBeginFrame和handleDrawFrame。上面已经讲解这2个方法的作用,
由于在RendererBinding中addPersistentFrameCallback,并且调用drawFrame方法,所以
执行handleDrawFrame会执行drawFrame这个方法,从而会布局和绘制页面。

void scheduleWarmUpFrame() {//...handleBeginFrame(null);//...handleDrawFrame();
}//执行里面scheduleFrameCallback注册的回调,动画之类的事件
void handleBeginFrame(Duration? rawTimeStamp) {
//...try {// TRANSIENT FRAME CALLBACKS_frameTimelineTask?.start('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);_schedulerPhase = SchedulerPhase.transientCallbacks;final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;_transientCallbacks = <int, _FrameCallbackEntry>{};callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {if (!_removedIds.contains(id))_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);});_removedIds.clear();} finally {_schedulerPhase = SchedulerPhase.midFrameMicrotasks;}
}//执行addPersistentFrameCallback和addPostFrameCallback中注册的回调
//void handleDrawFrame() {//..._schedulerPhase = SchedulerPhase.persistentCallbacks;for (final FrameCallback callback in _persistentCallbacks)_invokeFrameCallback(callback, _currentFrameTimeStamp!);// POST-FRAME CALLBACKS_schedulerPhase = SchedulerPhase.postFrameCallbacks;final List<FrameCallback> localPostFrameCallbacks =List<FrameCallback>.of(_postFrameCallbacks);_postFrameCallbacks.clear();for (final FrameCallback callback in localPostFrameCallbacks)_invokeFrameCallback(callback, _currentFrameTimeStamp!);//..}

综上所述,scheduleAttachRootWidget和 scheduleWarmUpFrame做如下几件事情

  1. 将所有树绑定关联起来
  2. 设置好Vsync信号下一帧回来执行的回调(后面验证 window是在哪里被执行的)
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  3. 请求获取第一帧的绘制
  4. 强制执行handleBeginFrame和 handleDrawFrame

感叹,普普通通的一行runApp,会触发这么多业务,如果不是细细品尝,很难发现这些关系。

上面讲的都是如何申请Vsync,然后被动触发事件任务的执行,还有布局的绘制工作,那么接下来需要串通的是,如何给Vsync发出申请,然后原生App怎么下发Vsync给Flutter下发执行的

申请Vsync流程

我们回头看上面提到的scheduleFrame会请求Vsync这个方法,最终会执行window.scheduleFrame, scheduleFrame是在window.dart这个类里

mixin SchedulerBinding on BindingBase{//..void scheduleFrame() {//..ensureFrameCallbacksRegistered();window.scheduleFrame();//...}//..
}

window其实是FlutterWindow的引用

class FlutterWindow extends FlutterView {
//...

final PlatformDispatcher platformDispatcher;
void scheduleFrame() => platformDispatcher.scheduleFrame();
//...
}

对于PlatformDispatcher的scheduleFrame,是调用了native方法

class PlatformDispatcher{void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';}

接下来,我们来到flutter引擎源码, lib/ui/window/platform_configuration.cc.
他执行的是PlatformConfigurationNativeApi::ScheduleFrame.他调用了
PlatformConfigurationClientScheduleFrame方法


PlatformConfigurationClient* client_;
PlatformConfigurationClient* client() const { return client_; }//
void PlatformConfigurationNativeApi::ScheduleFrame() {UIDartState::ThrowIfUIOperationsProhibited();UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}

而PlatformConfigurationClient是由RuntimeController实现的,代码在runtime/runtime_controller.cc


class RuntimeController : public PlatformConfigurationClient RuntimeDelegate& client_;
}void RuntimeController::ScheduleFrame() {//client_.ScheduleFrame();
}

接着是用RuntimeDelegate进行申请,也就是引擎类,他在shell/common/engine.h这个路径,
他会调用animator_的RequestFrame,这个才是最终真正进行申请的类

class Engine final : public blink::RuntimeDelegate{//..Animator& animator_;//..
}void Engine::ScheduleFrame(bool regenerate_layer_tree) {animator_->RequestFrame(regenerate_layer_tree);
}

代码在shell/common/animator.cc

void Animator::RequestFrame(bool regenerate_layer_tree) {//......task_runners_.GetUITaskRunner()->PostTask(//......frame_request_number = frame_request_number_]() {//......//申请Vsyncself->AwaitVSync();});
}

我们下面来看看Animator的源码AwaitVSync,

class Animator final 
{std::shared_ptr<VsyncWaiter> waiter_;
}void Animator::AwaitVSync() {waiter_->AsyncWaitForVsync([self = weak_factory_.GetWeakPtr()](std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {//...self->BeginFrame(std::move(frame_timings_recorder));//...});
}

他是委托VsyncWaiter实现,文件在shell/common/vsync_waiter.cc

void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {//......callback_ = std::move(callback);//......AwaitVSync();
}

然后点 AwaitVSync进去发现, 是空实现。头大了,找了很久发现是在shell/platform/android/vsync_waiter_android.cc里面实现的.也就是他对应在安卓的VsyncWaiterAndroid::AwaitVSync源码中


void VsyncWaiterAndroid::AwaitVSync() {//......task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {JNIEnv* env = fml::jni::AttachCurrentThread();env->CallStaticVoidMethod(g_vsync_waiter_class->obj(),  //调用安卓的asyncWaitForVsyncg_async_wait_for_vsync_method_,java_baton);});
}

VsyncWaiterAndroid::AwaitVSync它会调用安卓的Java文件FlutterJNI.java的静态方法
asyncWaitForVsync
在这里插入图片描述
asyncWaitForVsyncDelegate是个接口

public interface AsyncWaitForVsyncDelegate {void asyncWaitForVsync(final long cookie);}

让我们看看他的实现类


// TODO(mattcarroll): add javadoc.
public class VsyncWaiter {private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =new FlutterJNI.AsyncWaitForVsyncDelegate() {@Overridepublic void asyncWaitForVsync(long cookie) {Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {long delay = System.nanoTime() - frameTimeNanos;if (delay < 0) {delay = 0;}flutterJNI.onVsync(delay, refreshPeriodNanos, cookie);}});}};
}      

,好家伙,原来他是用Choreographer.getInstance().postFrameCallback
这个方法会触发申请Vsync,然后收到Vsync会回调这个new Choreographer.FrameCallback.
也就是说,等Vsync下发回来的时候执行Choreographer.FrameCallback的doFrame方法。
不了解Android中Choreographer的朋友,可以阅读我这篇文章点击>>15分钟带你彻底了解App绘制流程-安卓篇

找到申请Vsync后,下一步就是把Vsync发到Flutter,执行下一帧的工作

下发Vsync

在Choreographer.FrameCallback的doFrame执行中,会调用 flutterJNI.onVsync,在这你已经猜到了,这里开始要下发Vsync给flutter了, 于是上面的waiter_->AsyncWaitForVsync就会执行回调,也就是

//...
self->BeginFrame(std::move(frame_timings_recorder));
//...

让我们一路往下看

void Animator::BeginFrame(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {//...delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number);//...
}void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time,uint64_t frame_number) {//...if (engine_) {engine_->BeginFrame(frame_target_time, frame_number);}
}void Engine::BeginFrame(fml::TimePoint frame_time, uint64_t frame_number) {//..runtime_controller_->BeginFrame(frame_time, frame_number);
}

runtime_controller_在上面讲过,他是PlatformConfiguration的实例。
我们现在需要去flutter看看一个文件ui.dart
请添加图片描述
里面part了hooks.dart, 而hooks里面声明了很多被c++调用的代码其中有

('vm:entry-point')
void _drawFrame() {PlatformDispatcher.instance._drawFrame();
}('vm:entry-point')
void _beginFrame(int microseconds, int frameNumber) {PlatformDispatcher.instance._beginFrame(microseconds);PlatformDispatcher.instance._updateFrameData(frameNumber);
}

PlatformConfiguration中,有个方法将hooks的方法做了关联,关联了_beginFrame,和_drawFrame,也就是PlatformConfiguration可以调用这个2个方法,如下


void PlatformConfiguration::DidCreateIsolate() {Dart_Handle library = Dart_LookupLibrary(tonic::ToDart("dart:ui"));//...begin_frame_.Set(tonic::DartState::Current(),Dart_GetField(library, tonic::ToDart("_beginFrame")));draw_frame_.Set(tonic::DartState::Current(),Dart_GetField(library, tonic::ToDart("_drawFrame")));//...
}

承接Vsync

接着来看看runtime_controller_的方法BeginFrame, 你会发现,这个方法其实就是调用了
hooks.dart的_beginFrame和_drawFrame方法

void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,uint64_t frame_number) {//......tonic::LogIfError(tonic::DartInvoke(begin_frame_.Get(), {Dart_NewInteger(microseconds),Dart_NewInteger(frame_number),}));UIDartState::Current()->FlushMicrotasksNow();tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}

_beginFrame和_drawFrame这两个方法是被由PlatformDispatcher持有的。

接着回头看看上面提到的ensureFrameCallbacksRegistered这个方法,

  void ensureFrameCallbacksRegistered() {window.onBeginFrame ??= _handleBeginFrame;window.onDrawFrame ??= _handleDrawFrame;}

看看window是怎么设置onBeginFrame和onDrawFrame的

window.dart

FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;set onBeginFrame(FrameCallback? callback) {platformDispatcher.onBeginFrame = callback;}VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;set onDrawFrame(VoidCallback? callback) {platformDispatcher.onDrawFrame = callback;}

也就是说,Vsync下发回来其实就是触发window设置的回调onBeginFrame(SchedulerBinding的_handleBeginFrame)和onDrawFrame(SchedulerBinding的_handleDrawFrame).
这2个方法被回调就会进行事件任务的执行,以及布局绘制的工作。

终于,到这里所有流程都串通了.

我们重新梳理下所有流程

  1. 先初始化SchedulerBinding,RendererBinding,WidgetsBinding单例,确保不为空
  2. RendererBinding绑定persistentFrameCallback每次收到Vsync就进行布局和绘制工作
  3. 将SchedulerBinding的_handleBeginFrame,_handleDrawFrame通过window.onBeginFrame, onDrawFrame方法被绑定到platformDispatcher
  4. 初始化三棵树的绑定
  5. 申请第一个Vsync绘制第一帧
  6. 执行native方法从而让c++代码向安卓发出请求Vsync请求并绑定回调
  7. 安卓获取到Vsync后调用JNI传递Vsync给c++, 然后c++调用SchedulerBinding的_handleBeginFrame,_handleDrawFrame从而完成一帧的工作

好了,所以流程已经梳理完毕,是不是很赞?如果这篇文章对你有帮助,请关注🙏,点赞👍,收藏😋三连哦


http://www.mrgr.cn/p/83386857

相关文章

linux中进程相关概念(一)

什么是程序&#xff0c;什么是进程&#xff0c;有什么区别&#xff1f; 程序是静态的概念&#xff0c;当我们使用gcc xxx.c -o pro进行编译时&#xff0c;产生的pro文件&#xff0c;就是一个程序。 进程是程序的一次运行活动&#xff0c;通俗点就是说程序跑起来了就是进程。 …

C++反汇编,指针和内存分配细节,面试题05

文章目录 20. 指针 vs 引用21. new vs malloc 20. 指针 vs 引用 指针是实体&#xff0c;占用内存空间&#xff0c;逻辑上独立&#xff1b;引用是别名&#xff0c;与变量共享内存空间&#xff0c;逻辑上不独立。指针定义时可以不初始化&#xff1b;引用定义时必须初始化。指针的…

Vue自定义封装音频播放组件(带拖拽进度条)

Vue自定义封装音频播放组件&#xff08;带拖拽进度条&#xff09; 描述 该款自定义组件可作为音频、视频播放的进度条&#xff0c;用于控制音频、视频的播放进度、暂停开始、拖拽进度条拓展性极高。 实现效果 具体效果可以根据自定义内容进行位置调整 项目需求 有播放暂停…

localhost 重定向次数过多

在完成javaweb作业时出现了错误初始页面只有两个功能, 但是无论是点击登录还是注册,都会跳转到login.jsp页面从网上找到的答案是代码陷入死循环,因为总是跳转到login.jsp, 所以我查看了所有servlet类中跳转到login.jsp页面的代码,逻辑上并没有问题;然后我又查看了过滤器以…

Windows平台使用CMake+MinGW64编译OpenCV

Windows平台使用CMake+MinGW64编译OpenCV (注:2年前写的笔记, 可能有些地方过时了) 目录Windows平台使用CMake+MinGW64编译OpenCV1.安装及配置环境1.1 MinGW-w641.2 CMake1.3 OpenCV源码2.CMake配置及生成2.1 新建目录2.2 CMake-GUI2.3 编译配置2.4 生成2.5 Make编译和安装3.配…

【大模型赋能开发者】海云安入选数世咨询LLM驱动数字安全2024——AI安全系列报告

近日&#xff0c;国内知名数字产业领域第三方调研咨询机构数世咨询发布了LLM驱动数字安全2024——AI安全系列报告。报告通过调研、公开信息收集等方式对目前十余家已具备LLM相关的应用能力安全厂商对比分析出了这一领域当前的产业现状并进行了各厂商的能力展示。 海云安凭借近…

金融业开源软件应用 评估规范

金融业开源软件应用 评估规范 1 范围 本文件规定了金融机构在应用开源软件时的评估要求&#xff0c;对开源软件的引入、维护和退出提出了实现 要求、评估方法和判定准则。 本文件适用于金融机构对应用的开源软件进行评估。 2 规范性引用文件 下列文件中的内容通过文中的规范…

介绍适用于 Node.js 的 Elastic OpenTelemetry 发行版

作者&#xff1a;来自 Elastic Trent Mick 我们很高兴地宣布推出 Elastic OpenTelemetry Distribution for Node.js 的 alpha 版本。 该发行版是 OpenTelemetry Node.js SDK 的轻量级包装&#xff0c;可以让你更轻松地开始使用 OpenTelemetry 来观察 Node.js 应用程序。 背景 …

x64dbg中类似于*.exe+地址偏移

在CE和xdb中&#xff0c;形如*.exe数字偏移形式的地址被称为模块地址&#xff0c;CE附加到进程后点击查看内存&#xff0c;显示如下图 这种地址学名叫做模块地址&#xff0c;在x64dbg中显示如下图&#xff1a; CE中可以关闭&#xff0c;从而显示绝对的虚拟地址&#xff0c;如下…

时间复杂度空间复杂度 力扣:转轮数组,消失的数字

1. 算法效率 如何衡量一个算法的好坏&#xff1f;一般是从时间和空间的维度来讨论复杂度&#xff0c;但是现在由于计算机行业发展迅速&#xff0c;所以现在并不怎么在乎空间复杂度了下面例子中&#xff0c;斐波那契看上去很简洁&#xff0c;但是复杂度未必如此 long long Fib…

【JavaEE网络】HTTP响应详解:状态码、报头与正文的全面解析

目录 HTTP响应&#xff08;Response&#xff09;认识 "状态码" (status code)认识响应 “报头”&#xff08;header&#xff09;认识响应 “正文”&#xff08;body&#xff09; HTTP响应&#xff08;Response&#xff09; 响应&#xff1a; 首行响应头空行正文 认…

MySQL#MySql表的操作

目录 一、创建表 二、查看表结构 三、修改表 1.修改表的名字 2.新增一个列 3.修改列 4.删除列 5.修改列的名称 四、删除表 一、创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校…

一键实现在VS Code中绘制流程图

VS Code是一款常用的IDE&#xff0c;受到许多用户的欢迎和喜爱。而其较为出众的一点&#xff0c;就是较好的可拓展性&#xff0c;即丰富的插件应用&#xff0c;这些应用可以极大地提高生产效率&#xff0c;并优化日常使用。 流程图是一种直观的图示方法&#xff0c;可以用简明…

jsp 实验12 servlet

一、实验目的 掌握怎样在JSP中使用javabean 二、实验项目内容&#xff08;实验题目&#xff09; 编写代码&#xff0c;掌握servlet的用法。【参考课本 上机实验1 】 三、源代码以及执行结果截图&#xff1a; 源代碼&#xff1a; inputVertex.jsp&#xff1a; <% page lang…

[转帖]TLAB(Thread Local Allocation Buffer)

https://www.cnblogs.com/Chary/p/18034613 TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,…

【前端】CSS基础(1)

文章目录 前言一、CSS基础1、 CSS是什么2、 CSS基本语法规范3、 代码风格3.1 样式格式3.2 样式大小写3.3 空格规范 4、 CSS引入方式4.1 内部样式表4.2 行内样式表4.3 外部样式 前言 这篇博客仅仅是对CSS的基本结构进行了一些说明&#xff0c;关于CSS的更多讲解以及HTML、Javasc…

YOLOv5改进 | 独家创新篇 | 利用MobileNetV4的UIB模块二次创新C3(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用MobileNetV4的UIB模块二次创新C3&#xff0c;其中UIB模块来自2024.5月发布的MobileNetV4网络&#xff0c;其是一种高度优化的神经网络架构&#xff0c;专为移动设备设计。它最新的改动总结主要有两点&#xff0c;采用了通用反向瓶…

OpenHarmony 3.2 Release版本实战开发——Codec HDI适配过程

简介 OpenHarmony Codec HDI&#xff08;Hardware Device Interface&#xff09;驱动框架基于 OpenMax 实现了视屏硬件编解码驱动&#xff0c;提供 Codec 基础能力接口供上层媒体服务调用&#xff0c;包括获取组件编解码能力、创建组件、参数设置、数据的轮转和控制、以及销毁…

GPU通用计算介绍

谈到 GPU &#xff08;Graphics Processing Unit&#xff0c;图形显示卡&#xff09;大多数人想到的是游戏、图形渲染等这些词汇&#xff0c;图形处理确实是 GPU 的一大应用场景。然而人们也早已关注到它在通用计算上的巨大潜力&#xff0c;并提出了 GPGPU (General-purpose co…