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

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

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

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

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

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

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

设置与调试

本页介绍 JL_OTA Flutter 示例应用(code/JL_OTA/example)中「设置」页面的完整实现:包括 SettingPage 的用户界面结构、SettingManager 的数据加载/保存逻辑、Android/iOS 平台差异、MTU 调整、日志文件访问、版本信息展示,以及与 SDK 桥接层 BleMethod 的交互方式。

Purpose and Scope

本页聚焦于示例应用的「设置与调试」能力,涵盖:

  • UI 层:SettingPage(StatefulWidget)如何组织各设置区块(设备认证、HID、自定义重连、通信方式、MTU、SDK 蓝牙、GATT over EDR、日志文件、版本、自定义命令、关于)。
  • 数据层:SettingManager 如何通过静态方法封装对原生 SDK 的读写调用,以及异常兜底策略。
  • 平台差异:Android 与 iOS 各自特有的设置项与保存行为(Android 保存后 popAllActivity() 重启应用)。
  • 调试入口:日志文件目录展示、日志文件列表页(FileListPage)入口、自定义命令页(CustomCmdPage)入口。

以下主题由同目录下其他页面文档负责,不在本页展开:设备扫描与连接的完整流程(devices_page)、文件列表与升级流程(file_list_page / update_page)、关于页面(about_page)、自定义命令的指令细节(custom_cmd_page)。如需了解,请参阅对应页面。

Overview

「设置」页面是示例应用中对 SDK 运行参数进行配置与调试的核心入口。应用启动后,SettingPage.initState() 触发 _initialize(),通过 SettingManager 的系列静态方法从原生 SDK 读取当前配置(设备认证开关、HID 开关、自定义重连方式、通信方式、MTU、SDK 蓝牙、GATT over EDR 等),并展示在分组列表中。用户在页面上修改任意设置后,AppBar 上的「保存」按钮由 _hasSettingsChanged() 检测结果驱动变为可用;点击保存会调用 SettingManager.saveSettings(),按平台分支写入原生 SDK。

该设计有两点关键意图:

  1. 单一数据入口:所有与原生 SDK 的交互都收敛到 SettingManager,UI 层不直接接触 BleMethod,便于统一处理异常与默认值。
  2. 保存后重启(Android):部分原生参数(如 HID、自定义重连)在 SDK 初始化后无法热切换,因此 Android 保存成功后调用 BleMethod.popAllActivity() 重启应用使配置生效——这是示例应用明确的权衡,iOS 则不需要重启。

调试方面,页面顶部展示日志文件目录路径(Android 居中、iOS 左对齐),「日志文件」导航行在蓝牙连接状态下可进入 FileListPage 查看/下载日志;「自定义命令」行在 Android 或 iOS 使用 SDK 蓝牙时可用,进入 CustomCmdPage 发送调试指令;「版本信息」行展示 SDK 版本与应用版本;「关于应用」行进入 AboutPage。

Architecture

flowchart TD
    subgraph sg_UI["UI 层 (Flutter Widgets)"]
        SettingPage["SettingPage<br/>(StatefulWidget)"]
        SettingSection["SettingSection<br/>(分组容器)"]
        SettingSwitchRow["SettingSwitchRow<br/>(开关行)"]
        SettingNavigationRow["SettingNavigationRow<br/>(导航行)"]
        CommunicationOption["CommunicationOption<br/>(单选通信方式)"]
    end

    subgraph sg_Data["数据层 (示例应用)"]
        SettingManager["SettingManager<br/>(静态方法封装)"]
    end

    subgraph sg_Plugin["SDK 桥接层 (jl_ota 插件)"]
        BleMethod["BleMethod<br/>(MethodChannel)"]
    end

    subgraph sg_Native["原生层 (JL BLEKit / Android SDK)"]
        Android["Android 原生 SDK"]
        iOS["iOS 原生 SDK"]
    end

    SettingPage --> SettingSection
    SettingPage --> SettingSwitchRow
    SettingPage --> SettingNavigationRow
    SettingPage --> CommunicationOption
    SettingPage -->|"加载 / 保存"| SettingManager
    SettingManager -->|"静态调用"| BleMethod
    BleMethod -->|"通道分发"| Android
    BleMethod -->|"通道分发"| iOS

各层职责说明:

  • SettingPage:页面唯一入口(setting_page.dart)。持有所有设置项的状态变量与初始值快照,负责按平台条件渲染区块,并驱动保存流程。它通过 context.watch<ConnectionStateManager>() 订阅蓝牙连接状态,决定 MTU/日志/自定义命令等入口是否可用。
  • SettingManager:纯静态方法的数据访问层(setting_manager.dart)。每个方法都包裹 try-catch,原生调用失败时记录日志并返回安全默认值;只有 saveSettings 选择 rethrow,因为保存失败必须让 UI 感知。
  • BleMethod:jl_ota 插件暴露的 Dart 静态桥接类,内部通过 MethodChannel 与原生 SDK(Android JL SDK / iOS JL_BLEKit)通信。SettingManager 是它在本能力域内的唯一消费者。
  • 原生层:真正持有配置状态(如 MTU、通信方式、设备认证开关)的载体,示例应用仅做透传与展示。

主要实现

SettingPage 状态设计

SettingPage 是一个 StatefulWidget,其状态类 _SettingPageState 维护了两组状态变量(setting_page.dart):

  • 当前值:_isDeviceAuthenticated、_isHidDevice、_customReconnectMethod、_connectUsingSdkBluetooth、_isGattOverEdr、_currentCommunicationMethod、_mtu、_gattServiceUuids 等,直接驱动 UI 渲染。
  • 初始值快照:_initialDeviceAuthenticated、_initialHidDevice、_initialCustomReconnectMethod、_initialConnectUsingSdkBluetooth、_initialGattOverEdr、_initialCommunicationMethod、_initialMtu,用于 _hasSettingsChanged() 变更检测。
// State variables
String _logFileDirPath = "";
int _currentCommunicationMethod = AppConstants.communicationWayBle;
String _sdkVersion = "unknown";
String _appVersion = "unknown";

bool _isDeviceAuthenticated = false;
bool _isHidDevice = false;
bool _customReconnectMethod = false;
bool _connectUsingSdkBluetooth = false;
bool _isGattOverEdr = false;
List<String> _gattServiceUuids = [];
int _mtu = 0;

// Initial value tracking
bool _initialDeviceAuthenticated = false;
...

Source: setting_page.dart

关键的设计意图是**「初始值快照 + 变更检测」**:保存按钮的可用性由 hasChanges 决定,避免用户未做任何修改时触发无意义的原生写入。同时,_connectState 通过 context.watch<ConnectionStateManager>() 订阅,使页面在蓝牙断开时自动禁用依赖连接的入口。

页面渲染与平台分支

build() 方法(setting_page.dart)按以下顺序渲染:

  1. 日志路径提示(页面顶部):Android 下居中显示,iOS 下左对齐;颜色为浅灰(0xFF6F6F6F),字号 13。
  2. 设备认证开关:所有平台通用。
  3. Android 专属区块(if (isAndroid)):HID 设备开关、自定义重连方式开关、通信方式单选(BLE/SPP/GATT over BR/EDR)、MTU 调整行。
  4. iOS 专属区块(if (!isAndroid)):使用 SDK 蓝牙开关、GATT over EDR 开关(打开时弹出 UUID 设置页)。
  5. 通用区块:日志文件导航行、SDK 版本信息行、自定义命令导航行、关于应用导航行。
// Check if any settings have been modified
final bool hasChanges = _hasSettingsChanged();

return Scaffold(
  appBar: AppBar(
    ...
    actions: [
      TextButton(
        onPressed: hasChanges ? () => _onSavePressed(isAndroid) : null,
        child: Text(
          loc.save,
          style: TextStyle(
            color: hasChanges ? primaryColor : disabledColor,
            ...

Source: setting_page.dart

flowchart TD
    Start(["打开设置页"]) --> Init["initState() → _initialize()"]
    Init --> Load["SettingManager.loadXxx() 系列<br/>(从原生 SDK 读取)"]
    Load --> Build["build() 渲染页面"]
    Build --> P{"isAndroid?"}
    P -->|"是"| AndroidSec["Android 区块<br/>HID / 自定义重连 / 通信方式 / MTU"]
    P -->|"否"| iOSSec["iOS 区块<br/>SDK蓝牙 / GATT over EDR"]
    AndroidSec --> Common["通用区块<br/>日志 / 版本 / 自定义命令 / 关于"]
    iOSSec --> Common
    Common --> HasChange{"_hasSettingsChanged()?"}
    HasChange -->|"有变更"| SaveBtn["保存按钮可用 (primaryColor)"]
    HasChange -->|"无变更"| SaveBtn2["保存按钮禁用 (disabledColor)"]

平台分支的设计意图:Android 与 iOS 的底层 SDK 能力不同——Android 支持 HID、SPP 通信与自定义重连,iOS 则引入「使用 SDK 蓝牙」与「GATT over EDR」两个概念,因此 UI 必须按 AppUtil.isAndroid 做条件渲染,保证每个平台只暴露其原生支持的能力,避免误导用户。

通信方式选择

通信方式区块包含三个 CommunicationOption(单选),分别对应常量 AppConstants.communicationWayBle、communicationWaySpp、communicationWayGattOverBrEdr:

/// Builds BLE communication option
Widget _buildCommunicationOptionBle(AppLocalizations loc) {
  return CommunicationOption(
    title: loc.communicationWayBle,
    isSelected:
    _currentCommunicationMethod == AppConstants.communicationWayBle,
    onTap: () => _updateState(
          () => _currentCommunicationMethod = AppConstants.communicationWayBle,
    ),
  );
}

Source: setting_page.dart

选择结果存于 _currentCommunicationMethod,保存时经 SettingManager 写入原生层。_updateState(() => ...) 统一包装 setState,保证所有修改都走同一路径。

MTU 调整与连接状态守卫

MTU 调整行仅在通信方式为 BLE 时完全可用(Opacity(opacity: isBleEnabled ? 1.0 : 0.5)),点击前会检查连接状态:

SettingNavigationRow(
  title: loc.adjustMtu,
  subtitle: mtuDisplay,
  onTap: isBleEnabled
      ? () {
    if (_connectState == AppConstants.connectionDisconnect &&
        mounted) {
      ToastUtils.show(context, loc.bluetoothDisconnected);
    } else {
      showMtuAdjustmentDialog(context);
    }
  }
      : null,
),

Source: setting_page.dart

页面定义了 MTU 的合法区间常量:minMtu = 23、maxMtu = 509(setting_page.dart)。mtuDisplay getter 在 _mtu > 0 时显示数值,否则显示空字符串。这种「断开即 Toast 提示」的守卫模式同样应用于日志文件与自定义命令入口,确保所有依赖蓝牙连接的调试操作都先校验连接状态。

自定义命令与日志入口的可达性

  • 自定义命令(_onCustomCmdTap):仅 Android 或 iOS 使用 SDK 蓝牙时返回可点击回调;点击后再次校验连接状态,断开则 Toast 提示,否则 Navigator.push 进入 CustomCmdPage(setting_page.dart)。
  • 日志文件:连接状态下点击进入 FileListPage(setting_page.dart)。
  • 关于应用:无条件可用,进入 AboutPage;副标题展示 _appVersion。

SettingManager 数据层

SettingManager 是示例应用与 jl_ota 插件之间的静态门面(static facade),全部方法为 static,不持有实例状态(setting_manager.dart)。这样设计是因为配置项本质上是原生 SDK 的全局状态,无需在 Dart 侧保存副本;UI 层随时可调用静态方法读取最新值。

加载方法:统一异常兜底

每个 load* 方法都遵循同一模式——调用 BleMethod 对应方法,失败时用 dart:developer 的 log() 记录错误并返回安全默认值:

// 加载设备认证状态
static Future<bool> loadDeviceAuth() async {
  try {
    return await BleMethod.isUseDeviceAuth();
  } catch (e) {
    log("Failed to load device auth: $e");
    return false;
  }
}

// 加载MTU值(仅Android)
static Future<int> loadMtu() async {
  try {
    return await BleMethod.getBleRequestMtu();
  } catch (e) {
    log("Failed to load MTU: $e");
    return 23; // 默认值
  }
}

Source: setting_manager.dart

设计意图:加载失败返回默认值(false / 23 / communicationWayBle / 空字符串 / "unknown"),保证 UI 永远能渲染出一个可用状态,而不是因原生异常导致页面崩溃;错误通过 log() 写入调试日志,方便开发者在「日志文件」入口中回溯问题。

版本信息聚合

getVersions() 并发获取 SDK 与应用版本并组装为 Map,键使用 AppConstants.sdkName / AppConstants.appName:

static Future<Map<String, String>> getVersions() async {
  try {
    final sdkVersion = await BleMethod.getSdkVersion();
    final appVersion = await BleMethod.getAppVersion();
    return {
      AppConstants.sdkName: sdkVersion,
      AppConstants.appName: appVersion,
    };
  } catch (e) {
    log("Failed to get versions: $e");
    return {AppConstants.sdkName: "unknown", AppConstants.appName: "unknown"};
  }
}

Source: setting_manager.dart

保存流程(核心控制流)

saveSettings() 是唯一 rethrow 异常的方法——保存失败必须向上传播,由 UI 层感知并提示用户,这与加载方法的静默兜底形成鲜明对比:

static Future<void> saveSettings({
  required bool isAndroid,
  required bool deviceAuth,
  required bool hidDevice,
  required bool customReconnect,
  required int communicationMethod,
  required int mtu,
  required bool useSdkBluetooth,
  required bool gattOverEdr,
  required List<String> gattServiceUuids,
}) async {
  try {
    // 通用设置:设备认证
    await BleMethod.setUseDeviceAuth(deviceAuth);

    if (isAndroid) {
      // Android特有设置
      await BleMethod.setHidDevice(hidDevice);
      await BleMethod.setUseCustomReConnectWay(customReconnect);
      await BleMethod.setConnectWay(communicationMethod);
      await BleMethod.setBleRequestMtu(mtu);
      await BleMethod.popAllActivity(); // 重启应用
    } else {
      // iOS特有设置
      await BleMethod.setConnectUsingSdkBluetooth(useSdkBluetooth);
      await BleMethod.setGattOverEdrState(gattOverEdr);
      await BleMethod.setGattServiceUuids(gattServiceUuids);
    }
  } catch (e) {
    log("Failed to save settings: $e");
    rethrow;
  }
}

Source: setting_manager.dart

sequenceDiagram
    participant U as 用户
    participant SP as SettingPage
    participant SM as SettingManager
    participant BM as BleMethod
    participant N as 原生 SDK

    U->>SP: 修改任意设置项
    SP->>SP: _updateState() 更新状态变量
    SP->>SP: _hasSettingsChanged() → hasChanges=true
    U->>SP: 点击 AppBar「保存」
    SP->>SM: saveSettings(isAndroid, deviceAuth, ...)
    SM->>BM: setUseDeviceAuth(deviceAuth) (通用)
    alt Android
        SM->>BM: setHidDevice / setUseCustomReConnectWay
        SM->>BM: setConnectWay / setBleRequestMtu
        BM->>N: 写入原生配置
        SM->>BM: popAllActivity() 重启应用
    else iOS
        SM->>BM: setConnectUsingSdkBluetooth / setGattOverEdrState
        SM->>BM: setGattServiceUuids(gattServiceUuids)
        BM->>N: 写入原生配置
    end
    N-->>SM: 结果
    SM-->>SP: 成功返回 / rethrow 异常

保存流程要点:

  1. 通用设置先行:setUseDeviceAuth 不分平台总是最先写入,因为设备认证是所有通信方式的前提。
  2. Android 分支以 popAllActivity() 收尾:HID、自定义重连、通信方式、MTU 均为 SDK 启动期参数,原生侧要求重启应用才能生效,因此保存成功后直接退出所有 Activity 触发冷启动。
  3. iOS 分支顺序写入:SDK 蓝牙 → GATT over EDR → 服务 UUID 列表;其中 _gattServiceUuids 默认含 AE00(setting_page.dart),由 service_uuid_input_page 提供编辑入口。
  4. 异常传播:任一步骤失败即 rethrow,UI 层可据此提示「保存失败」,避免用户误以为配置已生效。

使用示例

以下示例均摘自仓库真实源码,展示如何在实际代码中使用本能力。

示例 1:在页面初始化时加载全部设置

_SettingPageState.initState() 调用 _initialize(),通过 SettingManager 的加载方法批量读取原生配置并填充状态变量(含初始值快照):

@override
void initState() {
  super.initState();
  _initialize();
}

Source: setting_page.dart

示例 2:读取 SDK 版本与应用版本并展示

final versions = await SettingManager.getVersions();
setState(() {
  _sdkVersion = versions[AppConstants.sdkName] ?? "unknown";
  _appVersion = versions[AppConstants.appName] ?? "unknown";
});

说明:getVersions() 内部通过 BleMethod.getSdkVersion() / BleMethod.getAppVersion() 获取,异常时回退为 "unknown"。Source: setting_manager.dart

示例 3:构造可复用的设置区块与开关行

页面使用自定义组件 SettingSection(分组容器)与 SettingSwitchRow(开关行)组织 UI:

// Device authentication settings
SettingSection(
  children: [
    SettingSwitchRow(
      title: loc.deviceAuthentication,
      value: _isDeviceAuthenticated,
      onChanged: (value) =>
          _updateState(() => _isDeviceAuthenticated = value),
    ),
  ],
),

Source: setting_page.dart

示例 4:保存设置的完整调用(按平台分支)

await SettingManager.saveSettings(
  isAndroid: isAndroid,
  deviceAuth: _isDeviceAuthenticated,
  hidDevice: _isHidDevice,
  customReconnect: _customReconnectMethod,
  communicationMethod: _currentCommunicationMethod,
  mtu: _mtu,
  useSdkBluetooth: _connectUsingSdkBluetooth,
  gattOverEdr: _isGattOverEdr,
  gattServiceUuids: _gattServiceUuids,
);

Source(参数契约): setting_manager.dart

Configuration Options

本能力没有独立配置文件,所有配置项均由原生 SDK 持有,示例应用通过 SettingManager 读写。可配置项如下:

设置项类型平台默认值说明
deviceAuthbool通用false设备认证开关,保存时最先写入
hidDeviceboolAndroidfalseHID 设备模式开关
customReconnectboolAndroidfalse自定义重连方式开关
communicationMethodintAndroidAppConstants.communicationWayBle通信方式:BLE / SPP / GATT over BR/EDR 单选
mtuintAndroid23BLE 请求 MTU,合法区间 23–509
useSdkBluetoothbooliOSfalse使用 SDK 蓝牙开关,控制自定义命令可用性
gattOverEdrbooliOSfalseGATT over EDR 开关,打开时弹出 UUID 设置
gattServiceUuidsList<String>iOS['AE00']GATT 服务 UUID 列表(defaultServiceUuid = 'AE00')
logFileDirPathString通用(展示)""日志目录路径,页面顶部展示
sdkVersion / appVersionString通用(展示)"unknown"版本信息,只读展示

说明:minMtu = 23、maxMtu = 509 常量定义见 setting_page.dart;通信方式常量与默认值见 setting_manager.dart。

API Reference

SettingManager(setting_manager.dart)为静态类,全部方法可直接以 SettingManager.xxx() 调用。

static Future<bool> loadDeviceAuth()

加载设备认证状态。

  • 返回:true 表示已开启设备认证;原生调用异常时返回 false。

static Future<bool> loadHidDevice()

加载 HID 设备状态(仅 Android 有意义)。

  • 返回:HID 开关状态;异常时返回 false。

static Future<bool> loadCustomReconnect()

加载自定义重连方式状态(仅 Android)。

  • 返回:是否使用自定义重连;异常时返回 false。

static Future<int> loadCommunicationMethod()

加载当前通信方式(仅 Android)。

  • 返回:AppConstants 中的通信方式常量(communicationWayBle / communicationWaySpp / communicationWayGattOverBrEdr);异常时返回 communicationWayBle。

static Future<int> loadMtu()

加载 BLE 请求 MTU(仅 Android)。

  • 返回:MTU 数值;异常时返回 23。

static Future<bool> loadSdkBluetooth()

加载 SDK 蓝牙使用状态(仅 iOS)。

  • 返回:是否使用 SDK 蓝牙;异常时返回 false。

static Future<bool> isUseGattOverEdr()

读取 GATT over EDR 状态(仅 iOS)。

  • 返回:是否启用 GATT over EDR;异常时返回 false。

static Future<String> getLogDirPath()

获取日志文件目录路径。

  • 返回:日志目录绝对路径;异常时返回空字符串 ""。

static Future<Map<String, String>> getVersions()

获取 SDK 版本与应用版本。

  • 返回:{AppConstants.sdkName: sdkVersion, AppConstants.appName: appVersion};异常时两个值均为 "unknown"。

static Future<void> saveSettings({required bool isAndroid, required bool deviceAuth, required bool hidDevice, required bool customReconnect, required int communicationMethod, required int mtu, required bool useSdkBluetooth, required bool gattOverEdr, required List<String> gattServiceUuids})

保存全部设置到原生 SDK。

参数:

  • isAndroid(bool,必填):平台标识,决定走 Android 还是 iOS 分支。
  • deviceAuth(bool,必填):设备认证开关。
  • hidDevice(bool,必填):HID 设备开关(Android)。
  • customReconnect(bool,必填):自定义重连开关(Android)。
  • communicationMethod(int,必填):通信方式常量(Android)。
  • mtu(int,必填):BLE 请求 MTU(Android)。
  • useSdkBluetooth(bool,必填):SDK 蓝牙开关(iOS)。
  • gattOverEdr(bool,必填):GATT over EDR 开关(iOS)。
  • gattServiceUuids(List<String>,必填):GATT 服务 UUID 列表(iOS)。

行为:

  • 通用设置 setUseDeviceAuth 总是先执行;
  • Android 分支写入 HID、自定义重连、通信方式、MTU 后调用 popAllActivity() 重启应用;
  • iOS 分支依次写入 SDK 蓝牙、GATT over EDR、服务 UUID。

Throws:

  • Exception(原生侧错误):任一写入失败时 rethrow,由调用方处理;错误同时写入 log()。

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

原生调用异常

  • 加载路径:SettingManager 每个 load* 方法都以 try-catch 包裹,异常时 log() 记录并返回默认值(false / 23 / communicationWayBle / "" / "unknown")。这意味着原生 SDK 不可用时页面仍可渲染,但会展示默认配置——用户在调试时可能误以为配置已生效,因此日志中保留错误记录用于排查。
  • 保存路径:saveSettings 失败时 rethrow,UI 层应提示保存失败。这是有意的不对称设计:加载可降级,保存必须明确失败,否则配置会静默丢失。

连接状态守卫

MTU 调整、日志文件、自定义命令三个入口在 _connectState == AppConstants.connectionDisconnect 时不会进入对应流程,而是 ToastUtils.show(context, loc.bluetoothDisconnected) 提示(setting_page.dart)。守卫使用 mounted 检查避免在 Widget 销毁后弹 Toast。

MTU 边界

页面定义 minMtu = 23、maxMtu = 509,实际输入校验由 mtu_adjustment_dialog.dart 负责;mtuDisplay 仅在 _mtu > 0 时展示数值,避免加载失败(默认 23 之外的异常值)显示误导信息。

平台特有边界

  • iOS 自定义命令:仅当 _connectUsingSdkBluetooth == true 时可用,否则整行以 Opacity(0.5) 半透明禁用(setting_page.dart)。
  • Android 保存重启:popAllActivity() 会退出所有 Activity,属于「破坏性」操作——用户必须接受重启才能使 HID/重连/MTU 等生效,这是原生 SDK 能力的硬约束。
  • GATT over EDR:iOS 打开该开关时立即弹出 service_uuid_input_page 设置 UUID,先于状态提交,确保用户理解该开关需要附加配置。

并发与状态一致性

  • 页面状态与原生配置不同步写入:用户修改 UI 后、点击保存前,原生侧仍是旧值;期间若蓝牙连接状态变化,context.watch<ConnectionStateManager>() 会触发重建,但已修改的设置项会保留(状态变量不因重建丢失)。
  • _initialize() 内部多个 await 串行加载,setState 前应检查 mounted,避免异步返回时页面已销毁(_autoScrollToBottom 中已使用 _scrollController.hasClients 防御)。
  • 所有加载/保存均为异步方法,示例应用未引入显式锁;由于入口集中在设置页且保存为一次性全量提交,当前并发模型足以满足需求。

扩展点与运维建议

新增设置项

若要新增一个配置项,按三步接入(与现有模式保持一致):

  1. 在 SettingManager 增加 loadXxx() / 复用 saveSettings 的参数,并包裹 try-catch 兜底;
  2. 在 _SettingPageState 增加状态变量与 _initialXxx 快照,并加入 _hasSettingsChanged() 与 _updateState() 的变更路径;
  3. 在 build() 对应平台分支中新增 SettingSection / SettingSwitchRow / SettingNavigationRow。

国际化

所有文案均通过 AppLocalizations.of(context)!(loc)获取,新增设置项时应同步在 l10n/app_localizations.dart 中补充多语言资源,保持页面可本地化。

调试入口(日志)

  • 日志路径在页面顶部实时展示,指向原生 SDK 写入的日志目录;
  • 「日志文件」入口在连接状态下进入 FileListPage,可查看/分享/下载日志文件,是排查 OTA 失败的首选手段;
  • SettingManager 的 log() 输出会进入 Flutter 调试控制台,便于开发期定位加载/保存异常。

测试与质量

仓库中本页相关测试未见独立测试文件;当前正确性保障主要依赖:加载方法的默认值兜底、保存方法的 rethrow 契约、以及连接状态守卫的 Toast 提示。若补充测试,建议优先覆盖:saveSettings 平台分支参数映射、loadXxx 异常兜底返回值、_hasSettingsChanged 的变更检测逻辑。

Related Links

  • 设置页源码 setting_page.dart — 页面 UI 与交互实现
  • 设置管理器源码 setting_manager.dart — 数据加载/保存封装
  • 设置组件 widgets/setting_components.dart — SettingSection / SettingSwitchRow / CommunicationOption 等复用组件
  • 导航行组件 widgets/setting_navigation_row_widget.dart — 导航行样式
  • 相关页面:设备连接与扫描(Devices)、文件与升级流程(FileList / Update)、关于页面(About)、自定义命令(Custom Command)、UUID 输入页(Service UUID Input)
  • SDK 桥接层:package:jl_ota/ble_method.dart 与 package:jl_ota/constant/constants.dart(AppConstants 通信方式常量)
Prev
升级执行与状态展示