惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
S
SegmentFault 最新的问题
Martin Fowler
Martin Fowler
罗磊的独立博客
T
The Blog of Author Tim Ferriss
N
Netflix TechBlog - Medium
大猫的无限游戏
大猫的无限游戏
Hugging Face - Blog
Hugging Face - Blog
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Heimdal Security Blog
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
T
Tailwind CSS Blog
AWS News Blog
AWS News Blog
雷峰网
雷峰网
PCI Perspectives
PCI Perspectives
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Register - Security
The Register - Security
N
News | PayPal Newsroom
C
CERT Recently Published Vulnerability Notes
Microsoft Security Blog
Microsoft Security Blog
Attack and Defense Labs
Attack and Defense Labs
T
Tenable Blog
博客园 - 【当耐特】
Vercel News
Vercel News
GbyAI
GbyAI
博客园 - 司徒正美
量子位
T
Threat Research - Cisco Blogs
The Cloudflare Blog
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Hacker News - Newest:
Hacker News - Newest: "LLM"
TaoSecurity Blog
TaoSecurity Blog
T
Troy Hunt's Blog
Y
Y Combinator Blog
P
Proofpoint News Feed
L
LINUX DO - 最新话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Jina AI
Jina AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
月光博客
月光博客
Apple Machine Learning Research
Apple Machine Learning Research

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 MediaCodec 硬编码之相机内容编码成 H264 文件 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
Flutter 状态管理之 InheritedWidget 使用和分析
音视频开发进阶 · 2024-02-01 · via 音视频开发进阶

一个专注音视频领域的小圈子

初识 Flutter 的 Demo 是一个点击按键然后数字增加的小应用,这小应用就涉及到了 Flutter 中的状态管理,核心就是 setState 方法。 setState 方法用于通知 Flutter 更新 Widget 的状态,比如重建当前 Widget 及其子树,它是从上到下进行通知的,但作用域仅停留在当前的 StatefulWidget 范围内。

如果有两个同级的 StatefulWidget ,比如 WidgetA 和 WidgetB ,当 WidgetA 的数据发生改变,想要通知 WidgetB 对应改变。

如果是 Android 的应用开发,采用 MVP 模式的话,需要 Controller 同时持有 WidgetA 和 WidgetB ,当 WidgetA 状态改变时,通过 Controller 对应改变 WidgetB 相关状态就行了。

而在 Flutter 中基于 InheritedWidget 控件,通过相应的封装库就可以实现 MVVM 模式的开发,相当快捷方便。

通过如下的例子来学习 InheritedWidget 控件的使用:左边栏和右边栏是两个同级的 Widget ,右边栏中点击修改名称,左边栏中显示对应的修改内容。

因为 InheritedWidget 是从上到下进行数据共享、传递的,所以要把 InheritedWidget 作为根节点,需要共享数据的节点作为子节点。

通过继承 InheritedWidget 实现自定义的控件类:

class DataModelWidget extends InheritedWidget {
  const DataModelWidget(
      {super.key,
      required super.child,
      required this.name,
      required this.changeName});

  // 当数据发生改变时,才会通知到子控件
  @override
  bool updateShouldNotify(covariant DataModelWidget oldWidget) {
    return oldWidget.name != name;
  }

  // 定义 static 快捷方法,方便在子控件中获取共享数据
  // 当子控件通过 of 方法使用了 InheritedWidget 中的数据,注册依赖关系
  // 此时数据变动会触发 didChangeDependencies 方法
  static DataModelWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<DataModelWidget>();
  }

  // 共享的数据
  final String name;
  final Function changeName;
}

要传递的数据就是 name 变量,在一个子控件中去修改 name 的值,在另一个子控件中显示修改内容。这里为了方便直接把要传递的数据放在了 Widget 里面,也可以封装对应的 Model 类,然后 Widget 持有 Model 类会更友好一些。

当数据发生改变时,会通过 updateShouldNotify 方法通知到子控件的 didChangeDependencies 方法。

didChangeDependencies 方法指的是子控件是否有依赖父 Widget 中 InheritedWidget 的数据,如果有就会响应回调。

子控件通过 of 静态方法获取 InheritedWidget 的时候调用了 dependOnInheritedWidgetOfExactType 方法,该方法会将 InheritedWidget 和获取数据的子控件进行注册,这才有了依赖关系。

child 参数就是接下来要写的子控件内容了。

class _DashboardState extends State<Dashboard> {
  String _name = "glumes";

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Row(
            children: <Widget>[
              // 初始化 InheritedWidget 并传递对应参数
              DataModelWidget(      
                name: _name,
                changeName: _changeUserName,
                child: const ContentBoard(),
              )
            ],
          ),
        ),
      ),
    );
  }
  // 右边栏调用 _changeUserName 方法修改共享数据的值
  void _changeUserName(String userName) {
    setState(() {
      _name = userName;
    });
  }
}

把子控件封装在 ContentBoard 中,分别是:

// 左边栏
Text("名称:${DataModelWidget.of(context)!.name}"),

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  Log.d("left sidebar change dependencies");
}

// 右边栏
ElevatedButton(
  onPressed: () {
    DataModelWidget.of(context)!.changeName("change name ${count.toString()}");
    count++;
  },
  child: const Text("修改名称")),

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  Log.d("right sidebar change dependencies");
}

由于左边栏和右边栏都调用到了 of 方法里的 dependOnInheritedWidgetOfExactType ,所以都注册上了依赖关系,didChangeDependencies 方法两边都会回调打印。

但是右边栏中并没有显示要共享的数据,只是内部改了它的值,实际上并不依赖 InheritedWidget 的数据,这时可以采用 getElementForInheritedWidgetOfExactType 方法获取 InheritedWidget ,它不会注册依赖关系,改造如下:

static DataModelWidget? of2(BuildContext context) {
  return context.getElementForInheritedWidgetOfExactType<DataModelWidget>()!.widget
      as DataModelWidget;
}

这样在右边栏中就不会响应 didChangeDependencies 的回调了。

回顾一下整体流程:在子控件右边栏中调用 changeName 方法改变了 InheritedWidget 中的数据,并且调用 setState 方法,触发了 Widget 的重建和刷新。而左边栏子控件中依赖了 InheritedWidget 中的数据,从而也随着刷新了。

重点分析 Flutter 三棵树和渲染流程以及 InheritedWidget 依赖关系的注册。

众所周知,Flutter 中有三棵树,Widget 树、Element 树、Render 树,如下图所示:

Widget 树就是日常开发中写的内容,它是描述 UI 元素的配置信息,实际渲染的内容并不是和 Widget 树一一对应的,比如上图中 Render 树的节点明显比 Widget 树少很多,是因为有些 Widget 是不用渲染的。

Widget 树和 Element 树是一一对应的,Render 树和 Element 树中的 RenderObjectElement 是一一对应的。

Widget 树的构建成本非常低,所以在运行过程中会经常刷新,也就是调用 setState 方法来重建,但是 Render 树就不会那么频繁了,这中间就是通过 Element 树来作为沟通的桥梁。

Element 树会在页面重建时比较 Widget 树前后的数据状态,当 Widget 前后不一致时会新创建 Element 并更新 Render 树,如果只是 Widget 的数据发生了改变,则不需要重建 Render 树,只更改对应的数据就行。

Element 树中有两种基本类型,ComponentElementRenderObjectElement 。其中 ComponentElement 不参与渲染,实际参与渲染的是 RenderObjectElement

关于 Element 类继承关系结构图可以参考如下:

Render 树和 Widget 树不是一一对应的,因为有些 Widget 不需要渲染,比如 StatefullWidget 和 StatelessWidget ,而需要渲染的 Widget 都会继承自 RenderObjectWidget 。

比如上图中 Container 就继承自 StatelessWidget ,在 Render 树中没有对应的位置,是由它的 child Widget 来完成渲染操作的。

Text 同样继承自 StatelessWidget ,在 Render 树中没有对应的位置,是由内部的 RichText 来完成渲染的,而 RichText 继承自 MultiChildRenderObjectWidget ,间接继承 RenderObjectWidget 。

RenderObjectWidget 又可以细分两类:MultiChildRenderObjectWidgetSingleChildRenderObjectWidget 。常见的 Row、Column、Text 都是继承 MultiChildRenderObjectWidget ,而 SizedBox、Center、Padding 都是继承 SingleChildRenderObjectWidget 。

关于 Widget 类继承关系结构图可以参考如下:

像 Container、Row、Text 这些 Widget 实际上是一些组合型的节点,最终返回的 Widget 是由 child Widget 来完成的,比如 Container 的 build 方法,最终返回的 current 都是其他的 Widget 。

// Container 的 build 方法
Widget build(BuildContext context) {
  Widget? current = child;

  final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
  if (effectivePadding != null) {
    current = Padding(padding: effectivePadding, child: current);
  }

  if (color != null) {
    current = ColoredBox(color: color!, child: current);
  }

  if (decoration != null) {
    current = DecoratedBox(decoration: decoration!, child: current);
  }

  if (constraints != null) {
    current = ConstrainedBox(constraints: constraints!, child: current);
  }

  if (margin != null) {
    current = Padding(padding: margin!, child: current);
  }

  if (transform != null) {
    current = Transform(transform: transform!, alignment: transformAlignment, child: current);
  }

  return current!;
}

渲染流程最早的起点是 main 函数里面的 runApp 方法,传入的参数并不是 Widget 树的根节点,而是根节点的子节点。

Widget 树的根节点是由 Flutter 框架定义的 RootWidget

void runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  binding
    ..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
    ..scheduleWarmUpFrame();
}

void scheduleAttachRootWidget(Widget rootWidget) {
  Timer.run(() {
    attachRootWidget(rootWidget);
  });
}

// 最终把传入的 Widget 挂到 RootWidget 下面
void attachRootWidget(Widget rootWidget) {
  attachToBuildOwner(RootWidget(
    debugShortDescription: '[root]',
    child: rootWidget,
  ));
}

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

在 attachToBuildOwner 方法内会创建 Element 树的根节点 RootElement

@override
RootElement createElement() => RootElement(this);

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

RootWidget 的 createElement 会创建对应的 RootElement 。实际上每个 Widget 的 createElement 都会创建对应的 Element ,因为 Widget 树和 Element 树是一一对应的 。

接下来调用 Element 的 mount 方法将该 Element 挂载到 Element 树对应的位置上。因为是 RootElement ,所以 parent 和 slot 都是 null 。

// RootElement 的 mount 实现
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _rebuild();
  super.performRebuild(); // clears the "dirty" flag
}

// Element 基类的 mount 实现
void mount(Element? parent, Object? newSlot) {
  // 更新 parent 和 slot 的状态
  _parent = parent;
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active;
  _depth = _parent != null ? _parent!.depth + 1 : 1;
  if (parent != null) {
    // Only assign ownership if the parent is non-null. If parent is null
    // (the root node), the owner should have already been assigned.
    // See RootRenderObjectElement.assignOwner().
    _owner = parent.owner;
  }
  final Key? key = widget.key;
  if (key is GlobalKey) {
    owner!._registerGlobalKey(key, this);
  }
  _updateInheritance();
  attachNotificationTree();
}

void _updateInheritance() {
  _inheritedElements = _parent?._inheritedElements;
}

当 Element 第一次被添加到 Element 树的时候会触发 mount 方法。

不同 Element 的子类对于 mount 的实现有所不同,子类可以覆写 updatevisitChildreninsertRenderObjectChildmoveRenderObjectChildremoveRenderObjectChild 这些方法,但共同点是都要调用基类的 mount 方法。

RootElement 的 mount -> _rebuild -> updateChild 方法,updateChild 的作用在注释中已经说的很清楚了。

newWidget == null newWidget != null
child == null Returns null. Returns new [Element].
child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].
Element? _child;

void _rebuild() {
  try {
    // RootElement 首次创建新的 _child 
    _child = updateChild(_child, (widget as RootWidget).child, /* slot */ null);
  }
}  

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  final Element newChild;
  if (child != null) {
    // 省略一大串代码
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
}

对于 RootElement 的第一次创建,child 肯定为 null ,直接调用 inflateWidget 来创建新的 Element 并赋值给 _child 变量。

在 Element 基类的 mount 实现中有个 _updateInheritance 方法,该方法将 _inheritedElements 指向父 Element 的 _inheritedElements

// Element 基类中定义的变量 _inheritedElements 和 _dependencies
PersistentHashMap<Type, InheritedElement>? _inheritedElements;
Set<InheritedElement>? _dependencies;

// Element 基类中的实现
void _updateInheritance() {
  _inheritedElements = _parent?._inheritedElements;
}

如果是 InheritedElement ,则除了将 _inheritedElements 指向父 Element 的 _inheritedElements ,还会添加或者替换类型为 widget.runtimeType 的 InheritedElement 为当前的 InheritedElement 。

// InheritedElement 类中的实现
void _updateInheritance() {
  final PersistentHashMap<Type, InheritedElement> incomingWidgets =
      _parent?._inheritedElements ?? const PersistentHashMap<Type, InheritedElement>.empty();
  _inheritedElements = incomingWidgets.put(widget.runtimeType, this);
}

如果 InheritedElement 的父类有多个类型为 widget.runtimeTypeInheritedElement ,则都会替换成当前的这个,相当于子节点只能找到离它最近的那个 InheritedElement

比如 InheritedWidget 有个子类为 SharedDataWidget ,内部有个 String 类型的变量 name ,然后 Widget 树的结构为 SharedDataWidget(name = “A”) -> SharedDataWidget(name = “B”) -> Container -> Text(text = SharedDataWidget.name) ,此时 Text 显示的内容是字符串 B ,而不是 A 。

明确了 _inheritedElements 是从何而来的,接下来回到 dependOnInheritedWidgetOfExactTypegetElementForInheritedWidgetOfExactType 这两个方法,它们都来自 BuildContext 抽象类,而 Element 正好继承了该抽象类。

dependOnInheritedWidgetOfExactType 注册依赖关系的实现如下,找到父节点的 InheritedElement ,然后实现双向的依赖。

@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  // 如果 Widget 树上的父节点有 InheritedWidget ,那么 _inheritedElements 就不会为 null .
  final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
  if (ancestor != null) {
  // 当前节点和 InheritedWidget 注册依赖关系
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
  _dependencies ??= HashSet<InheritedElement>();
  // 当前节点添加依赖的 InheritedElement
  _dependencies!.add(ancestor);
  // InheritedElement 同样也添加被依赖的节点
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget as InheritedWidget;
}

// InheritedElement 更新依赖
class InheritedElement extends ProxyElement {

  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }

  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }
}

getElementForInheritedWidgetOfExactType 方法只是找到了父节点的 InheritedElement ,并没有注册依赖关系,不会回调 didChangeDependencies 方法。

@override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
  return ancestor;
}

注册依赖关系的作用体现在当调用 InheritedElement 的 updated 方法时会根据 updateShouldNotify 判断数据是否有更新,如果有则调用子节点的 didChangeDependencies 方法。

class InheritedElement extends ProxyElement {
  void updated(InheritedWidget oldWidget) {
    if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) {
      // 调用基类 ProxyElement 的 updated 方法,从而调用 notifyClients 方法
      super.updated(oldWidget);
    }
  }

  // 通知被依赖的子节点有内容更新了,调用子节点的 didChangeDependencies 方法
  @override
  void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }

  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
}

abstract class ProxyElement extends ComponentElement {
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
}

void didChangeDependencies() {
  markNeedsBuild();
}

didChangeDependencies -> markNeedsBuild 方法会标记当前 Element 需要重构,从而刷新对应的数据内容。

总结:

通过上面一大串的分析就梳理清楚了 InheritedWidget 的使用原理。

Elementmount 时会调用 _updateInheritance 方法,拿到父节点的 _inheritedElements 数据。

如果是 InheritedElement ,拿到父节点的 _inheritedElements 数据之外,还会将父节点某个类型的数据更改外自己所持有的。

Element 在调用 getElementForInheritedWidgetOfExactType 方法时,会根据拿到的父节点的 _inheritedElements 数据注册依赖关系,实现 InheritedElementElement 的双向注册。

InheritedWidget 中的数据改变时,通过 InheritedElement 来通知对应的子节点,调用 didChangeDependencies 方法,标记需要重建,从而实现数据状态的刷新。

参考:

  1. https://flutter.cn/docs/development/data-and-backend/state-mgmt/declarative
  2. https://juejin.cn/post/6845166891539906574
  3. https://www.jianshu.com/p/0f3bb5f6ed59
  4. https://juejin.cn/post/6943515602191384613

原创文章,转载请注明来源:    Flutter 状态管理之 InheritedWidget 使用和分析