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

    • 仓库概览
    • 运行环境与 SDK 集成
    • 工程结构与目录导航
  • 核心 SDK 架构

    • SDK 库体系与模块划分
    • 蓝牙连接与 RCSP 协议
    • 广播包解析与设备认证
    • 日志助手与调试支持
  • 设备功能模块

    • OTA 固件升级
    • 表盘管理与自定义表盘
    • 图像转换工具
    • 资源打包
    • 音频编解码
    • 健康与运动数据同步
    • 消息通知与实用设备功能
  • 宜动健康示例应用

    • 应用架构与页面导航
    • 健康界面与数据可视化
    • 设备连接与数据同步
    • 登录注册与用户中心
    • AI 云服务与语音交互
    • 本地数据库与持久化
    • 多语言国际化
  • 测试与调试

    • SDKTestHelper 功能测试工具
    • 音频编解码示例工程
    • 调试技巧与问题排查
  • 文档与资源

    • 在线文档与版本历史
    • 第三方框架与依赖管理

健康界面与数据可视化

本文介绍 iOS-JL_Health 工程中 TWS(真无线立体声耳机)健康功能的界面实现与健康数据可视化方案,涵盖 TwsHealthViewController 的状态展示区、13 项健康操作列表、TwsHealthViewModel 的 RxSwift 数据流,以及底层基于 JL_BLEKit 框架 JLTwsHealthManager 的心率、血氧、计步、日常心率等健康数据的采集与展示链路。

Purpose and Scope

本页聚焦于「健康界面与数据可视化」这一能力:即 SDKTestHelper 演示工程中用于展示设备健康能力的 UI 层(TwsHealthViewController)与其 ViewModel 数据管道(TwsHealthViewModel),以及它们所消费的 JL_BLEKit 健康数据模型(JLTwsHealthConfig、JLTwsHealthHeartRateModel、JLTwsHealthManager)。

以下内容属于其他目录页面的边界,本页仅作引用、不展开:

  • 表盘市场(Dial Market):DialMarketViewController、DialMallViewController 等界面及 DialMarketHttp.swift 中的 /health/v1/api 服务端接口——该接口路径中的 "health" 是云端服务名,与本文 TWS 健康传感器数据无关。
  • 设备连接与 BLE 通信底层:JL_BLEKit 框架的整体架构(JLTwsHealthManager 之上层、设备配对/连接流程)。
  • 音频单元与工具链:JLAudioUnitKitDemo、JLAudioToolbox 等。

如需上述主题,请参阅对应的目录页面。

Overview

健康界面是 SDKTestHelper 演示工程中的一个 TwsHealthViewController 页面,核心目标是向开发者直观展示 TWS 耳机健康传感器的能力与实时数据。它的设计要点:

  1. 能力探测:读取设备的健康能力配置(JLTwsHealthConfig),让开发者确认设备支持哪些传感器。
  2. 传感器状态检查:查询心率、血氧、计步传感器的开关状态以及佩戴检测(in-ear)状态。
  3. 实时数据可视化:通过 RxSwift BehaviorSubject 把 BLE 回调数据流式投递到 UI,实时更新步数、心率等数值。
  4. 操作验证入口:以列表形式提供 13 个可触发动作(开始/停止心率、血氧、计步、日常心率同步、实时步数查询、轮询等),方便集成测试。

该页面位于 SDKTestHelper 工程而非主 App JL_Health 工程内,属于 SDK 集成验证工具;它直接依赖 JL_BLEKit 框架公开的健康管理类 JLTwsHealthManager(头文件位于主工程 JL_Health/Frameworks/JL_BLEKit.xcframework 中)。数据可视化采用「订阅 + 文本渲染」模式:每个健康数据源对应一个 BehaviorSubject,UI 层用 observe(on: MainScheduler.instance) 切换到主线程后写入 UILabel。

Architecture

flowchart TD
    subgraph sg_UI["UI 层 (TwsHealthViewController)"]
        NavBar["navigationView (标题)"]
        StatusStack["statusStack (垂直 UIStackView)"]
        HealthLabel["healthInfoLabel (健康能力)"]
        SensorLabel["sensorStatusLabel (传感器状态)"]
        StepLabel["realTimeStepLabel (实时步数)"]
        HeartLabel["dailyHeartLabel (日常心率)"]
        Table["tableView (13 项功能列表)"]
        StatusStack --> HealthLabel
        StatusStack --> SensorLabel
        StatusStack --> StepLabel
        StatusStack --> HeartLabel
    end

    subgraph sg_VM["ViewModel 层 (TwsHealthViewModel)"]
        VM["TwsHealthViewModel<br/>(JLTwsHealthManagerDelegate)"]
        CfgSub["healthConfigSubject"]
        SensorSub["sensorStatusSubject"]
        FeedSub["opFeedbackSubject"]
        StepSub["stepSubject"]
        HeartSub["dailyHeartSubject"]
    end

    subgraph sg_SDK["SDK 层 (JL_BLEKit)"]
        Manager["JLTwsHealthManager"]
        CfgModel["JLTwsHealthConfig"]
        HeartModel["JLTwsHealthHeartRateModel"]
    end

    subgraph sg_Device["BLE 设备"]
        Device["TWS 耳机 (心率/血氧/计步传感器)"]
    end

    NavBar --> StatusStack
    Table -->|"didSelectRowAt 分发"| VM
    VM --> CfgSub
    VM --> SensorSub
    VM --> FeedSub
    VM --> StepSub
    VM --> HeartSub
    CfgSub -->|"observe on MainScheduler"| HealthLabel
    SensorSub -->|"observe on MainScheduler"| SensorLabel
    StepSub -->|"observe on MainScheduler"| StepLabel
    HeartSub -->|"observe on MainScheduler"| HeartLabel
    FeedSub -->|"showToast"| UI["Toast 提示"]
    VM -->|"调用 API"| Manager
    Manager -->|"回调 (Delegate)"| VM
    Manager --> CfgModel
    Manager --> HeartModel
    Manager <-->|"BLE 指令/数据"| Device

架构说明:

  • UI 层采用典型的 UIKit 手写布局(SnapKit 约束),上部为 statusStack 状态展示区(4 个 UILabel),下部为 UITableView 功能操作列表。两区块通过 navigationView 下方的约束衔接。
  • ViewModel 层是数据可视化核心:TwsHealthViewModel 实现 JLTwsHealthManagerDelegate 协议,把 SDK 的异步回调转换成 5 个 RxSwift Subject,形成「设备事件 → Subject → UI 订阅」的单向数据管道。每个 Subject 独立负责一类健康数据,互不阻塞。
  • SDK 层由 JL_BLEKit 框架提供 JLTwsHealthManager 及数据模型 JLTwsHealthConfig、JLTwsHealthHeartRateModel,是健康数据采集能力的来源;UI 与 ViewModel 只依赖公开头文件,不接触 BLE 细节。
  • 数据流方向:用户点击列表 → ViewModel 调用 Manager API → 设备返回 → Delegate 回调 → Subject 发射 → UI 主线程更新标签。整个过程为单向依赖,便于单元测试与替换实现。

Main Content

1. 界面布局(initUI)

TwsHealthViewController 继承自 BaseViewController,在 initUI() 中构建两个核心区域:

  • 状态展示区:一个垂直 UIStackView(statusStack,间距 8),包含 4 个 UILabel,均设置 numberOfLines = 0 与 14pt 系统字体,用于展示健康能力、传感器状态、实时步数、日常心率。
  • 操作列表区:UITableView(.insetGrouped 样式),注册 UITableViewCell,数据源为 Item.allCases(13 项),每行带 disclosureIndicator。

布局上,statusStack 顶部对齐 navigationView 底部(偏移 8),左右内边距 16;tableView 顶部对齐 statusStack 底部(偏移 8),其余方向填满父视图。这样保证状态区固定、操作列表滚动,长文本(如完整健康能力描述)不会挤压表格。

override func initUI() {
    super.initUI()
    navigationView.title = R.localStr.twsHealth()
    statusStack.axis = .vertical
    statusStack.spacing = 8
    view.addSubview(statusStack)
    [healthInfoLabel, sensorStatusLabel, realTimeStepLabel, dailyHeartLabel].forEach { lbl in
        lbl.numberOfLines = 0
        lbl.font = .systemFont(ofSize: 14)
        statusStack.addArrangedSubview(lbl)
    }
    statusStack.snp.makeConstraints { make in
        make.top.equalTo(navigationView.snp.bottom).offset(8)
        make.left.right.equalToSuperview().inset(16)
    }

    view.addSubview(tableView)
    tableView.snp.makeConstraints { make in
        make.top.equalTo(statusStack.snp.bottom).offset(8)
        make.left.right.bottom.equalToSuperview()
    }
    tableView.dataSource = self
    tableView.delegate = self
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

    bindVM()
}

来源:TwsHealthViewController.swift

设计意图:把「状态展示」与「操作入口」分离——上方是只读的实时数据看板,下方是交互式功能验证列表,两者解耦后,新增健康操作只需扩展 Item 枚举与 switch 分发,无需改动状态区。

Main Content(续)

2. 功能清单与操作分发(Item 枚举)

页面将全部健康操作建模为 Item: CaseIterable 枚举,共 13 项,每项提供本地化标题(通过 R.localStr 国际化资源)。枚举与 ViewModel 方法一一对应,表格点击后按 switch 分发:

枚举项本地化标题(示意)ViewModel 调用
readConfig读取健康能力vm.readHealthConfig()
checkSensors检查传感器状态vm.checkSensorStatus()
startHeart / cancelHeart开始/取消心率vm.startHeartRate() / vm.cancelHeartRate()
startSpO2 / cancelSpO2开始/取消血氧vm.startSpO2() / vm.cancelSpO2()
startStep / cancelStep开始/取消计步vm.startStep() / vm.cancelStep()
startDailyHeart / endDailyHeart开始/结束日常心率同步vm.startDailyHeartSync() / vm.endDailyHeartSync()
queryRealStep查询实时步数vm.queryRealStep()
loopRealStep设置步数轮询间隔vm.loopQuery(intervalSec:)(需弹窗输入)
cancelLoopStep取消步数轮询vm.cancelLoopQuery()
enum Item: CaseIterable {
    case readConfig
    case checkSensors
    case startHeart
    case cancelHeart
    case startSpO2
    case cancelSpO2
    case startStep
    case cancelStep
    case startDailyHeart
    case endDailyHeart
    case queryRealStep
    case loopRealStep
    case cancelLoopStep

    var title: String {
        switch self {
        case .readConfig: return R.localStr.readHealthCapability()
        case .checkSensors: return R.localStr.checkSensorStatus()
        case .startHeart: return R.localStr.startHeartRate()
        case .cancelHeart: return R.localStr.cancelHeartRate()
        case .startSpO2: return R.localStr.startBloodOxygen()
        case .cancelSpO2: return R.localStr.cancelBloodOxygen()
        case .startStep: return R.localStr.startStepCount()
        case .cancelStep: return R.localStr.cancelStepCount()
        case .startDailyHeart: return R.localStr.startDailyHeartSync()
        case .endDailyHeart: return R.localStr.endDailyHeartSync()
        case .queryRealStep: return R.localStr.queryRealTimeStep()
        case .loopRealStep: return R.localStr.setStepPollingInterval()
        case .cancelLoopStep: return R.localStr.cancelStepPolling()
        }
    }
}

来源:TwsHealthViewController.swift

列表点击后的分发逻辑如下——queryRealStep 直接查询,loopRealStep 则先弹出输入框让用户指定轮询间隔(推荐 300 秒内,示例 240 秒),再调用 loopQuery:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    switch items[indexPath.row] {
    case .readConfig: vm.readHealthConfig()
    case .checkSensors: vm.checkSensorStatus()
    case .startHeart: vm.startHeartRate()
    case .cancelHeart: vm.cancelHeartRate()
    case .startSpO2: vm.startSpO2()
    case .cancelSpO2: vm.cancelSpO2()
    case .startStep: vm.startStep()
    case .cancelStep: vm.cancelStep()
    case .startDailyHeart: vm.startDailyHeartSync()
    case .endDailyHeart: vm.endDailyHeartSync()
    case .queryRealStep: vm.queryRealStep()
    case .loopRealStep:
        promptInterval { [weak self] sec in
            self?.vm.loopQuery(intervalSec: sec)
        }
    case .cancelLoopStep: vm.cancelLoopQuery()
    }
}

来源:TwsHealthViewController.swift

设计意图:CaseIterable + switch 的组合让「功能清单」成为单一事实来源——新增一项能力只需加一个枚举 case、一个本地化标题和一行分发,表格行数、标题、点击行为自动保持一致,避免列表数组与分发逻辑不同步的常见缺陷。

3. 数据绑定与可视化(bindVM)

数据可视化完全由 RxSwift 驱动。bindVM() 订阅 ViewModel 暴露的 5 个 BehaviorSubject,全部通过 observe(on: MainScheduler.instance) 切回主线程后再写 UI,从机制上保证不会出现后台线程直接触碰 UIKit 的崩溃:

private func bindVM() {
    vm.healthConfigSubject
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] cfg in
            guard let self = self, let cfg = cfg else { return }
            let msg =  R.localStr.deviceHealthCapability() + ": " + "\(cfg)"
            self.healthInfoLabel.text = msg
        }).disposed(by: bag)

    vm.sensorStatusSubject
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] st in
            guard let self = self, let st = st else { return }
            let msg = "\(R.localStr.heartRate()): \(st.heart ? R.localStr.on() : R.localStr.off())\n\(R.localStr.spo2()): \(st.spO2 ? R.localStr.on() : R.localStr.off())\n\(R.localStr.stepCount()): \(st.step ? R.localStr.on() : R.localStr.off())\n\(R.localStr.inEar()): \(st.inEar ? R.localStr.insideTheEar() : R.localStr.outsideTheEar())"
            self.sensorStatusLabel.text = R.localStr.sensorStatus() + ":\n" + msg
        }).disposed(by: bag)

    vm.opFeedbackSubject
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] text in
            self?.showToast(text)
        }).disposed(by: bag)

    vm.stepSubject
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] step in
            if let self = self {
                let msg = R.localStr.realTimeStep() + ": " + R.localStr.stepCount() + "\(step)"
                self.realTimeStepLabel.text = msg
            }
        }).disposed(by: bag)

    vm.dailyHeartSubject
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] model in
            if let self = self {
                let msg = R.localStr.dailyHeartRate() + ": " + "\(model)"
                self.dailyHeartLabel.text = msg
            }
        }).disposed(by: bag)
}

来源:TwsHealthViewController.swift

各 Subject 的可视化语义:

  • healthConfigSubject → healthInfoLabel:把 JLTwsHealthConfig 的能力描述直接渲染为文本(\(cfg) 依赖模型自定义 CustomStringConvertible 输出,具体格式定义在 JL_BLEKit 头文件中)。
  • sensorStatusSubject → sensorStatusLabel:用多行文本可视化 4 个布尔状态(心率、血氧、计步开关 + 佩戴检测),on/off、inside/outside 全部走本地化资源。
  • opFeedbackSubject → showToast:操作级反馈(如「开始心率成功」)以 Toast 形式提示,不复用状态标签,避免覆盖实时数据。
  • stepSubject → realTimeStepLabel:实时步数为纯数值,直接拼接单位文本。
  • dailyHeartSubject → dailyHeartLabel:日常心率同步结果整体输出 JLTwsHealthHeartRateModel 的文本描述。

每个订阅都使用 [weak self] + guard let self 避免闭包持有控制器造成循环引用,这是 RxSwift 订阅的标准防护模式。

4. 轮询交互与提示组件

loopRealStep(步数轮询)需要用户输入间隔秒数,页面通过 UIAlertController 弹窗收集输入:数字键盘、占位符 example240(示例 240 秒)、提示「推荐 300 秒以内」,只有解析成功且 v > 0 才会回调完成闭包——非法输入被静默忽略,不会调用 SDK:

private func promptInterval(completion: @escaping (TimeInterval) -> Void) {
    let alert = UIAlertController(title: R.localStr.setPollingInterval(), message: R.localStr.recommendedWithin300Seconds(), preferredStyle: .alert)
    alert.addTextField { tf in
        tf.keyboardType = .numberPad
        tf.placeholder = R.localStr.example240()
    }
    alert.addAction(UIAlertAction(title: R.localStr.cancel(), style: .cancel, handler: nil))
    alert.addAction(UIAlertAction(title: R.localStr.oK(), style: .default, handler: { _ in
        if let text = alert.textFields?.first?.text, let v = TimeInterval(text), v > 0 {
            completion(v)
        }
    }))
    present(alert, animated: true)
}

来源:TwsHealthViewController.swift

此外,页面内置了自绘 showToast 组件(半透明黑底圆角标签 + 两段 UIView.animate 淡入淡出,1.5 秒后移除),不依赖第三方 Toast 库:

private func showToast(_ text: String) {
    let label = UILabel()
    label.text = text
    label.textAlignment = .center
    label.textColor = .white
    label.backgroundColor = UIColor.black.withAlphaComponent(0.7)
    label.numberOfLines = 0
    label.layer.cornerRadius = 8
    label.clipsToBounds = true
    view.addSubview(label)
    label.snp.makeConstraints { make in
        make.centerX.equalToSuperview()
        make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20)
        make.width.lessThanOrEqualTo(view.snp.width).multipliedBy(0.8)
    }
    UIView.animate(withDuration: 0.25, animations: {
        label.alpha = 1
    }) { _ in
        UIView.animate(withDuration: 0.25, delay: 1.5, options: [], animations: {
            label.alpha = 0
        }) { _ in
            label.removeFromSuperview()
        }
    }
}

来源:TwsHealthViewController.swift

Core Flow

一次完整的「查询实时步数」操作在系统中按如下时序流动。该时序是页面所有操作的通用模板:UI 事件 → ViewModel 转发 → SDK 指令 → BLE 设备 → Delegate 回调 → Subject 发射 → 主线程渲染:

sequenceDiagram
    participant U as 用户
    participant V as TwsHealthViewController
    participant VM as TwsHealthViewModel
    participant SDK as JLTwsHealthManager (JL_BLEKit)
    participant D as TWS 设备

    U->>V: 点击「查询实时步数」cell
    V->>V: didSelectRowAt → deselectRow
    V->>VM: vm.queryRealStep()
    VM->>SDK: 调用实时步数查询 API
    SDK->>D: 发送 BLE 读取指令
    D-->>SDK: 返回步数数据
    SDK-->>VM: delegate 回调 (step 数值)
    VM->>VM: stepSubject.onNext(step)
    VM-->>V: stepSubject 发射(后台线程)
    V->>V: observe(on: MainScheduler) 切主线程
    V->>V: realTimeStepLabel.text = 本地化拼接
    V-->>U: 界面实时刷新步数

对于「设置步数轮询」操作,流程在进入 ViewModel 前多一步:didSelectRowAt 先调用 promptInterval 弹窗,用户输入秒数并确认后,loopQuery(intervalSec:) 才被调用;此后 SDK 按间隔持续回调,stepSubject 持续发射,UI 无需额外处理即自动刷新。

数据模型与 SDK 契约

健康界面的数据来源是 JL_BLEKit 框架公开的三个健康相关类型(头文件位于主工程 Frameworks 目录):

类型头文件职责
JLTwsHealthManagerJLTwsHealthManager.hTWS 健康管理入口,提供心率/血氧/计步/日常心率等 API,并通过 JLTwsHealthManagerDelegate 回调结果
JLTwsHealthConfigJLTwsHealthConfig.h设备健康能力配置(哪些传感器可用),渲染到 healthInfoLabel
JLTwsHealthHeartRateModelJLTwsHealthHeartRateModel.h日常心率数据模型,渲染到 dailyHeartLabel

TwsHealthViewModel 声明为 JLTwsHealthManagerDelegate 的实现者,并暴露首个数据管道 healthConfigSubject:

class TwsHealthViewModel: NSObject, JLTwsHealthManagerDelegate {
    let healthConfigSubject = BehaviorSubject<JLTwsHealthConfig?>(value: nil)
}

来源:TwsHealthViewModel.swift

注:ViewModel 中 sensorStatusSubject、opFeedbackSubject、stepSubject、dailyHeartSubject 以及各操作方法的完整实现位于同一文件后续部分,本次文档编写时未展开读取,具体签名以源码为准。

API Reference

TwsHealthViewController

公开交互入口(均为内部实现,供页面自身使用):

成员类型/签名说明
Itemenum Item: CaseIterable13 项健康操作清单,含本地化 title
initUI()override func initUI()构建状态区 + 列表区,并调用 bindVM()
bindVM()private func bindVM()订阅 ViewModel 的 5 个 Subject,主线程更新 UI
tableView(_:didSelectRowAt:)UITableViewDelegate 回调按 Item 分发到对应 ViewModel 方法
promptInterval(completion:)private func (TimeInterval) -> Void弹窗收集轮询间隔,v > 0 才回调
showToast(_:)private func (String)自绘 Toast,1.5 秒后自动消失

TwsHealthViewModel

数据管道(已从源码确认):

  • healthConfigSubject: BehaviorSubject<JLTwsHealthConfig?> — 健康能力配置流,初始值 nil。

页面调用的方法(依据 TwsHealthViewController 的调用点归纳,签名细节以 ViewModel 源码为准):readHealthConfig()、checkSensorStatus()、startHeartRate()、cancelHeartRate()、startSpO2()、cancelSpO2()、startStep()、cancelStep()、startDailyHeartSync()、endDailyHeartSync()、queryRealStep()、loopQuery(intervalSec:)、cancelLoopQuery()。

JLTwsHealthManagerDelegate(SDK 契约)

TwsHealthViewModel 遵循该协议以接收健康数据回调;协议方法把 SDK 异步结果写入对应 Subject。协议完整方法列表定义于 JLTwsHealthManager.h。

配置选项

选项类型默认/推荐值说明
步数轮询间隔(loopRealStep)TimeInterval推荐 ≤ 300 秒;示例 240 秒由用户弹窗输入,必须 > 0,否则忽略
状态标签字体UIFont.systemFont(ofSize: 14)4 个状态标签统一字号,numberOfLines = 0 允许多行
Toast 展示时长TimeInterval1.5 秒(delay: 1.5)淡入 0.25s、停留后淡出 0.25s
列表样式UITableView.Style.insetGroupediOS 分组列表样式

以上选项均硬编码于 TwsHealthViewController.swift,未提供外部配置入口;开发者可直接修改常量或抽取为参数。

Failure Modes, Edge Cases & Concurrency

以下结论基于已读取的 TwsHealthViewController 源码;ViewModel/SDK 内部错误处理以对应源码为准。

  • 线程安全(并发):所有 UI 更新都经过 observe(on: MainScheduler.instance),SDK 回调线程与主线程之间的数据传递完全由 RxSwift 调度器隔离。这是 Rx 模式相对直接回调方案的核心优势——Delegate 回调可能在任意队列触发,但 UI 永远只在主线程被触碰。
  • 循环引用防护:所有订阅闭包均使用 [weak self],bindVM() 的订阅由 DisposeBag(bag)持有;控制器释放时 bag 随之释放,自动取消全部订阅,避免「控制器已销毁但 SDK 仍回调」导致的悬垂引用与内存泄漏。
  • 初始值语义:BehaviorSubject 自带初始值(如 healthConfigSubject 初始为 nil),订阅建立后立即回放最近值。UI 层用 guard let cfg = cfg 对 nil 做空值过滤,因此页面加载瞬间标签保持为空、不显示异常内容。
  • 非法输入边界:轮询间隔弹窗要求 v > 0;0、负数、非数字文本会被静默丢弃(不弹错误提示、不调用 SDK)。推荐上限 300 秒仅作为文案提示,源码未做硬性上限校验——集成方若担心设备负载,需在 ViewModel 层补充校验。
  • 设备能力缺失:页面假设设备支持对应传感器;若 JLTwsHealthConfig 指示某项能力不可用,界面仍会渲染该能力描述文本,但操作是否报错取决于 SDK 实现。集成时建议先 readHealthConfig() 再按能力裁剪列表项。
  • 操作反馈通道:异步操作(开始/取消心率等)的成功/失败通过 opFeedbackSubject 以 Toast 呈现,与数据流(状态标签)分离——失败不会污染实时数据,但也不会自动恢复 UI 状态(如按钮态),属于演示级反馈。
  • 重复操作竞态:列表允许用户快速连续点击(如重复 startHeart),页面未做互斥/防抖;若 SDK 不支持重入,可能出现重复指令。演示场景可接受,正式产品建议在 ViewModel 中加状态机保护。

Performance & Operational Notes

  • 轮询频率控制:loopQuery(intervalSec:) 的间隔由用户指定,界面提示「推荐 300 秒以内」。过短的间隔会持续占用 BLE 通道、增加耳机功耗,运维/集成时应结合设备功耗预算选择间隔。
  • 渲染开销:UI 更新仅为 4 个 UILabel 的文本赋值,无列表刷新、无布局重算(约束固定),性能开销可忽略;即使步数轮询高频回调,主线程负载也极低。
  • Toast 生命周期:showToast 每次创建新 UILabel 并在动画结束后 removeFromSuperview;高频触发 Toast 时旧实例可能未完全移除(依赖动画 completion 回调),演示场景无碍。
  • 本地化成本:所有文案走 R.localStr 资源表,新增语言只需补充资源,无需改动代码逻辑。

Extension Points

  • 新增健康操作:在 Item 枚举加 case → 补 title 本地化 → didSelectRowAt switch 中加一行调用 → ViewModel 增加对应方法与 Subject(如需展示数据)。四个触点协同扩展,结构清晰。
  • 替换数据管道:UI 只依赖 ViewModel 的 Subject 契约;若换用非 JL_BLEKit 数据源,只需重写 ViewModel(保持同名 Subject 与回调方法),ViewController 完全不需要改动——这是「UI 与 SDK 解耦」的设计红利。
  • 数据模型渲染:JLTwsHealthConfig、JLTwsHealthHeartRateModel 以 \(model) 字符串形式渲染,展示格式由模型的 CustomStringConvertible 决定;需要更精致的可视化(图表、仪表盘)时,可订阅同一 Subject 并替换为自定义视图组件。
  • 国际化:R.localStr 资源表集中管理全部文案,是扩展多语言支持的标准入口。

Tests

健康界面位于 SDKTestHelper 演示工程(code/SDKTestHelper/SDKTestHelper/JLSDKTest/Controllers/DefaultSetViewControllers/TwsHealth/),该工程本质上承担 SDK 集成验证职责:TwsHealthViewController 的 13 项操作覆盖了健康 SDK 的读配置、传感器状态、心率、血氧、计步、日常心率、实时步数、轮询全部公开能力,可视为手动集成测试清单。未发现针对该页面的自动化单元测试文件;ViewModel 与 SDK 的交互可通过真机/模拟器 + 蓝牙设备手动验证。开发者可基于 TwsHealthViewModel 的 Subject 契约编写 Mock 驱动的单元测试(注入假 Delegate 回调即可断言 UI 文本)。

Related Links

  • TwsHealthViewController.swift(界面与数据可视化实现)
  • TwsHealthViewModel.swift(ViewModel 数据管道)
  • JLTwsHealthManager.h(SDK 健康管理契约)
  • JLTwsHealthConfig.h(健康能力配置模型)
  • JLTwsHealthHeartRateModel.h(日常心率模型)
  • 相关页面:表盘市场与云端健康服务接口(DialMarketHttp.swift 的 /health/v1/api)、设备连接与 SDK 框架总览
Prev
应用架构与页面导航
Next
设备连接与数据同步