Flutter-->Widget上屏之路

news2024/11/14 1:38:36

本文主要介绍Flutter中创建一个Widget到屏幕上渲染出Widget内容的路程.

拾用本文您将获得:

  • Widget是什么
  • Element是什么
  • RenderObject是什么

附加Buff:

  • Widget直接渲染成图片
  • 文本String的绘制
  • 图片ui.Image的绘制

这一切都要从runApp方法开始说起, 如果你还不知道runApp是什么, 建议从
https://docs.flutter.dev/ui 开始阅读…

runApp

runApp方法就是进入Flutter世界的入口, 方法参数也是唯一的参数就是一个Widget

void runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}

那么这个名为appWidget是怎样到界面上的呢? 开始吧…

Element

要想将Widget渲染上屏, 首先就需要将Widget变换成Element.

所以在runApp方法的执行链上, 很容易就能跟踪到下面的代码:

void attachToBuildOwner(RootWidget widget) {
  final bool isBootstrapFrame = rootElement == null;
  _readyToProduceFrames = true;
  // 请注意这里
  _rootElement = widget.attach(buildOwner!, rootElement as RootElement?);
  if (isBootstrapFrame) {
    SchedulerBinding.instance.ensureVisualUpdate();
  }
}

关键代码widget.attach(buildOwner!, rootElement as RootElement?)

/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(/* parent */ null, /* slot */ null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

上述代码,就是将一个Widget变换成Element的关键代码. 请注意上面的代码, 因为这玩意在另一个类中原封不动的也出现过.

那就是RenderObjectToWidgetAdapter如下:

/// 代码来自[RenderObjectToWidgetAdapter.attachToRenderTree]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(null, null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

Widget变换成Element同样也是将Widget渲染成图片的关键步骤.

到这一步WidgetsBinding.rootElement就赋值完成了, 接下来就是等待屏幕信号刷新帧,开始渲染了…

上述attachToBuildOwner方法中, 有一行代码SchedulerBinding.instance.ensureVisualUpdate()就是用来安排刷新帧的, 触发之后, 等待屏幕信号即可.

/// 代码来自[SchedulerBinding.ensureVisualUpdate]
void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}
/// 代码来自[SchedulerBinding.scheduleFrame]
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled) {
    return;
  }
  assert(() {
    if (debugPrintScheduleFrameStacks) {
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    }
    return true;
  }());
  ensureFrameCallbacksRegistered();
  platformDispatcher.scheduleFrame();
  _hasScheduledFrame = true;
}

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

PlatformDispatcher.onBeginFrame

调用链路SchedulerBinding.ensureVisualUpdate->SchedulerBinding.scheduleFrame->SchedulerBinding._handleBeginFrame->SchedulerBinding.handleBeginFrame->SchedulerBinding._invokeFrameCallback

平时通过SchedulerBinding.scheduleFrameCallback方法安排的帧回调就是在SchedulerBinding._invokeFrameCallback方法中执行的.

PlatformDispatcher.onDrawFrame

调用链路SchedulerBinding.ensureVisualUpdate->SchedulerBinding.scheduleFrame->SchedulerBinding._handleDrawFrame->SchedulerBinding.handleDrawFrame->SchedulerBinding._invokeFrameCallback

平时通过SchedulerBinding.addPersistentFrameCallbackSchedulerBinding.addPostFrameCallback方法安排的帧回调就是在这里进行处理的.

之后Flutter通过无限循环执行PlatformDispatcher.onBeginFramePlatformDispatcher.onDrawFrame方法渲染出精美的软件界面.

读到这里, 你是否注意到和WidgetsBinding.rootElement似乎一毛钱关系都没有呢?

不慌, 它在这里…

WidgetsFlutterBinding.ensureInitialized

还记得runApp方法吗?

void runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}

这方法头一句就是WidgetsFlutterBinding.ensureInitialized, 来看看它的神秘面纱.

/// 代码来自[WidgetsFlutterBinding.ensureInitialized]
static WidgetsBinding ensureInitialized() {
  if (WidgetsBinding._instance == null) {
    WidgetsFlutterBinding();
  }
  return WidgetsBinding.instance;
}

聪明的你, 应该看出来了, 就是创建了一个单例WidgetsFlutterBinding对象. 您可千万不要被它的表象所迷惑, 这玩意可是一个巨兽…

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding

当您创建WidgetsFlutterBinding对象后会执行父类的构造方法BindingBase

BindingBase() {
  if (!kReleaseMode) {
    FlutterTimeline.startSync('Framework initialization');
  }
  assert(() {
    _debugConstructed = true;
    return true;
  }());

  assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');
  //注意这里
  initInstances();
  assert(_debugInitializedType != null);

  assert(!_debugServiceExtensionsRegistered);
  initServiceExtensions();
  assert(_debugServiceExtensionsRegistered);

  if (!kReleaseMode) {
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    FlutterTimeline.finishSync();
  }
}

会触发BindingBase.initInstances方法, 此方法会依次执行:

  • GestureBinding.initInstances 主要用来执行屏幕手势回调处理

void initInstances() {
  super.initInstances();
  _instance = this;
  platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
  • SchedulerBinding.initInstances在开发阶段用来计算帧率时间等

void initInstances() {
  super.initInstances();
  _instance = this;

  if (!kReleaseMode) {
    addTimingsCallback((List<FrameTiming> timings) {
      timings.forEach(_profileFramePostEvent);
    });
  }
}
  • ServicesBinding.initInstances主要用于平台服务支持等

void initInstances() {
  super.initInstances();
  _instance = this;
  _defaultBinaryMessenger = createBinaryMessenger();
  _restorationManager = createRestorationManager();
  _initKeyboard();
  initLicenses();
  SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
  SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));
  SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
  SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
  platformDispatcher.onViewFocusChange = handleViewFocusChanged;
  TextInput.ensureInitialized();
  readInitialLifecycleStateFromNativeWindow();
  initializationComplete();
}
  • PaintingBinding.initInstances没想到吧, Flutter原生就有图片缓存池

void initInstances() {
  super.initInstances();
  _instance = this;
  _imageCache = createImageCache();
  shaderWarmUp?.execute();
}
  • SemanticsBinding.initInstances平台一些语义,无障碍服务等

void initInstances() {
  super.initInstances();
  _instance = this;
  _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
  platformDispatcher
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsActionEvent = _handleSemanticsActionEvent
    ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
  _handleSemanticsEnabledChanged();
}
  • RendererBinding.initInstances Flutter引擎渲染调度核心

void initInstances() {
  super.initInstances();
  _instance = this;
  _rootPipelineOwner = createRootPipelineOwner();
  platformDispatcher
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;
  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame, debugLabel: 'RendererBinding.webFirstFrame');
  }
  rootPipelineOwner.attach(_manifold);
}

注意, 注意, 注意, 这个addPersistentFrameCallback(_handlePersistentFrameCallback)方法就是无限循环渲染的关键. addPersistentFrameCallback方法, 在前面已经介绍过了, 是不是忘记了? 往上翻一翻~~

  • WidgetsBinding.initInstances 一些和Widget有关的操作

void initInstances() {
  super.initInstances();
  _instance = this;

  assert(() {
    _debugAddStackFilters();
    return true;
  }());

  // Initialization of [_buildOwner] has to be done after
  // [super.initInstances] is called, as it requires [ServicesBinding] to
  // properly setup the [defaultBinaryMessenger] instance.
  _buildOwner = BuildOwner();
  buildOwner!.onBuildScheduled = _handleBuildScheduled;
  platformDispatcher.onLocaleChanged = handleLocaleChanged;
  SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
  SystemChannels.backGesture.setMethodCallHandler(
    _handleBackGestureInvocation,
  );
  assert(() {
    FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
    return true;
  }());
  platformMenuDelegate = DefaultPlatformMenuDelegate();
}

去掉杂念, 让我们关注 _handlePersistentFrameCallback方法:

/// 代码来自[RendererBinding._handlePersistentFrameCallback]
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

/// 代码来自[RendererBinding.drawFrame]

void drawFrame() {
  rootPipelineOwner.flushLayout();
  rootPipelineOwner.flushCompositingBits();
  rootPipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    for (final RenderView renderView in renderViews) {
      renderView.compositeFrame(); // this sends the bits to the GPU
    }
    rootPipelineOwner.flushSemantics(); // this sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

注意到drawFrame方法了吗? 此方法在WidgetsBinding.drawFrame被重写了:


void drawFrame() {
  assert(!debugBuildingDirtyElements);
  assert(() {
    debugBuildingDirtyElements = true;
    return true;
  }());

  TimingsCallback? firstFrameCallback;
  bool debugFrameWasSentToEngine = false;
  if (_needToReportFirstFrame) {
    assert(!_firstFrameCompleter.isCompleted);

    firstFrameCallback = (List<FrameTiming> timings) {
      assert(debugFrameWasSentToEngine);
      if (!kReleaseMode) {
        // Change the current user tag back to the default tag. At this point,
        // the user tag should be set to "AppStartUp" (originally set in the
        // engine), so we need to change it back to the default tag to mark
        // the end of app start up for CPU profiles.
        developer.UserTag.defaultTag.makeCurrent();
        developer.Timeline.instantSync('Rasterized first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
      }
      SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
      firstFrameCallback = null;
      _firstFrameCompleter.complete();
    };
    // Callback is only invoked when FlutterView.render is called. When
    // sendFramesToEngine is set to false during the frame, it will not be
    // called and we need to remove the callback (see below).
    SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!);
  }

  try {
  	//注意此处
    if (rootElement != null) {
      buildOwner!.buildScope(rootElement!);
    }
    super.drawFrame();
    assert(() {
      debugFrameWasSentToEngine = sendFramesToEngine;
      return true;
    }());
    buildOwner!.finalizeTree();
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
  if (!kReleaseMode) {
    if (_needToReportFirstFrame && sendFramesToEngine) {
      developer.Timeline.instantSync('Widgets built first useful frame');
    }
  }
  _needToReportFirstFrame = false;
  if (firstFrameCallback != null && !sendFramesToEngine) {
    // This frame is deferred and not the first frame sent to the engine that
    // should be reported.
    _needToReportFirstFrame = true;
    SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
  }
}

注意到buildOwner!.buildScope(rootElement!);了吗?

Element被丢到BuildOwner对象中, 然后在super.drawFrame方法中调用了PipelineOwner.flushPaint完成一帧渲染的所有流程.

此流程在将Widget直接渲染成图片如出一辙,

直接将Widget渲染成图片代码如下:

Future<ui.Image> buildImage(Widget widget) async {
   final repaintBoundary = RenderRepaintBoundary();
   final view = ui.PlatformDispatcher.instance.implicitView ??
       RendererBinding.instance.renderView.flutterView;

   //渲染树根
   final renderView = RenderView(
     view: view,
     child: RenderPositionedBox(
       alignment: Alignment.center,
       child: repaintBoundary,
     ),
     configuration: ViewConfiguration.fromView(view),
   );

   //管道
   final pipelineOwner = PipelineOwner()..rootNode = renderView;
   renderView.prepareInitialFrame();

   //管道对象
   final buildOwner = BuildOwner(focusManager: FocusManager());

   //根元素
   final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
       container: repaintBoundary,
       child: Directionality(
         textDirection: TextDirection.ltr,
         child: widget,
       )).attachToRenderTree(buildOwner);

   //构建树
   buildOwner
     ..buildScope(rootElement)
     ..finalizeTree();

   //渲染树
   pipelineOwner
     ..flushLayout()
     ..flushCompositingBits()
     ..flushPaint();

   final image = await repaintBoundary.toImage();
   return image;
 }

总结

runApp方法的参数app是怎么到界面上的?

  • 使用RootWidget包裹app Widget
  • 然后使用RootWidget.attach方法将Widget转变成RootElement
  • Element被丢到BuildOwner对象中
  • 然后调用PipelineOwner.flushPaint完成一帧渲染

到这里, 我们还有一个东西没有介绍RenderObject, 它要来了…

RenderObject

首先, 并不是所有的Element都有RenderObject,

Element又是由Widget变换来的, 所以并不是所有的Widget都需要RenderObject.

这你纠正一点, 在ElementrenderObject是一个get方法, 所以Element能获取到renderObject, 但不一定有值…

/// 代码来自[Element.renderObject]
RenderObject? get renderObject {
  Element? current = this;
  while (current != null) {
    if (current._lifecycleState == _ElementLifecycle.defunct) {
      break;
    } else if (current is RenderObjectElement) {
      return current.renderObject;
    } else {
      current = current.renderObjectAttachingChild;
    }
  }
  return null;
}

Flutter系统里面, 只有RenderObjectElement才有renderObject, RenderObjectWidget会默认创建RenderObjectElement, 所以…

/// 代码来自[RenderObjectElement.renderObject]

RenderObject get renderObject {
  assert(_renderObject != null, '$runtimeType unmounted');
  return _renderObject!;
}
RenderObject? _renderObject; //注意这里

这里顺便说一下, 平时使用的Text小部件, 文本InlineSpan是通过渲染对象RenderObject->RenderParagraph使用TextPainter绘制出来的.
平时使用的Image小部件, 图片ui.Image是通过渲染对象RenderObject>RenderImage使用paintImage方法绘制出来的

Element会经历->Element.mount->Element.update->Element.unmount可能不全, 但最核心的就这几个生命周期的回调.

RootElement根元素的mount方法在RootWidget.attach中的BuildOwner.buildScope方法中调用, 代码在之前已经提到过, 这里再来一次, 不劳烦翻页了.

/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      //注意这里
      element!.mount(/* parent */ null, /* slot */ null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

而之后子元素Elementmount方法就会在inflateWidget方法中调用了:

Element inflateWidget(Widget newWidget, Object? newSlot) {
	...
    final Element newChild = newWidget.createElement();
	...
	//注意这里
    newChild.mount(this, newSlot);
    ...
    return newChild;
  } finally {
    if (isTimelineTracked) {
      FlutterTimeline.finishSync();
    }
  }
}

方法调用链:RootElement.mount->RootElement._rebuild->Element.updateChild->Element.inflateWidget->Widget.createElement->Element.mount

火车就这样开起来了…

RenderObjectElement._renderObject对象就是在RenderObjectElement.mount方法中调用RenderObjectWidget.createRenderObject方法赋值的.

/// 代码来自[RenderObjectElement.mount]

void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  assert(() {
    _debugDoingBuild = true;
    return true;
  }());
  //注意此处
  _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
  assert(!_renderObject!.debugDisposed!);
  assert(() {
    _debugDoingBuild = false;
    return true;
  }());
  assert(() {
    _debugUpdateRenderObjectOwner();
    return true;
  }());
  assert(slot == newSlot);
  attachRenderObject(newSlot);
  super.performRebuild(); // clears the "dirty" flag
}

然后RenderObject.paint方法会被调用, 用来绘制, 里面有熟悉的canvas对象, 这对于Android开发的同学, 再熟悉不过了把?

/// 代码来自[RenderObject.paint]
void paint(PaintingContext context, Offset offset) { 
	final canvas = context.canvas; 
}

RenderObject对象中有:

  • performLayout方法, 用来实现布局(类似Android中的onMeasureonLayout)
  • paint方法, 用来实现自绘(类似Android中的onDraw)
  • handleEvent方法, 用来处理手势事件(类似Android中的onTouchEvent)

我将在之后的文章中介绍Flutter中的自定义控件:

  • 自定义自绘Widget(类似于自定义Android中的View)
  • 自定义布局Widget(类似于自定义Android中的ViewGroup)

总结

Widget是什么?

用来变换成Element的配置对象.

Element是什么?

用来创建最终的RenderObject对象.

RenderObject是什么?

使用Canvas绘制的, 界面上能看到的都是绘制出来的. 其余类其实都是控制在什么地方绘制用的.

至此文章就结束了! 感谢读者的宝贵时间~


群内有各(pian)种(ni)各(jin)样(qun)的大佬,等你来撩.

联系作者

点此QQ对话 该死的空格 点此快速加群
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2071534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【非常简单】 猿人学web第一届 第12题 入门级js

这一题非常简单&#xff0c;只需要找到数据接口&#xff0c;请求参数 m生成的逻辑即可 查看数据接口 https://match.yuanrenxue.cn/api/match/12 查看请求对应的堆栈中的 requests 栈 list 为对应的请求参数 list 是由 btoa 函数传入 ‘yuanrenxue’ 对应的页码生成的 bto…

安装torchvision==0.5.0

安装pytorch 1.4 但是在当前配置的镜像源中找不到 torchvision0.5.0 这个版本的包。 直接找资源下载 网址添加链接描述 直接运行该命令&#xff0c;成功。 然后重复运行上面的命令就可以了 # CUDA 9.2 conda install pytorch1.4.0 torchvision0.5.0 cudatoolkit9.2 -c pyto…

Spring Boot(快速上手)

Spring Boot 零、环境配置 1. 创建项目 2. 热部署 添加依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional> </dependency&…

Linux下进程间的通信--消息队列

System V IPC的概念 System V IPC&#xff08;System V Interprocess Communication&#xff09;是Unix和类Unix操作系统中一套传统的进程间通信机制&#xff0c;它包括三种主要的通信方式&#xff1a;消息队列、信号量和共享内存。这些机制提供了一种在不同进程之间交换数据和…

llamaindex+Internlm2 RAG实践 #书生谱语大模型实战营#

1.打卡任务&#xff1a; 本次的打卡任务是llamaindexInternlm2 RAG实践&#xff0c;我们需要基于 LlamaIndex 构建自己的 RAG 知识库&#xff0c;寻找一个问题 A 在使用 LlamaIndex 之前InternLM2-Chat-1.8B模型不会回答&#xff0c;借助 LlamaIndex 后 InternLM2-Chat-1.8B 模…

Axure设计之下拉单选框教程(中继器)

在Axure RP中&#xff0c;使用中继器&#xff08;Repeater&#xff09;可以实现许多复杂而动态的用户界面组件&#xff0c;比如下拉单选框。本文将详细介绍如何通过中继器创建一个美观且功能丰富的下拉单选框。 一、案例预览 预览地址&#xff1a;https://1zvcwx.axshare.com …

如何使用ssm实现基于JAVA的网上药品售卖系统

TOC ssm133基于JAVA的网上药品售卖系统jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规…

什么是堡垒机

堡垒机&#xff0c;即在一个特定的网络环境下&#xff0c;为了保障网络和数据不受来自外部和内部用户的入侵和破坏&#xff0c;而运用各种技术手段监控和记录运维人员对网络内的服务器、网络设备、安全设备、数据库等设备的操作行为&#xff0c;以便集中报警、及时处理及审计定…

鹏城杯 2022 取证writeup

简单取证 我们进去首先使用imageinfo pslist screenshot clipboard filescan 等等插件查看相关信息。 这里找到一个password姑且先留着 然后使用filescan找到了一个jpg文件 我们先dump下来 vol -f file.raw --profileWinXPSP2x86 dumpfiles -Q 0x000000000207e3d8 -D . 然…

Generating Query Recommendations via LLMs 【阅读笔记】

背景 主要去进行query的相关搜索补充&#xff1b; 例如 我们引入生成查询推荐&#xff08;GQR&#xff09;系统。 GQR以大语言模型为基础&#xff0c;利用大语言模型的提示能力&#xff0c;通过提示中提供的几个例子&#xff08;检索或手工&#xff09;来理解推荐任务。 方案…

主机监控与审计系统是什么?这个功能让您的效率翻倍!

天下之事&#xff0c;皆需明察秋毫&#xff0c;方能运筹帷幄&#xff0c;决胜千里。 于信息之海&#xff0c;主机者&#xff0c;犹若疆土之基石&#xff0c;承载着数据之重&#xff0c;运行着系统之脉。 然&#xff0c;世事如棋局局新&#xff0c;网络之域&#xff0c;暗流涌…

惠海H6432 dcdc升压恒压支持IC 3.7V 7.4V升压9V12V15V24V3A大电流 摄影灯光电源

1. 产品描述 H6432是一款电流模式B00ST异步升压恒压控制驱动芯片&#xff0c;适用于2.7-24V输入电压范 围的升压恒压电源应用领域&#xff0c;启动电压低至2. 5V。芯片根据负载的大小自动切换PWM&#xff0c;PFM和BURST模式以提高各个负载端的电源系统效率。芯片通过EN脚实现…

kali2022重置密码

在如下系统选择界面&#xff0c;按‘E’&#xff0c; 进入到编辑界面&#xff0c;将“ro quiet”修改为“rw init/bin/bash”。修改完成后ctrl X保存编辑并继续引导ctrlx进行引导&#xff0c;passwd修改密码&#xff0c;成功后重启&#xff0c;用root和新密码登录。

Adversarial Diffusion Distillation

sd turbohttps://static1.squarespace.com/static/6213c340453c3f502425776e/t/65663480a92fba51d0e1023f/1701197769659/adversarial_diffusion_distillation.pdf#page5.83https://github.com/Stability-AI/generative-models 问题引入 1-4 steps fast diffusion model samp…

软件测试最全面试题,了解一下

一、前言 近期有不少同学&#xff0c;朋友问我什么是软件测试&#xff0c;它是干什么的&#xff0c;我适不适合做、这行发展前景、工资怎么样等等等…在这里我把问题总结一下&#xff0c;整理一篇文章出来。 我也看过很多贴吧、论坛&#xff0c;在入行之前对这块都是迷茫的&a…

os 虚拟内存

虚拟内存不仅能隔离进程&#xff0c;还能提高内存利用率&#xff0c;请解释虚拟内存如何提高内存利用率&#xff1f;&#xff1f;&#xff1f; 虚拟内存&#xff08;Virtual Memory&#xff09;通过以下几个机制来提高内存利用率&#xff1a; 1. 内存分页&#xff08;Paging&…

20240824给飞凌OK3588-C的核心板刷Ubuntu22.04并安装iperf3测试网速

20240824给飞凌OK3588-C的核心板刷Ubuntu22.04并安装iperf3测试网速 2024/8/24 9:24 缘起&#xff0c;通过千兆交换机接入外网&#xff0c;开机之后发现以太网异常&#xff0c;多上电几次就会发现以太网也能用。 缘由&#xff1a;由于自制的飞凌的OK3588-C的核心板的底板对空间…

五子棋理解C++思想

双人五子棋项目目录&#xff1a; class Game { public:Game();void init();bool waitPlayerPutChess(Player* player, int& oldi, int& oldj);void draw();void play();bool isOver(int playerId);public:int whoWin -1; // 谁赢了&#xff08;0&#xff1a;白棋&a…

【题解】【模拟】—— [CSP-J 2023] 一元二次方程

【题解】【模拟】—— [CSP-J 2023] 一元二次方程 [CSP-J 2023] 一元二次方程题目背景题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示 1.题意解析2.AC代码 [CSP-J 2023] 一元二次方程 戳我查看题目&#xff08;洛谷&#xff09; 题目背景 众所周知&#xff0c;对…

共享文件操作记录如何查看?这种方法帮你轻松实现

查看共享文件操作记录&#xff0c;可以通过以下几种方法实现&#xff1a; 一、利用操作系统的文件访问记录功能 在Windows操作系统中&#xff0c;可以利用“事件查看器”来查看共享文件的访问记录。事件查看器是Windows内置的一个工具&#xff0c;用于记录系统中发生的各种事…