【Flutter进阶】聊一聊组件中的生命周期、状态管理及局部重绘

news2024/11/15 21:44:05

前言

说到生命周期,熟悉Android开发的小伙伴一定第一时间会想到Activity的生命周期,由于在Flutter中一切都是组件,所以组件的生命周期其实是类似的。

在这个过程中组件的状态——State就非常重要,它记录这整个组件内可变部分的状态,当状态发生改变时就需要刷新组件以显示最新的状态。

当然,如果组件比较复杂的时候,其中一个状态改变就导致整个组件的刷新是不可取的,这就涉及到Flutter中的局部重绘即局部刷新的机制。

本篇文章就通过这三个方面,来详细的解读Flutter中相关的机制以及如何使用。

生命周期

flutter的生命周期其实有两种:StatefulWidget和StatelessWidget。

这两个是flutter的两个基本组件,名称已经很好表明了这两个组件的功能:有状态和无状态。

  1. StatelessWidget

StatelessWidget是无状态组件,它的生命周期非常简单,只有一个build,如下:

class WidgetA extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return ...;
  }
}

对于StatelessWidget来说只渲染一次,之后它就不再有任何改变。

由于无状态组件在执行过程中只有一个 build 阶段,在执行期间只会执行一个 build 函数,没有其他生命周期函数,因此在执行速度和效率方面比有状态组件更好。所以在设计组件时,要考虑业务情况,尽量使用无状态组件。

  1. StatefulWidget

StatelessWidget是有状态组件,我们讨论的生命周期也基本指它的周期,如图:

image.png

包含以下几个阶段:

  • createState:该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。

  • initState:该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。

  • didChangeDependencies:该函数是在该组件依赖的 State 发生变化时,这里说的 State 为全局 State ,例如语言或者主题等,类似于前端 Redux 存储的 State 。

  • build:主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常,注意这里的性能问题

  • reassemble:主要是提供开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。

  • didUpdateWidget:该函数主要是在组件重新构建,比如说热重载,父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。

  • deactivate:在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。

  • dispose:永久移除组件,并释放组件资源。

在StatelessWidget中,只要我们调用setState,就会执行重绘,也就是说重新执行build函数,这样就可以改变ui。

组件刷新

先来看看下方的代码:

class MyHomePage extends StatefulWidget {

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            WidgetB(),
            WidgetC(_incrementCounter)
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  final int counter;

  WidgetA(this.counter);

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(counter.toString()),
    );
  }
}

class WidgetB extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text('I am a widget that will not be rebuilt.');
  }
}

class WidgetC extends StatelessWidget {
  final void Function() incrementCounter;

  WidgetC(this.incrementCounter);

  
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        incrementCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

我们有三个Widget,一个负责显示count,一个按钮改变count,一个则是静态显示文字,通过这三个Widget来对比比较页面的刷新逻辑。

上面代码中,三个Widget是在_MyHomePageState的build中创建的,执行后点击按钮可以发现三个Widget都刷新了。

在Flutter Performance面板上选中Track Widget Rebuilds即可看到

image.png

虽然三个Widget都是无状态的StatelessWidget,但是因为_MyHomePageState的State改变时会重新执行build函数,所以三个Widget会重新创建,这也是为什么WidgetA虽然是无状态的StatelessWidget却依然可以动态改变的原因。

所以:无状态的StatelessWidget并不是不能动态改变,只是在其内部无法通过State改变,但是其父Widget的State改变时可以改变其构造参数使其改变。实际上确实不能改变,因为是一个新的实例。

下面我们将三个组件提前创建,可以在_MyHomePageState的构造函数中创建,修改后代码如下:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  List<Widget> children;

  _MyHomePageState(){
    children = [
      WidgetA(_counter),
      WidgetB(),
      WidgetC(_incrementCounter)
    ];
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: children,
        ),
      ),
    );
  }
}

再次执行,发现点击没有任何效果,Flutter Performance上可以看到没有Widget刷新(这里指三个Widget,当然Scaffold还是刷新了)。

这是因为组件都提前创建了,所以执行build时没有重新创建三个Widget,所以WidgetA显示的内容并没有改变,因为它的counter没有重新传入。

所以,不需要动态改变的组件可以提前创建,build时直接使用即可,而需要动态改变的组件实时创建。

这样就可以实现局部刷新了么?我们继续改动代码如下:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget b = WidgetB();
  Widget c ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            b,
            c
          ],
        ),
      ),
    );
  }
}

我们只将WidgetB和WidgetC重新创建,而WidgetA则在build中创建。执行后,点击按钮WidgetA的内容改变了,查看Flutter Performance可以看到只有WidgetA刷新了,WidgetB和WidgetC没有刷新。

image.png

所以:通过提前创建静态组件build时直接使用,而build时直接创建动态Widget 这种方式可以实现局部刷新。

注意:

只要setState,_MyHomePageState就会刷新,所以WidgetA就会跟着刷新,即使count没有改变。比如上面代码中将setState中的_count++代码注释掉,再点击按钮虽然内容没有改变,但是WidgetA依然刷新。

这种情况可以通过InheritedWidget来进行优化。

InheritedWidget

InheritedWidget的作用什么?网上有人说是数据共享,有人说是用于局部刷新。我们看官方的描述:

Base class for widgets that efficiently propagate information down the tree.

可以看到它的作用是Widget树从上到下有效的传递消息,所以很多人理解为数据共享,但是注意这个“有效的”,这个才是它的关键,而这个有效的其实就是解决上面提到的问题。

那么它怎么使用?

先创建一个继承至InheritedWidget的类:

class MyInheriteWidget extends InheritedWidget{
  final int count;
  MyInheriteWidget({ this.count, Widget child}) : super(child: child);

  static MyInheriteWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyInheriteWidget>();
  }

  
  bool updateShouldNotify(MyInheriteWidget oldWidget) {
    return oldWidget.count != count;
  }
}

这里将count传入。重点注意要实现updateShouldNotify函数,通过名字可以知道这个函数决定InheritedWidget的Child Widget是否需要刷新,这里我们判断如果与之前改变了才刷新。这样就解决了上面提到的问题。

然后还要实现一个static的of方法,用于Child Widget中获取这个InheritedWidget,这样就可以访问它的count属性了,这就是消息传递,即所谓的数据共享(因为InheritedWidget的child可以是一个layout,里面有多个widget,这些widget都可以使用这个InheritedWidget中的数据)。

然后我们改造一下WidgetA:

class WidgetA extends StatelessWidget {

  
  Widget build(BuildContext context) {
    final MyInheriteWidget myInheriteWidget = MyInheriteWidget.of(context);
    return Center(
      child: Text(myInheriteWidget.count.toString()),
    );
  }
}

这次不用在构造函数中传递count了,直接通过of获取MyInheriteWidget,使用它的count即可。

最后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget a = WidgetA();
  Widget b = WidgetB();
  Widget c ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: a,
            ),
            b,
            c
          ],
        ),
      ),
    );
  }
}

注意,这里用MyInheriteWidget包装一下WidgetA,而且WidgetA必须提前创建,如果在build中创建则每次MyInheriteWidget刷新都会跟着刷新,这样updateShouldNotify函数的效果就无法达到。

执行,点击按钮,可以发现只有WidgetA刷新了(当然MyInheriteWidget也刷新了)。如果注释掉setState中的_count++代码,再执行并点击发现虽然MyInheriteWidget刷新了,但是WidgetA并不刷新,因为MyInheriteWidget的count并未改变。

下面我们改动一下代码,将WidgetB和C都放入MyInheriteWidget会怎样?

 
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: Column(
                children: [
                  a,
                  b,
                  c
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

MyInheriteWidget的child是一个Column,将a、b、c都放在这下面。执行会发现依然是WidgetA刷新,B和C都不刷新。这是因为在B和C中没有执行MyInheriteWidget的of函数,就没有执行dependOnInheritedWidgetOfExactType,这样其实就没构成依赖,MyInheriteWidget就不会通知它们。

如果我们修改WidgetC,在build函数中添加一行MyInheriteWidget.of(context);那么虽然没有任何使用,依然能会跟着刷新,因为建立了依赖关系就会被通知。

InheritedWidget会解决多余的刷新问题,比如在一个页面中有多个属性,同样有多个Widget来使用这些属性,但是并不是每个Widget都使用所有属性。如果用最普通的实现方式,那么每次setState(无论改变哪个属性)都需要刷新这些Widget。但是如果我们用多个InheritedWidget来为这些Widget分类,使用相同属性的用同一个InheritedWidget来包装,并实现updateShouldNotify,这样当改变其中一个属性时,只有该属性相关的InheritedWidget才会刷新它的child,这样就提高了性能。

InheritedModel

InheritedModel是继承至InheritedWidget的,扩充了它的功能,所以它的功能更加强大。具体提现在哪里呢?

通过上面我们知道,InheritedWidget可以通过判断它的data是否变化来决定是否刷新child,但是实际情况下这个data可以是多个变量或者一个复杂的对象,而child也不是单一widget,而是一系列widget组合。比如展示一本书,数据可能有书名、序列号、日期等等,但是每个数据可能单独变化,如果用InheritedWidget,就需要每种数据需要一个InheritedWidget类,然后将使用该数据的widget包装,这样才能包装改变某个数据时其他widget不刷新。

但是这样的问题就是widget层级更加复杂混乱,InheritedModel就可以解决这个问题。InheritedModel最大的功能就是根据不同数据的变化刷新不同的widget。下面来看看如何实现。

首先创建一个InheritedModel:

class MyInheriteModel extends InheritedModel<String>{
  final int count1;
  final int count2;
  MyInheriteModel({ this.count1,  this.count2, Widget child}) : super(child: child);

  static MyInheriteModel of(BuildContext context, String aspect){
    return InheritedModel.inheritFrom(context, aspect: aspect);
  }

  
  bool updateShouldNotify(MyInheriteModel oldWidget) {
    return count1 != oldWidget.count1 || count2 != oldWidget.count2;
  }

  
  bool updateShouldNotifyDependent(MyInheriteModel oldWidget, Set<String> dependencies) {
    return (count1 != oldWidget.count1 && dependencies.contains("count1")) ||
        (count2 != oldWidget.count2 && dependencies.contains("count2"));
  }
}

这里我们传入两个count,除了实现updateShouldNotify方法,还需要实现updateShouldNotifyDependent方法。这个函数就是关键,可以看到我们判断某个数据是否变化后还判断了dependencies中是否包含一个关键词:

count1 != oldWidget.count1 && dependencies.contains(“count1”)

这个关键词是什么?从哪里来?后面会提到,这里先有个印象。

然后同样需要实现一个static的of函数来获取这个InheritedModel,不同的是这里获取的代码变化了:

InheritedModel.inheritFrom(context, aspect: aspect);

这里的aspect就是后面用到的关键字,而inheritFrom会将这个关键字放入dependencies,以便updateShouldNotifyDependent来使用。后面会详细解释这个aspect完整作用。

然后我们改造WidgetA:

class WidgetA extends StatelessWidget {

  
  Widget build(BuildContext context) {
    final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count1");
    return Center(
      child: Text(myInheriteModel.count1.toString()),
    );
  }
}

可以看到,这里定义了aspect。

然后因为有两个count,所以我们再新增两个Widget来处理count2:

class WidgetD extends StatelessWidget {

  
  Widget build(BuildContext context) {
    final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count2");
    return Center(
      child: Text(myInheriteModel.count2.toString()),
    );
  }
}

class WidgetE extends StatelessWidget {
  final void Function() incrementCounter;

  WidgetE(this.incrementCounter);

  
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        incrementCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

这里可以看到WidgetD的aspect与WidgetA是不同的。

最后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  int _counter2 = 0;
  Widget a = Row(
    children: [
      WidgetA(),
      WidgetD()
    ],
  );
  Widget b = WidgetB();
  Widget c ;
  Widget e ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
    e = WidgetE(_incrementCounter2);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _incrementCounter2() {
    setState(() {
      _counter2++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteModel(
              count1: _counter,
              count2: _counter2,
              child: a,
            ),
            b,
            c,
            e
          ],
        ),
      ),
    );
  }
}

WidgetD和E是处理count2的,A和C则是处理count。而MyInheriteModel的child不是单一Widget,而是一个Row,包含WidgetD和A。

执行代码,可以发现点击WidgetC的时候,只有WidgetA刷新了(当然MyInheriteModel也刷新);而点击WidgetD的时候,只有WidgetE刷新了。这样我们就实现了MyInheriteModel中的局部刷新。

其实原理很简单,aspect就相当于一个标记,当我们通过InheritedModel.inheritFrom(context, aspect: aspect);获取MyInheriteModel时,实际上将本Widget依赖到MyInheriteModel,并且将这个Widget标记。这时候如果data改变,遍历它的所有依赖时,会通过每个依赖的Widget获取它对应的标记集dependencies,然后触发updateShouldNotifyDependent判断该Widget是否刷新。

所以在InheritedModel(其实是InheritedElement)中存在一个map,记录了每个依赖的Widget对应的dependencies,所以一个Widget可以有多个标记,因为dependencies是一个Set,这样就可以响应多个数据的变化(比如多个数据组成一个String作为文本显示)。

上面其实可以用两个InheritedWidget也可以实现,但是布局越复杂,就需要越多的InheritedWidget,维护起来也费时费力。

所以可以看到InheritedModel使用更灵活,功能更强大,更适合复杂的数据和布局使用,并且通过细分细化每一个刷新区域,使得每次刷新都只更新最小区域,极大的提高了性能。

InheritedNotifier

InheritedNotifier同样继承至InheritedWidget,它是一个给Listenable的子类的专用工具,它的构造函数中要传入一个Listenable(这是一个接口,不再是之前的各种数据data),比如动画(如AnimationController),然后其依赖的组件则根据Listenable进行更新。

首先还是先创建一个InheritedNotifier:

class MyInheriteNotifier extends InheritedNotifier<AnimationController>{
  MyInheriteNotifier({
       Key key,
       AnimationController notifier,
       Widget child,
     }) : super(key: key, notifier: notifier, child: child);

  static double of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyInheriteNotifier>().notifier.value;
  }
}

这里提供的of函数则直接返回AnimationController的value即可。

然后创建一个Widget:

class Spinner extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Transform.rotate(
      angle: MyInheriteNotifier.of(context) * 2 * pi,
      child: Text("who!!"),
    );
  }
}

内容会根据AnimationController进行旋转。

修改WidgetA:

class WidgetA extends StatelessWidget {

  
  Widget build(BuildContext context) {
    return Center(
      child: Text("WidgetA"),
    );
  }
}

然后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage6> with SingleTickerProviderStateMixin {
  AnimationController _controller;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 10),
    )..repeat();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(),
            MyInheriteNotifier(
                notifier: _controller,
                child: Spinner()
            ),
          ],
        ),
      ),
    );
  }
}

运行会看到Text在不停的旋转,当然如果有其他Widget可以看到并不跟着刷新。

总之InheritedNotifier是一个更细化的工具,聚焦到一个具体场景中,使用起来也更方便。

Notifier

最后再简单介绍一下Notifier,考虑一个需求:页面A是列表页,而页面B是详情页,两个页面都有点赞操作和显示点赞数量,需要在一个页面点赞后两个页面的数据同时刷新。这种情况下就可以使用flutter提供另外一种方式——Notifier。

Notifier其实就是订阅模式的实现,主要包含ChangeNotifier和ValueNotifier,使用起来也非常简单。通过addListener和removeListener进行订阅和取消订阅(参数是无参无返回值的function),当数据改变时调用notifyListeners();通知即可。

ValueNotifier是更简单的ChangeNotifier,只有一个数据value,可以直接进行set和get,set时自动执行notifyListeners(),所以适合单数据的简单场景。

当时注意Notifier只是共享数据并通知变化,并不实现刷新,所以还要配合其他一并实现。比如上面的InheritedNotifier(因为Notifier都继承Listenable接口,所以两个可以很简单的配合使用),或者第三方库Provider(web开发的习惯)等等。

总结

本篇文章中提到的有关生命周期、组件状态、局部重绘等机制在Flutter开发中非常重要,了解相关知识不仅能让代码逻辑更清晰,同时也会提高应用流畅度,减少bug的出现。

Flutter的特点是跨平台,另外一个特点就是混合开发,这就涉及到与原生代码的交互,可以看这几篇博客:

【Flutter进阶】与Native进行交互的三种方式

【Flutter混合开发】在已有iOS项目中引入Flutter

【Flutter混合开发】在Android项目中如何启动Flutter

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

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

相关文章

【SSM整合】1—Spring和Mybatis整合

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279588.html SpringMVC专栏&#x1f449;htt…

linux安装kafka

目录 目录 一.安装包准备&#xff1a; 二.解压安装&#xff1a; 先将该安装包放入到/opt/install目录&#xff1a; 解压该文件到soft目录中&#xff1a; 改名&#xff0c;方便后续使用&#xff1a; 三修改其中配置和配置环境变量&#xff1a; 3.1 修改/opt/soft/kafka2…

camunda工作流引擎开发架构

Camunda的开发架构可以分为前端开发架构和后端开发架构。 前端开发架构&#xff1a; Camunda前端使用Angular框架进行开发&#xff0c;主要包括以下组件&#xff1a; 1、Cockpit&#xff1a;流程监控和管理界面。 2、Tasklist&#xff1a;任务管理和审批界面。 3、Admin&…

答题积分小程序云开发实战-开篇:项目介绍以及效果图

答题积分小程序云开发实战 开篇:项目介绍以及效果图 前言 我也看过不少的册子或者文章,大部分都很优秀,但也有的就长篇累牍,从时代背景讲起,复述各种基本概念、底层原理......嗯,看似很高级~ 但我阅读的时候,给我的感觉是,把你绕晕、把你劝退的感觉,相信大家都有同感,…

C++输入输出、缺省参数、函数重载、引用【C++初阶】

目录 一、C输入&输出 二、缺省参数 1、概念 2、分类 &#xff08;1&#xff09;全缺省 &#xff08;2&#xff09;半缺省 三、函数重载 1、概念 2、原理------名字修饰 一、C输入&输出 在C语言中&#xff0c;我们常用printf和scanf这两个函数进行输入输出。 …

产品-Axure9(英文版),.rp文件与.rplb文件的转换与区分

文章目录1、区分2、相互转换2.1 rp转为rplb2.1 rplb转为rp1、区分 rp文件是文档文件&#xff0c;可以理解为作品文件&#xff0c;自己的工作输出就是rp文件&#xff0c;图标如下。 rplb文件是库文件&#xff0c;是在制作文件过程中一个快捷库&#xff0c;图标如下 在点击绿色…

GitHub 上诞生了一个可视化低代码神器

作为开发者&#xff0c;你是否早已厌倦了日复一日的“增删改查”&#xff0c;每天都在重复造轮子&#xff0c;今天给大家推荐一款开源、靠谱、实用的低代码开发平台 -- ILLA Builder。 产品介绍 ILLA Builder 是 ILLA 的核心产品&#xff0c;是一款开源的低代码开发工具。通过…

ROS话题通信自定义+发布订阅代码--03

话题通信自定义msg 在 ROS 通信协议中&#xff0c;数据载体是一个较为重要组成部分&#xff0c;ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是&#xff0c;这些数据一般只包含一个 data 字段&#xff0c;结构的单一意味…

C++实现JPEG格式图片解析(附代码)

在网上看了好多解析JPEG图片的文章&#xff0c;多多少少都有问题&#xff0c;下面是我参考过的文章链接&#xff1a; 首先&#xff0c;解析的步骤1.读取文件的信息2.Huffman编码解码3.直流交流编码解析然而&#xff0c;读取多少个88矩阵才能解析出一个MCU呢&#xff1f;4.反量化…

8年测试老鸟总结,接口自动化测试测试用例编写(全覆盖场景)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化测试&#xf…

15-721 Chapter 6 索引

最先是解释了一个古老的&#xff0c;现在没什么人用数据结构----T-tree&#xff0c;因为现代的cpu到cache和到memory差异巨大&#xff0c;同时memory的容量也变大了。 T-tree 两个key标志着范围&#xff0c;决定到哪里找key&#xff0c;然后存的都是指针&#xff0c;指向pare…

CANopen | 对象字典OD 05 - 创建对象字典变量,映射到RPDO

文章目录一、前言二、实验目的三、对象字典OD四、通过RPDO修改变量rx_Value4.1、NMT指令让CANopen从站进入操作状态4.2、RPDO修改变量rx_Value一、前言 该章节的源代码地址&#xff1a;github 以上摘自《CANopen_easy_begin》的第7章。 二、实验目的 CANopen从站有一个变量…

【博学谷学习记录】超强总结,用心分享 | 架构师 MySql扩容学习总结

文章目录1. 停机方案2.停写方案3.日志方案4.双写方案&#xff08;中小型数据&#xff09;5.平滑2N方案&#xff08;大数据量&#xff09;1. 停机方案 发布公告 为了进行数据的重新拆分&#xff0c;在停止服务之前&#xff0c;我们需要提前通知用户&#xff0c;比如&#xff1a…

网络io与select,poll,epoll

一个形象的类比 水龙头等水 水龙头就是内核进程 等水复制到内核区 学生就是进行io的进程或线程 阻塞io 学生在那里 等水来 非阻塞io 学生看数据没准备好,先回寝室,一会儿再过来检查下,看水准备好没 多路复用io 阿姨帮忙看着水龙头,等来水的时候通知学生 前面三个都是同步…

HQChart实战教程60-如何定制十字光标输出内容

HQChart实战教程60-如何定制十字光标输出内容 十字光标效果图步骤:1. 注册事件2. 外部格式化输出内容Y 轴输出说明X轴输出说明HQChart插件源码地址完整的demo源码十字光标 当鼠标或手势在K线上移动的时候, 会出现一个十字线,已经X轴和Y轴对应数值的输出。X轴输出日期+时间 …

2.1.1网络io与io多路复用select/poll/epoll

关于网络io&#xff0c;我们可以通过一个服务端-客户端的示例来了解&#xff1a; 这是一段TCP服务端的代码&#xff1a; #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include &l…

Android系统启动流程--zygote进程的启动流程

在上一篇init进程启动流程中已经提到&#xff0c;在init中会解析一个init.rc文件&#xff0c;解析后会执行其中的命令来启动zygote进程、serviceManager进程等&#xff0c;下面我们来看一下&#xff1a; //文件路径&#xff1a;system/core/init/init.cppstatic void LoadBoot…

电子商务转化率对你来说有多重要?

有许多电子商务企业遇到了瓶颈期&#xff0c;低转化率并不总是表明您的业务出了大问题&#xff0c;但它们确实表明您可以做得更多&#xff0c;赚得更多。在文中&#xff0c;我们将讨论电子商务转化率对你的重要性&#xff0c;以及提高电子商务转化率的最佳久经考验的方法。 一、…

如何选择IT培训机构?

作为学习IT技术的一种方式、平台&#xff0c;培训班存在已久。而作为国内培训机构的老大哥&#xff0c;北大青鸟于1999年成立&#xff0c;是IT职业教育的开创者&#xff0c;专注于软件、网络、营销等各个IT技术领域&#xff0c;为IT行业输送了奖金百万技术人才。24年以来&#…

网络编程学习,项目er图

https://note.youdao.com/s/FEoXGdFe 思路&#xff1a;将每位上的数存放在一个数组里&#xff0c;每次从最高位开始遍历&#xff0c;先找到最大的位数&#xff0c;再根据是否为0&#xff0c;得到要加的数 网络编程是使用Java语言编写网络应用程序的过程。Java提供了一系列API&…