Flutter 问题小结

2023-10-18  本文已影响0人  Abner_XuanYuan
1、Invalid module name: 'XXX' - must be a valid Dart package name (lower_case_with_underscores).

原因:创建 flutter 项目的根目录不能有大写。
例:新创建的 Flutter 项目名称为 androidFlutter01 时会出现以上错误,当把名称改为 androidflutter01 时则可以。

2、setState 方法原理

流程
1、mounted 条件判断
生命周期判断,在 setState 之前不能调用 dispose;
是否可以刷新 (if(mounted){setState(() {});});
2、添加到脏列表 (_dirty = true)
脏列表是待更新的列表;
_dirty 为 true 时添加,false是返回;
3、管理类
让管理类方法知道自己需要被重新构建;
调用 owner.scheduleBuildFor(this);
4、调用 window.scheduleFrame() 方法;
5、更新UI
WidgetsBinding.drawFrame 中调用 buildScope 重构 element;

总结
可见,setState() 过程主要工作是记录所有的脏元素,添加到 BuildOwner 对象的 _dirtyElements 成员变量,然后调用 scheduleFrame 来注册 Vsync 回调。 当下一次 Vsync 信号的到来时会执行 handleBeginFrame() 和 handleDrawFrame() 来更新 UI。

参考链接 >>>

3、Flutter 生命周期
Flutter

StatelessWidget

class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

对于 StatelessWidget 来说,生命周期只有构造方法和 build 方法。build 方法用来创建 Widget,它只在每次刷新页面时调用。

StatefulWidget

class HomePage extends StatefulWidget {
  final String title;

  HomePage({Key? key, required this.title}) : super(key: key) {
    print('Widget 构造方法');
  }

  @override
  State<HomePage> createState() {
    print(' Widget 的 createState 方法');
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  _HomePageState() {
    print('State 构造方法');
  }

  @override
  void initState() {
    print('State 的 initState 方法');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('State 的 didChangeDependencies 方法');
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    print('State 的 build 方法');
    return Container();
  }

  @override
  void dispose() {
    print('State 的 dispose 方法');
    super.dispose();
  }
}

生命周期大致分为以下阶段:
1、初始化阶段
1、Widget 构造方法。
2、Widget 的 CreateState 方法:该方法为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 调用时会立即执行 CreateState 方法。
3、State 构造方法。
4、State 的 initState 方法:State 初始化调用。常用来变量初始化赋值,从服务器获取数据。

2、组件创建阶段
1、didChangeDependencies 方法:它可执行多次。在初始化 initState 后调用、依赖的全局 State 发生变化时会调用(如:语言、主题等)、组件可见状态发生变化的时候会调用。
2、State 的 Build 方法:可以调用多次。初始化后绘制界面调用;调用 setState 方法会调用(调用 setState 会重新调用 Build 进行渲染,setState 方法内部主要是利用 _element(本质是就是 context 对象) 调用 markNeedsBuild)。
3、didUpdateWidget:可以调用多次。组件状态发生改变的时候会调用,该方法调用之后一定会再调用本组件的 Build 方法。

3、组件多次 Build
didChangeDependencies、setState、didUpdateWidget 都可以触发 Build,需要着重注意。

4、组件销毁阶段
1、deativate:可执行多次。在组件被移除节点后会被调用,如果该组件未被插入到其他节点时,会继续调用 dispose 方法,永久删除。
2、dispose:在永久删除对象的时候调用,通常在此方法中释放资源;

StatefulWidget 为什么将 State 和 Widget 分开?
因为性能, State 管理状态(可以理解为 Controller),Widget 是 UI(即 View)。 根据状态变化每次生成 Widget 可以节省内存,即不必每次创建状态对象 State。

路由跳转时方法顺序
1、Widget A 打开 Widget B: Navigator.push(B)
B 构造函数 ---> B initState ---> B didChangeDependencies ---> B build ---> A deactive ---> A didChangeDependencies。
2、Widget B 退出: Navigator.pop
A deactive ---> A didChangeDependencies ---> A build ---> B deactive ---> B dispose

4、多线程

Dart 是单线程语言,它没有像 OC、Swift 那样复杂的多线程控制。Dart 中的异步操作首要运用 Future 以及 async、await,async 和 await 是要一起运用的。
Future: 延时操作的一个封装,能够将异步使命封装为 Future 目标,一般经过 then() 来处理回来的成果。
async:用于标明函数是一个异步函数,其回来值类型是 Future 类型。
await:用来等候耗时操作的回来成果,这个操作会堵塞到后边的操作。

协程:可分为无线协程和有线协程。
无线协程:无线协程在脱离当时调用方位时,会将当时变量放在堆区,再次回到当时方位时,会持续从堆区中获取到变量。一般在履行当时函数时就会将变量直接分配到堆区,async--await 也是一种无线协程。
有限协程:有线协程会将变量持续保存在栈区,在回到指针指向的脱离方位时,会持续从栈中取出调用。

asyn--await 原理
以 async--await 为例,协程在履行时,履行到 async 则表明进入一个协程,会同步履行 async 的代码块。async 的代码块本质上也相当于一个函数,并且有自己的上下文环境。当履行到 await 时,则表明有使命需要等候,CPU 则去调度履行其他 IO,也便是后边的代码或其他协程代码。过一段时间 CPU 就会轮循一次,看某个协程是否使命已经处理完结,有回来成果能够被持续履行,如果能够被持续履行的话,则会沿着上次脱离时指针指向的方位持续履行,也便是 await 标志的方位。
由于并没有开启新的线程,只是进行 IO 中断改动 CPU 调度,所以网络请求这样的异步操作能够运用 async--await,但如果是履行很多耗时同步操作的话,应该运用 isolate 开辟新的线程去履行。

Isolate
Isolate 是 Dart 平台多线程方案,但和一般 Thread 不同的是,isolate 具有独立的内存,isolate 由线程和独立内存构成。正是由于 isolate 线程之间的内存不共享,所以 isolate 线程之间并不存在资源抢夺的问题,所以也不需要锁。isolate 能够很好的运用多核 CPU,来进行很多耗时使命的处理。
isolate 组的使用,使得 Isolate 组中的 isolate 共享各种内部数据结构,这些数据结构则表明正在运转的程序。这使得组中的单个 isolate 变得愈加简便,使用速度更快,内存消耗更小。

compute
dart 中的 Isolate 比较重量级,UI 线程和 Isolate 中的数据的传输比较复杂,因此 Flutter 为了简化用户代码,在 Foundation 库中封装了一个轻量级 compute 操作来完成。这个运用十分方便,并且能够直接回来值。

经验:操作耗时很短的用 Future;耗时较长但只要一次回来的用 compute;耗时较长且有多次回来的用 Isolate。

参考链接 >>>

5、Flutter 和 RN

语言
Flutter 开发语言是 Dart,是通过 AOT 编译的,可以快速编译成原生代码,直接跟系统进行通信。UI 各种嵌套,可读性较差;RN 开发语言是 React 是 JS 语法。

原理
RN:利用 JS 来做桥接,将 JS 调用转为本地代码调用,底层代码会调用不同平台原生代码,会存在一套代码在不同平台展示出不同的样式问题,差异比较大。一些 API、属性只支持某个特定的平台,经常在代码判断平台使用不同的 API、属性等,兼容性差。
Flutter:自己实现了一套 UI 框架,是底层自己实现图形设备接口,直接在渲染系统上画 UI,兼容性高,一个页面是一个整体,效率也高。

代码调试
RN:因为 RN 代码是 JS 语言,所以需要在浏览器中调试。
Flutter:可以在编辑器中直接下断点调试,相对于 RN,Flutter 这种调试方式比较方便,更符合原生调试习惯。

集成
RN:过桥接方式,步骤比较麻烦。
Flutter:集成到项目中比较简单,可以将 Flutter 代码编译成一个 Module 直接引入到已有的原生项目中,包括后面原生和 Flutter 交互可以自己写插件。

包大小
Flutter:打包后体积会增大。IOS 项目体积会比较大,因为需要额外集成绘图引擎(Skia)。
RN:体积会增大,

性能
Flutter 在高低端机的 CPU 上的表现都优于 RN,尤其在低端的手机优势更明显。

三方库
因为 RN 出来时间比较早,且有些库还可以跟 Web 共用,所以在这方面 RN 要优于 Flutter。

热更新
RN 支持热更新,Flutter 不支持。

参考链接 >>>

6、Flutter 性能优化

1、减少 Widget 重建:Widget 重建是 Flutter 应用中的一个常见性能问题,因为它会导致不必要的渲染和绘制。减少 Widget 重建的方法包括使用 const 构造函数创建常量 widget、使用 Key 标识 Widget、使用 StatefulWidget 等。
2、避免过多的 UI 重绘:避免过多的 UI 重绘可以提高应用的性能。可以使用 shouldRepaint 方法来判断是否需要重绘;使用 ClipRect 方法避免不必要的绘制;使用 offstage 避免不必要的布局计算;使用 RepaintBoundary 避免重复绘制。
3、优化图片加载:在 Flutter 中,图片加载是一个常见的性能问题。可以使用缓存或预加载技术来优化图片加载,以减少不必要的网络请求。还可以合理的使用压缩图片,使用占位图片。
4、避免过多的网络请求:过多的网络请求会导致应用响应速度变慢。可以使用缓存、减少请求次数、合并网络请求的方法来减少网络请求,适当的通过 webSocket 替换网络请求,从而提高应用的性能。
5、优化布局:布局是应用性能的重要因素之一。可以使用 Flex 布局或者使用 CustomMultiChildLayout 等方法来优化布局;使用 IndexedStack 可以在多个 Widget 之间快速切换、使用 AspectRatio 可以控制 Widget 的宽高比,从而避免不必要的布局计算,以提高应用的性能。
6、使用异步操作:在应用中使用异步操作可以避免 UI 卡顿的问题。可以使用 Future、Stream 等异步操作来优化应用的性能。
使用 Future 可以在后台线程执行耗时操作,从而避免阻塞主线程。可以使用 async 和 await 关键字来实现异步操作。
使用 Isolate 可以在多个线程执行耗时操作,从而避免阻塞主线程。可以使用 Flutter 自带的 compute 函数来实现 Isolate。
使用 Stream 可以实现数据流的异步处理,从而避免阻塞主线程。可以使用 StreamController 来创建和管理 Stream。
使用 async/await 和 Future.wait 可以同时执行多个异步操作,并等待所有操作完成后再统一处理结果。
7、避免过多的内存使用:过多的内存使用会导致应用响应速度变慢。可以使用 Flutter 自带的内存分析工具来查找内存泄漏问题,从而避免过多的内存使用。
避免不必要的对象创建可以减少内存使用,从而提高应用性能。可以使用 const 关键字来避免重复创建相同的对象。
对于一些较大的图片,可以使用压缩技术来减少图片大小,从而减少内存使用。
及时释放无用资源可以避免内存泄漏,从而减少内存使用。可以使用 dispose 方法来释放无用资源。
使用缓存技术可以避免重复创建相同的对象,从而减少内存使用。可以使用 Flutter 自带的 ImageCache 类来实现图片缓存。
8、使用热重载:热重载是 Flutter 的一个重要特性,它可以快速预览和调试应用。使用热重载可以提高开发效率,从而加速应用的开发过程。

7、Flutter 渲染

渲染原理
Flutter 界面是由 Widget 组成的,所有 Widget 组成 Widget 树。Flutter 的 UI 绘制包含了三个元素,Widget,Element 和 RenderObject。这三个元素分别组成了三棵树:Widget 树,Element 树和 RenderObject 树,界面更新时会更新 Widget Tree,然后再更新 Element Tree,最后更新 RenderObject Tree。
接下来的渲染流程,Flutter 渲染在 Framework 层会有 Build、Wiget Tree、Element Tree、RenderObject Tree、Layout、Paint、Composited Layer 等几个阶段。将 Layer 进行组合,生成纹理,使用 OpenGL 的接口向 GPU 提交渲染内容进行光栅化与合成,是在 Flutter 的 C++ 层,使用的是 Skia 库。包括提交到 GPU 进程后,合成计算,显示屏幕的过程和 iOS 原生基本是类似的,因此性能也差不多。
Flutter 在缓冲策略上与 iOS 原生稍有不同的是:Flutter 使用的是 VSync + 三重缓冲 (Triple Buffering),在 iOS 原生使用的是 VSync + 双重缓冲 (Double Buffering)。

Flutter 渲染
Widget 树:配置信息,用来描述 UI 特征,比如尺寸,颜色,位置。
Element 树:element 是 widget 的实际实例,它同时持有了 widget 和 renderObject 的引用,用来决定是否进行 UI 更新。
RenderObject 树:UI 更新的执行者,保存了元素的大小,布局等信息。它才是真正调用渲染引擎去进行更新的对象。
注:只有继承自 RenderObjectWidget 的 Widget 才能创建 RenderObject 并加入 RenderObject 树,被渲染

渲染管道
Flutter 渲染管道涉及到多重步骤:
首先得到用户的输入,然后开始构建组件并去渲染它们;
1、Layout(布局),它的作用是在屏幕上确定每个组件的大小和位置;
2、Paint(绘制),它提供一系列方法把组件渲染成用户看到的样子;
3、Composite(图层合成)它把绘制步骤生成的图层或者纹理堆叠在一起,按照顺序组织它们,以便它们可以高效的在屏幕上进行呈现,图层合成是组件最终呈现在屏幕上之前很关键的也是最后的一个优化步骤;
4、最后是光栅化,它把抽象的表现映射成物理像素显示在屏幕上。

渲染管道
上一篇下一篇

猜你喜欢

热点阅读