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

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模式的初始化活
	//...
  }
 
 //申请vsync
 void 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_]() {
        //......
        //申请Vsync
        self->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(),  
        //调用安卓的asyncWaitForVsync
        g_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() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          Choreographer.getInstance()
              .postFrameCallback(
                  new Choreographer.FrameCallback() {
                    @Override
                    public 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.mfbz.cn/a/608570.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

卡码网模拟笔试题第十六期 |

A、构造二阶行列式 数字不大&#xff0c;直接四重循环暴力枚举 #include <iostream> using namespace std;int main() {int x;cin >> x;for (int i 1; i < 20; i) {for (int j 1; j < 20;j) {for (int x1 1;x1 < 20;x1) {for (int y 1;y<20;y){if…

2023-2024年家电行业报告合集(精选51份)

家电行业报告/方案&#xff08;精选51份&#xff09; 2023-2024年 报告来源&#xff1a;2023-2024年家电行业报告合集&#xff08;精选51份&#xff09; 【以下是资料目录】 空气炸锅出海品牌策划创意全案【家电出海】【品牌全案】 卡萨帝潮流消费品生活家电音乐节活动方案…

44.乐理基础-音符的组合方式-附点

内容参考于&#xff1a; 三分钟音乐社 首先如下图&#xff0c;是之前的音符&#xff0c;但是它不全&#xff0c;比如想要一个三拍的音符改怎样表示&#xff1f; 在简谱中三拍&#xff0c;在以四分音符为一拍的情况下&#xff0c;在后面加两根横线就可以了&#xff0c;称为附点…

山东齐鲁文化名人颜廷利:教育的本质区别重点是什么

教育的本质区别重点是‘方式’&#xff0c; 现在的教育却成为了一种‘形式’&#xff1b; 教育的核心价值关键载于‘实践’&#xff0c; 当前我们的教育观念却变成了消耗‘时间’&#xff1b; ‘读书’的原则在于‘堵疏’&#xff0c;作为汉语‘堵疏’一词&#xff0c;顾名思义…

亚马逊是如何铺设多个IP账号实现销量大卖的?

一、针对亚马逊平台机制&#xff0c;如何转变思路&#xff1f; 众所周知&#xff0c;一个亚马逊卖家只能够开一个账号&#xff0c;一家店铺&#xff0c;这是亚马逊平台明确规定的。平台如此严格限定&#xff0c;为的就是保护卖家&#xff0c;防止卖家重复铺货销售相同的产品&a…

多线程学习Day07

共享模型之不可变 从一个日期转换的问题开始 Slf4j(topic "c.Test1") public class Test1 {public static void main(String[] args) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");for (int i 0; i < 10; i) {new Thread(() -> {…

使用GitLab自带的CI/CD功能在本地部署.Net8项目(二)

前置内容&#xff1a; 通过Docker Compose部署GitLab和GitLab Runner&#xff08;一&#xff09; 目录 一、创建代码仓库 二、创建GitLabRunner 三、注册Runner 四、配置Runner&#xff0c;绑定宿主Docker 五、创建.Net8WebApi项目进行测试 六、总结 一、创建代码仓库 …

Qt---项目的创建及运行

一、创建第一个Qt程序 1. 点击创建项目后&#xff0c;选择项目路径以及给项目起名称 名称&#xff1a;不能有中文、不能有空格 路径&#xff1a;不能有中文路径 2. 默认创建有窗口类myWidget&#xff0c;基类有三种选择&#xff1a;QWidget、QMainWindow、QDialog 3. m…

【C++11】线程库 | 互斥量 | 原子性操作 | 条件变量

文章目录 一、线程库 - thread1. 线程对象的构造方式无参构造带可变参数包的构造移动构造 2. thread类的成员函数thread::detach()thread::get_id()thread::join()thread::joinable() 线程函数参数的问题 二、互斥量库 - mutex标准库提供的四种互斥锁1. std::mutex2. std::recu…

【Ubuntu18.04+melodic】抓取环境设置

UR5_gripper_camera_gazebo&#xff08;无moveit&#xff09; 视频讲解 B站-我要一米八了-抓取不止&#xff01;Ubuntu 18.04下UR5机械臂搭建Gazebo环境&#xff5c;开源分享 运行步骤 1.创建工作空间 catkin_make2.激活环境变量 source devel/setup.bash3.1 rviz下查看模…

Oracle 修改数据库的字符集

Oracle 修改数据库的字符集 alter system enable restricted session; alter database "cata" character set ZHS16CGB231280; alter database "cata" national character set ZHS16CGB231280; alter system enable restricted session; alter database…

使用动态种子的DGA:DNS流量中的意外行为

Akamai研究人员最近在域名系统&#xff08;DNS&#xff09;流量数据中观察到&#xff1a;使用动态种子的域名生成算法&#xff08;Domain Generation Algorithm&#xff0c;DGA&#xff09;的实际行为&#xff0c;与对算法进行逆向工程推测的预期行为之间存在一些差异。也就是说…

C++ 基础 输入输出

一 C 的基本IO 系统中的预定义流对象cin和cout: 输入流&#xff1a;cin处理标准输入&#xff0c;即键盘输入&#xff1b; 输出流&#xff1a;cout处理标准输出&#xff0c;即屏幕输出&#xff1b; 流&#xff1a;从某种IO设备上读入或写出的字符系列 使用cin、cout这两个流对…

在Ubuntu上安装Anaconda之后,启动失败

为了方便管理Pythonu环境&#xff0c;在Ubuntu的Docker容器中安装了Anaconda&#xff0c;安装完成&#xff0c;启动时出现如下错误&#xff1a; conda activate xxx usage: conda [-h] [--no-plugins] [-V] COMMAND ... conda: error: argument COMMAND: invalid choice: acti…

Linux的基础IO:文件描述符 重定向本质

目录 前言 文件操作的系统调用接口 open函数 close函数 write函数 read函数 注意事项 文件描述符-fd 小补充 重定向 文件描述符的分配原则 系统调用接口-dup2 缓冲区 缓冲区的刷新策略 对于“2”的理解 小补充 前言 在Linux中一切皆文件&#xff0c;打开文件…

springcloud服务间调用 feign 的使用

引入依赖包 <!-- 服务调用feign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>创建调用外部服务的接口 需要使用的地方注入 使用 启动类增…

CTFHUB-技能树-Web题-RCE(远程代码执行)-eval执行

CTFHUB-技能树-Web题-RCE&#xff08;远程代码执行&#xff09; 文章目录 CTFHUB-技能树-Web题-RCE&#xff08;远程代码执行&#xff09;eval执行解题方法&#xff1a;构造网址&#xff0c;查找当前目录文件并没有发现flag,接着查看上一级目录接着查看上一级接着查看上一级目录…

luceda ipkiss教程 66:金属线的钝角转弯

案例分享&#xff1a;金属线的135度转弯&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk import ipkiss3.all as i3 from ipkiss.geometry.shape_modifier import __ShapeModifierAutoOpenClosed__ from numpy import sqrtclass ShapeManhattanStub(__…

一种快速H.264 NALU快速搜索算法

1. 引言 在播放H.264码流的时候,进行NALU的搜索的效率高低影响着系统的性能。有采用普通逐字节搜索的算法,有利用cpu的simd的单指令多数据操作的并行功能进行搜索的算法,今天要介绍的是一个非常简单而且高效的快速搜索算法,而且不需要利用simd指令,搜索的速度甚至快于我之…

Spring-依赖来源

依赖来源 1 Spring BeanDefinition&#xff08;xml,注解&#xff0c;BeanDefinitionBuilder, 还有API实现的单例对象&#xff09; 2 Spring 内建BeanDefinition 3 内建单例对象 依赖注入和依赖查找的区别 Context.refresh() 的时候会调用这个方法&#xff1a;prepareBeanF…
最新文章