从Dart的角度去取理解一个flutter的启动

代码

void main(List<String> args) {
  // 1-runApp: Flutter 框架提供的全局函数。参数必须是一个 Widget。
  //它会把这个 Widget 挂载到 Flutter 的根部 UI 树,并启动整个应用的渲染流程。
  // 2-const MyApp(): 创建一个MyApp的实例
  runApp(const MyApp());
}

const img1 = "https://linresource.uk/img/13399635147767705.jpg";
const img2 = "https://linresource.uk/img/13399635169004637.jpg";

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Center(child: const Text('有无状态组件'))),
        body: Center(child: const BannerWidget()),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}

class BannerWidget extends StatelessWidget {
  const BannerWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Image.network(img1);
  }
}

执行流程(简化时序)

main()
 ↓
runApp(const MyApp())
 ↓
Flutter 创建 MyApp → 调用 MyApp.build()
 ↓
MyApp.build() 返回 MaterialApp(内部包裹 Scaffold)
 ↓
Scaffold.body 里创建 BannerWidget → 调用 BannerWidget.build()
 ↓
BannerWidget.build() 返回 Image.network(img1)
 ↓
Flutter 渲染引擎根据 Widget 树绘制 UI
 ↓
异步加载网络图片 → 加载完成后触发局部重绘

程序入口

void main(List<String> args) {
  runApp(const MyApp()); 
}

Dart 程序入口:执行 main() 函数(所有 Dart 程序都从 main 开始)。

args:命令行参数(一般 Flutter 移动端不会用到)。

runApp()

  • Flutter 框架提供的全局函数。
  • 参数必须是一个 Widget(你传入的是 const MyApp())。
  • 它会把这个 Widget 挂载到 Flutter 的根部 UI 树,并启动整个应用的渲染流程。

const MyApp():调用 MyApp 这个类的常量构造函数(StatelessWidget 支持 const)。

常量构造函数

构造函数声明为 const

class MyApp extends StatelessWidget {
  const MyApp({super.key});
}

这样声明表示:

  • 如果构造参数({super.key})全是编译期常量,Dart 编译器可以在编译阶段就创建对象(而不是运行时)。
  • 这个对象会被当作不可变值重用(节省内存、提升性能)。

为什么不可以变值可以提升性能

内存可重用(减少创建开销)

假设你写了:

const MyApp();
const MyApp();

Dart 编译器会在编译期就直接生成一个唯一的 MyApp 实例,并且两个地方都指向同一块内存地址
这样:

  • 少创建对象 → 减少内存分配和垃圾回收压力。
  • 不需要复制数据 → 节省 CPU 时间。

而可变对象(mutable)可能会在运行时被修改,所以每次用都得创建一个新的,不能重用。

Flutter 可以跳过 UI 重建

Flutter 构建 UI 时,会比较新旧 Widget 树:

  • 如果发现是同一个对象(内存地址相同),且是不可变的,就直接跳过 build 过程
  • 如果是新对象(哪怕内容一样),Flutter 也会认为它变了,触发重建。
// 不可变且 const
runApp(const MyWidget(title: 'Hello'));

// 每次重建都会用同一个对象


// 每次都是新对象
runApp(MyWidget(title: 'Hello'));

在 UI 一样的情况下,第一种跳过了大量不必要的 widget build,所以性能更好。

调用时的情况

如果类的构造函数声明成 const,在调用的时候你可以用 const 关键字,但不是必须用。

const(推荐)

runApp(const MyApp());

好处:

  • 编译器会直接重用这个对象实例,而不是每次运行时重新创建。
  • 在 widget 树中,如果 const 对象没变,Flutter 会跳过重建,提高渲染效率。

不带 const

runApp(MyApp());

效果上 UI 是一样的,但:

  • 每次都会在运行时创建一个新的实例。
  • 对于 Flutter 的 Widget Diff(比较新旧 Widget 树) 来说,这会被当作“新对象”,可能会触发不必要的重建。

MyApp 类(无状态组件)

代码说明

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  • 继承 StatelessWidget → 说明这是一个无状态组件,数据不可变,UI 只能依赖构造函数参数。
  • super.key:把 key 传给父类,用于 Flutter 的 widget 树比对优化(Widget Diff 算法)。
@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Center(child: const Text('有无状态组件'))),
      body: Center(child: const BannerWidget()),
    ),
    debugShowCheckedModeBanner: false,
  );
}

build() 方法:StatelessWidget 必须实现的抽象方法。

MaterialApp

  • Flutter 提供的应用顶层容器(包含主题、路由、国际化等)。
  • home 属性:应用启动后显示的第一个页面。

Scaffold

  • 提供页面结构(AppBar、Body、FloatingActionButton等)。

AppBar

  • 应用顶部栏。
  • title 属性是一个 Widget,这里用 Center + Text 让标题居中。

body

  • 页面主要内容区,这里用 Center 居中一个 BannerWidget

BannerWidget 类(无状态组件)

class BannerWidget extends StatelessWidget {
  const BannerWidget({super.key});
  • 依然是无状态组件,没有持久化数据。

  • const 构造函数可减少重复创建 widget 的开销。

@override
Widget build(BuildContext context) {
  return Image.network(img1);
}
  • build() 返回一个网络图片组件。

  • Image.network(img1)

    • 通过 URL 加载网络图片。

    • 运行时 Flutter 会先显示空白或占位,等网络请求完成后再绘制图片。

    • 图片加载是异步的,但 build() 是同步执行的(UI 先返回结构,渲染引擎异步填充图片内容)。

关于build()

哪些类需要实现 build()

只有 Widget 才需要实现 build()

  • 在 Flutter 中,不是所有类都要有 build() 方法。

  • 只有继承了 StatelessWidgetStatefulWidget(以及它们的父类 Widget)的类,才会要求你实现 build() 方法。

    • Widget 是一个抽象类,他还有其他许多子类
    • 这两个类是构建自定义 UI 的主要入口。其它 Widget 类型更多是作为框架内部或特殊场景使用,你在做业务 UI 时很少直接继承它们。
  • build() 是 Flutter 框架在构建 Widget 树时调用的,用来描述 UI 长什么样

  • 用一个简单的例子:

    void main() {
      runApp(const MyApp()); // 入口
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text('Hello');
      }
    }
    

    执行过程大概是:

    1. runApp(MyApp()) → Flutter 框架接管程序。
    2. 框架会把 MyApp 当成根 widget,交给 Flutter 渲染系统。
    3. 框架调用 MyApp.build(context) → 得到一个 Text widget。
    4. Flutter 根据这个 widget 构建 Element 树RenderObject 树,最终在屏幕绘制 UI。
  • 总结:

    • Flutter 里“作为启动调用的类”如果是一个 Widget,就必须实现 build() 方法
    • 框架会在需要构建 UI 时自动调用它,不需要你手动去调。

什么时候调用build()

const MyApp()const BannerWidget() 这两个地方虽然都是 创建 Widget 的子类实例,但它们不会在构造时立即调用 build() 方法

这里只是调用构造函数,得到一个 Widget 实例(纯数据对象),它还没有被挂载到 Widget 树上,所以不会立刻执行 build()

Flutter 中 Widget 是“描述”

  • 你写的 StatelessWidget / StatefulWidget 只是一个UI 描述对象,并不是真正的 UI 元素。
  • Flutter 的渲染引擎会拿到这个描述,然后创建对应的 ElementRenderObject 去真正绘制界面。
  • build() 方法就是在 Element 阶段调用的,用来生成下一层 Widget 树。

build() 的调用流程是:

  • runApp() 接收一个根 Widget(这里是 MyApp)。
  • Flutter 框架创建一个 Element(StatelessElement 或 StatefulElement)。
  • Element 调用 Widget 的 build() 来生成子 Widget。
  • 再递归生成所有子 Widget 的 Element 并 build。

×

喜欢就点赞,疼爱就打赏