第十三讲 异步操作与异步构建

前言:

这一讲对高性能需求是很重要的,响应式,异步,等,都是能够提升性能的手段,不过很多时候还是平铺直述,大力出奇迹才是正道。

一、总览

本讲聚焦 Flutter 中异步编程的核心技术栈,解决「UI 构建依赖异步数据(如网络请求、文件读取、延时操作)」的核心问题:

  • 基础层:掌握 Futureasync/await 处理单次异步任务(如一次网络请求);
  • UI 层:通过 FutureBuilderStreamBuilder 将异步任务状态与 UI 联动,避免手动管理「加载中/成功/失败」状态;
  • 体验层:结合 LinearProgressIndicator/CircularProgressIndicator 实现异步过程的可视化反馈;
  • 核心目标:让你从「手动维护异步状态+更新UI」的繁琐逻辑中解放,用 Flutter 内置组件优雅处理异步场景。

再来一遍:

  1. 异步基础Future 处理单次异步任务,async/await 简化异步代码,需用 try/catch 捕获异常;
  2. 异步构建FutureBuilder 绑定单次异步任务,StreamBuilder 绑定持续数据流,核心是根据 AsyncSnapshot 的状态(connectionState/hasError/hasData)构建UI;
  3. 状态与反馈 :异步场景需覆盖「loading(进度条)、error(重试)、success(数据展示)」三状态,且需注意缓存 Future、关闭 StreamController 避免内存泄漏。

原理解读

  1. 异步任务源:所有需要耗时的操作(网络、文件、设备传感器等)是异步的起点;
  2. Future/Stream :Flutter 封装的异步数据载体(Future 对应「单次结果」,Stream 对应「持续数据流」);
  3. async/await:简化异步代码的语法糖,替代回调地狱,让异步代码像同步代码一样易读;
  4. 异步构建器FutureBuilder/StreamBuilder 监听异步任务状态,自动触发 UI 重建;
  5. 三状态管理:异步任务的通用生命周期(加载中→成功/失败),是异步 UI 构建的核心逻辑;
  6. 进度指示器:可视化异步状态,提升用户体验。

二、核心技术拆解

2.1 异步基础:Future、async/await

核心概念
  • Future:表示「未来某个时间会完成的操作」,有三种状态:未完成(pending)、完成成功(completed with value)、完成失败(completed with error);
  • async:标记函数为异步函数,返回值自动包装为 Future
  • await:暂停异步函数执行,直到 Future 完成,只能在 async 函数中使用。
基础案例
dart 复制代码
// 模拟异步任务:延时获取数据
Future<String> fetchData() async {
  // 模拟网络请求延时2秒
  await Future.delayed(const Duration(seconds: 2));
  // 可注释下面一行,取消抛出异常,测试error状态
  // throw Exception("网络请求失败:服务器无响应");
  return "异步数据加载成功!";
}

// 调用异步函数的示例
void testAsync() async {
  print("开始执行异步任务...");
  try {
    String result = await fetchData();
    print("结果:$result");
  } catch (e) {
    print("异常:$e");
  }
}
注意事项
  1. async 函数即使没有显式返回 Future,也会自动包装返回值(如 async () => 1 等价于 () => Future.value(1));
  2. 未处理的 Future 异常会导致应用崩溃,必须用 try/catch 捕获,或通过 Future.catchError() 处理;
  3. await 只能在 async 函数内使用,不能在 main 函数(非 async)直接使用(需套 main() async {})。

2.2 异步构建:FutureBuilder

核心作用

Future 与 UI 绑定,根据 Future 的状态(loading/error/success)自动重建 UI,无需手动调用 setState

核心属性
属性名 类型 作用
future Future<T>? 要监听的异步任务(注意:每次build会重新创建Future,需用变量缓存)
builder Widget Function(BuildContext, AsyncSnapshot<T>) 根据异步状态构建UI的回调
initialData T? 初始数据(可选,未加载完成时显示)
基础案例
php 复制代码
class FutureBuilderDemo extends StatelessWidget {
  // 缓存Future,避免每次build重新创建
  final Future<String> _future = fetchData();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("FutureBuilder 示例")),
      body: Center(
        child: FutureBuilder<String>(
          future: _future,
          builder: (context, snapshot) {
            // 1. 加载中状态:ConnectionState.waiting
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 圆形进度条
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text("加载中...请稍候")
                ],
              );
            }
            // 2. 错误状态:hasError为true
            else if (snapshot.hasError) {
              return Text(
                "加载失败:${snapshot.error}",
                style: const TextStyle(color: Colors.red, fontSize: 18),
              );
            }
            // 3. 成功状态:hasData为true
            else if (snapshot.hasData) {
              return Text(
                snapshot.data!,
                style: const TextStyle(color: Colors.green, fontSize: 20),
              );
            }
            // 其他状态(兜底)
            else {
              return const Text("暂无数据");
            }
          },
        ),
      ),
    );
  }
}
注意事项
  1. 避免Future重建 :不要在 future 属性中直接写 fetchData()(每次build都会重新执行异步任务),需提前缓存为变量;
  2. ConnectionState判断ConnectionState.waiting 是加载中,done 表示任务完成(需再判断 hasError/hasData);
  3. 内存泄漏 :如果页面销毁时异步任务还未完成,需手动取消(如用 cancelable_future 库)。

2.3 异步构建:StreamBuilder

核心作用

监听 Stream(持续数据流),实时更新 UI(如实时聊天消息、倒计时、传感器数据),比 FutureBuilder 多「持续接收数据」的能力。

核心概念
  • Stream:数据流,可多次发射数据/错误/完成信号;
  • StreamController:创建和管理 Stream 的控制器(需手动关闭避免内存泄漏);
  • StreamBuilder:绑定 Stream,实时监听数据变化并重建 UI。
核心属性
属性名 类型 作用
stream Stream<T>? 要监听的数据流
builder Widget Function(BuildContext, AsyncSnapshot<T>) 实时构建UI的回调
initialData T? 初始数据
基础案例(倒计时示例)
less 复制代码
class StreamBuilderDemo extends StatefulWidget {
  @override
  _StreamBuilderDemoState createState() => _StreamBuilderDemoState();
}

class _StreamBuilderDemoState extends State<StreamBuilderDemo> {
  late StreamController<int> _streamController;
  late Stream<int> _countdownStream;

  @override
  void initState() {
    super.initState();
    // 1. 创建Stream控制器
    _streamController = StreamController<int>();
    // 2. 生成倒计时数据流(从10到0)
    _countdownStream = Stream.periodic(
      const Duration(seconds: 1), // 每隔1秒发射一次数据
      (count) => 10 - count,      // 计算倒计时数值
    ).take(11); // 只取11次(0-10)

    // 3. 将数据流添加到控制器
    _countdownStream.listen(
      (value) {
        _streamController.add(value);
        // 倒计时结束关闭控制器
        if (value == 0) _streamController.close();
      },
      onError: (e) => _streamController.addError(e),
      onDone: () => print("倒计时结束"),
    );
  }

  @override
  void dispose() {
    // 必须关闭控制器,避免内存泄漏
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("StreamBuilder 示例")),
      body: Center(
        child: StreamBuilder<int>(
          stream: _streamController.stream,
          initialData: 10, // 初始值
          builder: (context, snapshot) {
            // 加载中/正常状态
            if (snapshot.connectionState == ConnectionState.active ||
                snapshot.connectionState == ConnectionState.waiting) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 线性进度条(根据倒计时进度更新)
                  LinearProgressIndicator(
                    value: snapshot.data! / 10, // 进度0-1
                    minHeight: 8,
                    backgroundColor: Colors.grey[200],
                    color: Colors.blue,
                  ),
                  const SizedBox(height: 20),
                  Text(
                    "倒计时:${snapshot.data}秒",
                    style: const TextStyle(fontSize: 24),
                  ),
                ],
              );
            }
            // 错误状态
            else if (snapshot.hasError) {
              return Text(
                "错误:${snapshot.error}",
                style: const TextStyle(color: Colors.red, fontSize: 18),
              );
            }
            // 完成状态(倒计时结束)
            else {
              return const Text(
                "倒计时结束!",
                style: TextStyle(color: Colors.green, fontSize: 28),
              );
            }
          },
        ),
      ),
    );
  }
}
注意事项
  1. StreamController 必须在 dispose 中关闭(close()),否则会导致内存泄漏;
  2. StreamBuilder 会监听整个数据流生命周期,直到 Stream 关闭或页面销毁;
  3. 常用 Stream 生成方式:Stream.periodic()(定时发射)、Stream.fromIterable()(从集合生成)、StreamController(手动发射)。

2.4 进度条:Linear/CircularProgressIndicator

核心作用

可视化异步任务的进度,分为「确定进度」和「不确定进度」两种模式:

  • 不确定模式(默认):无限循环动画,用于未知耗时的任务(如网络请求);
  • 确定模式:设置 value(0-1),用于已知进度的任务(如文件下载)。
核心属性(通用)
属性名 类型 作用
value double? 进度值(0-1,null为不确定模式)
color Color? 进度条颜色
backgroundColor Color? 背景颜色(仅确定模式)
strokeWidth double 进度条宽度(Circular专属)
minHeight double 进度条高度(Linear专属)
基础案例
less 复制代码
// 不确定进度条(网络请求加载中)
const CircularProgressIndicator(
  color: Colors.blue,
  strokeWidth: 4,
);

// 确定进度条(文件下载,进度50%)
LinearProgressIndicator(
  value: 0.5,
  minHeight: 8,
  color: Colors.green,
  backgroundColor: Colors.grey[200],
);

三、综合应用案例

需求说明

实现一个「用户信息加载页面」:

  1. 进入页面后显示「圆形进度条+加载中文字」;
  2. 模拟网络请求加载用户信息(延时2秒);
  3. 加载成功:显示用户头像、名称、简介(用LinearProgressIndicator展示加载完成度);
  4. 加载失败:显示错误提示+重试按钮;
  5. 额外实现一个实时刷新的「数据更新倒计时」(StreamBuilder)。

完整代码

less 复制代码
import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '异步操作综合案例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const AsyncComprehensiveDemo(),
    );
  }
}

// 模拟用户数据模型
class User {
  final String name;
  final String avatar;
  final String desc;

  User({required this.name, required this.avatar, required this.desc});
}

// 模拟异步请求:加载用户信息
Future<User> fetchUserInfo() async {
  await Future.delayed(const Duration(seconds: 2));
  // 注释下面一行,测试成功状态;取消注释,测试错误状态
  // throw Exception("加载失败:网络超时");
  return User(
    name: "Flutter开发者",
    avatar: "https://img.icons8.com/fluency/96/000000/user.png",
    desc: "专注Flutter异步编程与UI构建",
  );
}

// 综合案例页面
class AsyncComprehensiveDemo extends StatefulWidget {
  const AsyncComprehensiveDemo({super.key});

  @override
  _AsyncComprehensiveDemoState createState() => _AsyncComprehensiveDemoState();
}

class _AsyncComprehensiveDemoState extends State<AsyncComprehensiveDemo> {
  late Future<User> _userFuture;
  late StreamController<int> _refreshController;

  @override
  void initState() {
    super.initState();
    // 初始化异步任务
    _userFuture = fetchUserInfo();
    // 初始化刷新倒计时Stream(10秒后自动刷新)
    _refreshController = StreamController<int>();
    Stream.periodic(const Duration(seconds: 1), (count) => 10 - count)
        .take(11)
        .listen(
          (value) => _refreshController.add(value),
          onDone: () => _refreshController.close(),
        );
  }

  // 重试加载用户信息
  void _reloadUserInfo() {
    setState(() {
      _userFuture = fetchUserInfo();
    });
  }

  @override
  void dispose() {
    _refreshController.close(); // 关闭Stream控制器
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("异步综合案例"),
        actions: [
          // StreamBuilder:实时刷新倒计时
          StreamBuilder<int>(
            stream: _refreshController.stream,
            initialData: 10,
            builder: (context, snapshot) {
              if (snapshot.data == 0) {
                return TextButton(
                  onPressed: _reloadUserInfo,
                  child: const Text("立即刷新", style: TextStyle(color: Colors.white)),
                );
              }
              return Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16),
                child: Center(
                  child: Text(
                    "${snapshot.data}秒后刷新",
                    style: const TextStyle(color: Colors.white, fontSize: 12),
                  ),
                ),
              );
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: FutureBuilder<User>(
          future: _userFuture,
          builder: (context, snapshot) {
            // 1. 加载中状态
            if (snapshot.connectionState == ConnectionState.waiting) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const CircularProgressIndicator(
                    color: Colors.blue,
                    strokeWidth: 6,
                  ),
                  const SizedBox(height: 20),
                  const Text("正在加载用户信息..."),
                  const SizedBox(height: 10),
                  // 线性进度条(不确定模式)
                  const LinearProgressIndicator(
                    color: Colors.blue,
                    minHeight: 4,
                  ),
                ],
              );
            }

            // 2. 错误状态
            if (snapshot.hasError) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.error_outline, color: Colors.red, size: 64),
                  const SizedBox(height: 20),
                  Text(
                    "加载失败:${snapshot.error}",
                    style: const TextStyle(color: Colors.red, fontSize: 16),
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: _reloadUserInfo,
                    child: const Text("重试加载"),
                  ),
                ],
              );
            }

            // 3. 成功状态
            final user = snapshot.data!;
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 线性进度条(确定模式,100%完成)
                LinearProgressIndicator(
                  value: 1.0,
                  minHeight: 4,
                  color: Colors.green,
                  backgroundColor: Colors.grey[200],
                ),
                const SizedBox(height: 30),
                // 用户头像
                CircleAvatar(
                  backgroundImage: NetworkImage(user.avatar),
                  radius: 64,
                ),
                const SizedBox(height: 20),
                // 用户名
                Text(
                  user.name,
                  style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 10),
                // 用户简介
                Text(
                  user.desc,
                  style: const TextStyle(fontSize: 16, color: Colors.grey),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

效果说明

  1. 页面启动后,显示「圆形进度条+线性进度条(不确定)+加载中文字」;

  2. 2秒后:

    1. 成功:显示用户头像、名称、简介,线性进度条变为100%(绿色);
    2. 失败:显示错误图标、提示文字、重试按钮;
  3. 右上角有「10秒倒计时」(StreamBuilder实现),倒计时结束后显示「立即刷新」按钮,点击可重新加载数据;

  4. 所有异步状态(loading/error/success)均有对应的UI反馈,符合用户体验最佳实践。

关键注意事项

  • 避免在 FutureBuilderfuture 属性中直接创建 Future(会重复执行);
  • StreamController 必须在 dispose 中关闭;
  • 所有异步异常必须处理,否则会导致应用崩溃;
  • 进度条分「确定/不确定」模式,按需选择(网络请求用不确定,文件下载用确定)。
相关推荐
读忆2 小时前
解决 `:first-child` / `:last-child` 不生效的问题
前端·css·vue.js·css3
兔年鸿运Q小Q2 小时前
vue 使用public数据
前端·javascript·vue.js
wuhen_n2 小时前
开发环境优化完全指南:告别等待,让开发如丝般顺滑
前端·javascript·vue.js
新镜2 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
badhope2 小时前
GitHub超有用项目推荐:skill仓库--用技能树打造AI超频引擎
java·开发语言·前端·人工智能·python·重构·github
时寒的笔记2 小时前
js逆向入门03_会展中心案例&shuwei观察&ji思录
开发语言·前端·javascript
@PHARAOH2 小时前
HOW - 前端页面低代码 Schema 驱动最小范式
前端·低代码
LFly_ice2 小时前
C# Web 开发从入门到实践
开发语言·前端·c#