杰理 SDK 文档中心
首页
首页
  • 概览与快速开始

    • 项目概述与能力总览
    • 快速开始与 SDK 集成
  • SDK 核心接口

    • 发送接口 BleMethod
    • 接收接口 BleEventStream
    • 数据模型与常量定义
  • 平台原生实现

    • Android 原生层
    • iOS 原生层架构
    • iOS 蓝牙管理与 SDK 运行
    • 辅助连接与广播音箱
  • OTA 升级功能

    • 升级流程与传输通道
    • 自动回连机制
    • 复用空间升级
    • 自定义命令
  • 示例应用

    • 页面结构与用户旅程
    • 设备扫描与连接管理
    • 固件文件管理
    • 升级执行与状态展示
    • 设置与调试
  • 文档与支持

    • 接口文档与收发说明
    • 调试与问题排查

页面结构与用户旅程

本文档描述 JL_OTA Flutter 示例应用(code/JL_OTA/example)的页面组织结构、应用入口初始化流程、页面导航方式,以及用户从启动到完成 OTA 升级的完整旅程。

Purpose and Scope

本页聚焦于 5-1-app-pages(页面结构与用户旅程) 这一主题,覆盖以下内容:

  • 应用入口 main.dart 的初始化流程与依赖注入(Provider)装配
  • 根路由配置与 WelcomePage 的定位
  • 页面目录(lib/pages/)、对话框目录(lib/dialog/)与数据管理层(lib/data/、lib/utils/)的职责划分
  • 用户从启动应用到完成固件升级的端到端旅程与导航方式

以下相关主题属于兄弟页面,不在本页展开:

  • 具体页面(如欢迎页、OTA 设置页等)的 widget 级实现细节,请参见对应页面文档
  • OTA 连接的底层蓝牙/设备通信逻辑,请参见「OTA 连接管理」相关文档
  • 对话框的 UI 细节与交互,请参见「对话框组件」相关文档

Overview

JL_OTA 示例应用是一个基于 Flutter 的 OTA(Over-The-Air)固件升级演示应用,用于演示杰理科技(Jieli)蓝牙芯片的在线升级流程。应用采用单入口 + Provider 全局状态 + 根路由的结构:

  • 唯一的应用入口是 main.dart,其中 main() 负责初始化 Flutter 绑定、锁定竖屏方向、装配全局状态 Provider,并启动 GlobalConnectionListener 全局连接监听。
  • 根路由 "/" 指向 WelcomePage,它是整个应用旅程的起点。
  • 全局状态通过 ChangeNotifierProvider 注入两个核心管理器:DataNotifier(数据通知)与 ConnectionStateManager(连接状态管理)。
  • 页面的辅助 UI(对话框)统一放在 lib/dialog/ 目录,涵盖 OTA 升级、文件下载、设备过滤、MTU 调整、隐私政策等场景。

这种「入口 → 根路由 → 页面 → 对话框/数据管理层」的层次结构,使 OTA 流程中的每一环节(连接设备、选择固件、下载、升级、结果反馈)都有清晰归属,便于扩展和维护。

Architecture

下图展示了应用的整体页面结构与组件关系(基于 main.dart 源码与 lib/ 目录结构验证):

flowchart TD
    subgraph sg_Entry["应用入口"]
        Main["main()<br/>WidgetsFlutterBinding.ensureInitialized"]
        MultiProvider["MultiProvider<br/>DataNotifier / ConnectionStateManager"]
        GCL["GlobalConnectionListener<br/>initialize()"]
    end

    subgraph sg_App["MyApp (StatefulWidget)"]
        MaterialApp["MaterialApp<br/>routes: '/' -> WelcomePage"]
        Theme["ThemeData<br/>seedColor #FF398BFF"]
        Locale["本地化<br/>AppLocalizations / supportedLocales"]
    end

    subgraph sg_Pages["lib/pages/ 页面层"]
        WelcomePage["WelcomePage<br/>根路由起点"]
    end

    subgraph sg_Dialogs["lib/dialog/ 对话框层"]
        OtaDialog["ota_dialog.dart"]
        DownloadDialog["download_file_dialog.dart"]
        DeviceFilter["device_filter_dialog.dart"]
        OtherDialogs["privacy_policy / mtu_adjustment /<br/>computer_transfer / loading ..."]
    end

    subgraph sg_Data["数据与工具层"]
        DataNotifier["DataNotifier"]
        ConnManager["ConnectionStateManager"]
        DataManagers["ota_connection_manager /<br/>ota_file_manager / setting_manager"]
    end

    Main --> MultiProvider
    Main --> GCL
    MultiProvider --> MaterialApp
    MaterialApp -->|"路由 '/'"| WelcomePage
    WelcomePage -->|"Navigator 导航"| OtaDialog
    WelcomePage -->|"Navigator 导航"| DownloadDialog
    WelcomePage -->|"Navigator 导航"| DeviceFilter
    WelcomePage -->|"Navigator 导航"| OtherDialogs
    OtaDialog --> DataManagers
    DownloadDialog --> DataManagers
    WelcomePage --> DataNotifier
    WelcomePage --> ConnManager

架构说明:

  • 入口层:main() 在 runApp 之前调用 WidgetsFlutterBinding.ensureInitialized() 并锁定竖屏方向;GlobalConnectionListener 在 runApp 之后立即初始化,用于全局监听连接事件(例如设备断开)。
  • 应用层:MyApp 是一个带 WidgetsBindingObserver 的 StatefulWidget,负责配置 MaterialApp 的主题(种子色 #FF398BFF)、本地化委托与根路由。dispose() 中会移除观察者并释放 GlobalConnectionListener。
  • 页面层:WelcomePage 是唯一在路由表中显式注册的页面,作为用户旅程的起点;其他页面通过 Navigator 动态压栈进入(受本页读取范围所限,其余页面的类名以 lib/pages/ 目录实际文件为准)。
  • 对话框层:lib/dialog/ 集中了 OTA 升级、文件下载、设备过滤、MTU 调整、隐私政策、通用确认、加载中提示等可复用对话框,从页面中抽离以保证页面代码聚焦于业务流程。
  • 数据层:DataNotifier 与 ConnectionStateManager 由 Provider 注入,供页面与对话框共享 OTA 状态;ota_connection_manager、ota_file_manager、setting_manager 等管理类承载具体业务逻辑。

应用入口与初始化流程

main():应用启动

code/JL_OTA/example/lib/main.dart 的 main() 是唯一入口函数,其执行顺序决定了整个应用的生命周期起点:

void main() async {
  // Ensure that the Flutter binding is initialized.
  WidgetsFlutterBinding.ensureInitialized();

  // Set the orientation to portrait
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((
    _,
  ) {
    runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (context) => DataNotifier()),
          ChangeNotifierProvider(create: (context) => ConnectionStateManager()),
        ],
        child: MyApp(),
      ),
    );
  });

  GlobalConnectionListener().initialize();
}

Source: main.dart

关键设计意图:

  • WidgetsFlutterBinding.ensureInitialized():在调用任何平台通道(如 SystemChrome)之前必须先初始化 Flutter 绑定,否则会抛出断言错误。这是 Flutter 插件/平台能力使用的前置条件。
  • 竖屏锁定:SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) 将应用锁定为竖屏。OTA 升级类应用通常固定竖屏,以避免旋转导致 UI 重建干扰升级状态展示。
  • Provider 装配:MultiProvider 在应用根部注入两个 ChangeNotifier——DataNotifier(数据通知)与 ConnectionStateManager(连接状态管理)。选择在根节点注入而非页面级创建,是为了保证任何页面/对话框都能通过 context.watch<T>() 共享同一份状态,这是 OTA 流程中「页面 A 发起连接、对话框 B 响应状态变化」能够协同的基础。
  • 全局连接监听:GlobalConnectionListener().initialize() 在 runApp 之后启动,用于监听设备连接/断开等全局事件(例如升级中途蓝牙断开时需要全局感知)。

MyApp:应用壳与路由表

MyApp 是一个监听应用生命周期(WidgetsBindingObserver)的 StatefulWidget,其 build 方法配置 MaterialApp:

return MaterialApp(
  localizationsDelegates: [
    ...AppLocalizations.localizationsDelegates,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: AppLocalizations.supportedLocales,
  localeResolutionCallback: (locale, supportedLocales) {
    for (var supportedLocale in supportedLocales) {
      if (supportedLocale.languageCode == locale?.languageCode) {
        return supportedLocale;
      }
    }
    return supportedLocales.first;
  },
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: HexColor.hexColor('#FF398BFF'),
    ),
  ),
  routes: {"/": (context) => WelcomePage()},
);

Source: main.dart

要点分析:

  • 本地化:应用使用 flutter_localizations 与自定义 AppLocalizations(对应 lib/l10n/app_localizations.dart 与 lib/generated/intl/ 下生成的翻译文件),支持多语言;localeResolutionCallback 在系统语言与支持语言不匹配时回退到第一个支持的语言。
  • 主题:通过 ColorScheme.fromSeed 以品牌色 #FF398BFF 作为种子色生成整套 Material 3 配色,保证全应用视觉一致。
  • 路由表:routes 仅注册了根路由 "/" → WelcomePage。这体现「单一入口」设计——应用启动后先进入欢迎页,再由欢迎页根据用户操作动态导航,而不是预先注册大量静态路由。

WidgetsBindingObserver 与资源释放

_MyAppState 在 initState 中注册为 WidgetsBindingObserver,在 dispose 中对称释放:

@override
void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  GlobalConnectionListener().dispose();
  super.dispose();
}

Source: main.dart

这种「initState 注册 / dispose 注销」的配对模式是 Flutter 中避免内存泄漏的标准做法;GlobalConnectionListener 是单例式的全局监听器,必须在应用销毁时显式释放。

页面与组件的目录结构

根据 lib/ 目录布局,示例应用按职责将代码划分为以下层次(目录/文件名为仓库实际内容):

目录职责主要文件(实测)
lib/pages/页面层,承载主要业务流程界面welcome_page.dart(由 main.dart 导入,根路由页面)
lib/dialog/可复用对话框,覆盖升级、下载、设备筛选等场景ota_dialog.dart、download_file_dialog.dart、device_filter_dialog.dart、privacy_policy_dialog.dart、mtu_adjustment_dialog.dart、computer_transfer_dialog.dart、generic_confirm_dialog.dart、loading_dialog.dart、loading_content_dialog.dart、select_file_save_dialog.dart、delete_all_log_dialog.dart
lib/data/业务数据与状态管理器ota_connection_manager.dart、ota_file_manager.dart、setting_manager.dart、dialog_manager.dart、popup_menu_manager.dart
lib/utils/工具与全局状态connection_state_manager.dart、data_notifier.dart、global_connection_listener.dart(均由 main.dart 导入)
lib/extensions/Dart 扩展hex_color.dart(提供 HexColor.hexColor 解析十六进制颜色)
lib/l10n/ + lib/generated/intl/本地化app_localizations.dart、messages_all.dart、messages_en.dart
lib/gen/生成代码assets.gen.dart(资源清单)

设计意图:将对话框与页面分离,使同一个对话框(如「下载文件」)可被多个页面复用;将管理器放在 data/、全局状态放在 utils/,避免页面之间直接耦合。这种分层在 OTA 场景尤为重要——升级流程涉及「连接管理、文件管理、弹窗管理」多个横切关注点,集中管理可显著降低页面间的相互依赖。

用户旅程与核心流程

完整用户旅程(端到端)

结合入口代码与目录结构,JL_OTA 示例应用的典型用户旅程如下(依据已验证的 main.dart 与 lib/ 目录文件推断各环节归属):

sequenceDiagram
    participant U as 用户
    participant MA as MyApp (main.dart)
    participant WP as WelcomePage
    participant Nav as Navigator
    participant Dlg as dialog/ 对话框
    participant Mgr as data/ 管理器

    U->>MA: 启动应用
    MA->>MA: main() 初始化绑定/竖屏/Provider
    MA->>WP: 路由 '/' -> WelcomePage
    U->>WP: 点击「连接设备 / 开始升级」
    WP->>Mgr: 触发 OTA 连接/文件管理
    WP->>Nav: Navigator.push(MaterialPageRoute)
    Nav->>Dlg: 打开对应对话框
    Dlg->>Mgr: 读取/更新 OTA 状态
    Mgr-->>Dlg: 状态回调(下载进度/升级进度)
    Dlg-->>U: 展示进度与结果
    U->>WP: 返回/完成,旅程结束

旅程关键节点说明:

  1. 启动:main() 完成初始化后,MaterialApp 通过 routes: {"/": ...} 直接呈现 WelcomePage——欢迎页是用户旅程的第一个触点。
  2. 入口动作:用户在欢迎页选择设备或发起 OTA 流程,页面通过 context.read<ConnectionStateManager>() / context.read<DataNotifier>() 访问注入的全局状态。
  3. 导航:页面通过 Navigator.push + MaterialPageRoute 打开对话框或次级页面;对话框文件集中存放于 lib/dialog/,例如:
    • device_filter_dialog.dart —— 筛选可连接设备;
    • download_file_dialog.dart —— 选择/下载固件文件;
    • ota_dialog.dart —— OTA 升级主流程对话框(进度、结果);
    • mtu_adjustment_dialog.dart —— 调整蓝牙 MTU 以适配大包传输。
  4. 数据交互:对话框通过 lib/data/ 下的管理器(ota_connection_manager、ota_file_manager、setting_manager)执行业务逻辑,并通过 DataNotifier/ConnectionStateManager 广播状态变化,驱动 UI 刷新。

说明:以上对话框中各环节的具体交互代码位于对应文件内,本页仅从页面结构与旅程角度描述其归属;各对话框的详细实现请参阅「对话框组件」相关页面。

状态驱动的 UI 刷新机制

应用使用 Provider 的 ChangeNotifier 模式实现「业务状态变化 → UI 自动刷新」:

flowchart LR
    Mgr["data/ 管理器<br/>(ota_connection_manager 等)"] -->|"notifyListeners()"| N["ChangeNotifier<br/>DataNotifier / ConnectionStateManager"]
    N -->|"Provider 广播"| P["页面 / 对话框<br/>context.watch / context.read"]
    P -->|"触发操作"| Mgr
  • DataNotifier(lib/utils/data_notifier.dart):承载应用级数据变更通知,供页面监听 OTA 过程中的数据变化。
  • ConnectionStateManager(lib/utils/connection_state_manager.dart):管理设备连接状态机(连接中 / 已连接 / 断开),是升级流程能否进行的前提。
  • 两个管理器在 main.dart 根部注入,保证整个应用共享同一连接会话——这是 OTA 升级中断线重连、进度恢复等能力的基础。

配置选项

应用级配置集中在 main.dart 中,可通过修改源码调整:

配置项类型默认值说明
DeviceOrientation枚举portraitUp应用锁定方向,仅竖屏
主题种子色String'#FF398BFF'经 HexColor.hexColor 解析后作为 ColorScheme.fromSeed 种子色
根路由Map<String, WidgetBuilder>{"/": WelcomePage}唯一静态注册路由,其余页面动态导航
supportedLocalesList<Locale>来自 AppLocalizations.supportedLocales支持的语言列表,匹配失败时回退 supportedLocales.first
Provider 列表List<SingleChildWidget>DataNotifier、ConnectionStateManager全局注入的 ChangeNotifier 集合

配置读取逻辑见 main.dart。

API 参考

main()

应用入口。初始化 Flutter 绑定、锁定竖屏、装配 Provider、启动全局连接监听。

参数: 无(async 顶层函数)

行为:

  • 调用 WidgetsFlutterBinding.ensureInitialized();
  • 通过 SystemChrome.setPreferredOrientations 锁定 portraitUp,回调完成后执行 runApp;
  • runApp 参数为 MultiProvider(注入 DataNotifier、ConnectionStateManager),其 child 为 MyApp;
  • 随后调用 GlobalConnectionListener().initialize()。

Throws: 若在绑定初始化前调用平台通道相关 API 会触发 Flutter 断言(ensureInitialized 正是为防止此问题)。

class MyApp extends StatefulWidget

应用根 Widget。createState 返回 _MyAppState(实现 WidgetsBindingObserver)。

_MyAppState.build(): Widget

构建 MaterialApp:

返回值: 配置了本地化委托、supportedLocales、localeResolutionCallback、主题与 routes: {"/": WelcomePage} 的 MaterialApp。

_MyAppState.dispose(): void

移除 WidgetsBindingObserver 并释放 GlobalConnectionListener。

失败模式、边界情况与并发

  • 平台通道时序:main() 中任何平台通道调用(如 SystemChrome)都必须发生在 WidgetsFlutterBinding.ensureInitialized() 之后。源码中先初始化绑定再设置方向,顺序正确;若未来在此函数中新增其他平台调用,需保持同样的顺序约定。
  • 方向锁定与生命周期:runApp 被包裹在 setPreferredOrientations(...).then(...) 中——若方向设置失败(极少数平台异常),应用可能不会启动;这是「先完成平台配置再渲染 UI」的权衡。
  • 全局监听器生命周期:GlobalConnectionListener 在 main() 中 initialize(),在 _MyAppState.dispose() 中 dispose()。若应用在 MyApp 之外被重建(例如测试环境直接 runApp(WelcomePage())),监听器将无法释放——因此所有页面都应通过 MyApp 树启动。
  • 多 Provider 并发访问:DataNotifier 与 ConnectionStateManager 由 Provider 管理,多个页面/对话框可同时 watch。ChangeNotifier 默认不支持并发写保护,OTA 进度回调应尽量在主 isolate(UI 线程)中触发 notifyListeners(),避免跨 isolate 直接修改状态。
  • 本地化回退:localeResolutionCallback 在系统语言不匹配时回退到 supportedLocales.first,保证任何设备语言下都有可用文案,不会因缺失翻译而崩溃。

性能与运维注意事项

  • 启动开销:main() 中 setPreferredOrientations 返回的 Future 完成前不会调用 runApp,因此首帧渲染会略晚于默认启动路径;对 OTA 示例应用而言影响可忽略。
  • 状态粒度:ChangeNotifier 广播是全量通知(notifyListeners 不携带变更详情),依赖 context.watch 的页面在任意状态变化时都会重建。若后续页面增多、状态频繁变化,可考虑引入 Selector 或拆分更细粒度的 ChangeNotifier 以降低重建范围。
  • 对话框管理层:dialog_manager.dart 集中管理对话框的显示/关闭,可避免多个对话框同时弹出导致的 UI 冲突(例如升级中弹出设备断开提示)。

扩展点

  • 新增页面:遵循现有模式,在 lib/pages/ 下新增页面文件,并在入口动作处通过 Navigator.push(MaterialPageRoute(...)) 动态导航,无需修改路由表。
  • 新增对话框:在 lib/dialog/ 下新增可复用对话框,并通过 dialog_manager.dart 统一调度。
  • 新增全局状态:在 main.dart 的 MultiProvider.providers 列表中追加 ChangeNotifierProvider,即可让所有页面共享新状态。
  • 新增语言:扩展 AppLocalizations 及 lib/generated/intl/ 下的翻译消息文件(messages_all.dart、messages_en.dart 为生成产物),并确保 supportedLocales 包含新语言。
  • 品牌定制:修改主题种子色 '#FF398BFF' 即可全局换肤,无需改动各页面样式。

测试

受本页源码读取预算所限,未对 test/ 目录进行采样读取,因此无法在此列出已验证的测试用例。从实现角度看,main.dart 的初始化链(绑定 → 方向 → Provider → 监听器)与 localeResolutionCallback 的回退逻辑是适合单元测试/Widget 测试的关键路径;测试时应注意 GlobalConnectionListener 与平台通道(SystemChrome、flutter_localizations)需要 Mock 或 TestWidgetsFlutterBinding 支持。

Related Links

  • 应用入口 main.dart:入口初始化、Provider 装配与根路由配置
  • lib/pages/welcome_page.dart:根路由页面 WelcomePage(用户旅程起点),详见「欢迎页」页面文档
  • lib/utils/data_notifier.dart、lib/utils/connection_state_manager.dart、lib/utils/global_connection_listener.dart:全局状态与连接监听,详见「状态管理与连接监听」相关文档
  • lib/dialog/ 目录:OTA 升级、文件下载、设备过滤、MTU 调整等对话框,详见「对话框组件」页面文档
  • lib/data/ 目录:ota_connection_manager、ota_file_manager、setting_manager 等业务管理器,详见「OTA 数据管理层」相关文档
  • 项目总览:README.md 与 README_EN.md

本文档基于 code/JL_OTA/example/lib/main.dart(已完整读取)与 lib/ 目录结构(已通过 ListFiles 验证)编写;涉及具体页面与对话框内部实现的描述以文件名为依据,详细逻辑请以对应源文件为准。

Next
设备扫描与连接管理