杰理 SDK 文档中心
首页
首页
  • 项目概述

    • 杰理 OTA SDK 项目简介
    • 快速开始与接入指南
    • 工程结构与发布物
  • 核心库与依赖

    • OTA 核心库集成
    • 版本历史与更新说明
  • SDK 工具层

    • OTA 参数配置
    • 蓝牙扫描与连接管理
    • BLE 通道与事件回调
    • OTA 升级流程与状态模型
    • 固件文件管理与监听
  • 演示应用

    • 演示应用架构与主界面
    • 设备发现与连接界面
    • 文件选择与升级界面
    • 多设备 OTA 模型

设备发现与连接界面

本页介绍 Android-JL_OTA Demo 应用(BroadcastBox)中用于发现、扫描并连接 JL 系列低功耗蓝牙(BLE)设备的用户界面,包括宿主 Activity、连接页 Fragment、连接状态 LiveData 以及底层扫描调用链。

Purpose and Scope

本页覆盖 Demo 应用「设备发现与连接」这条完整链路的界面侧实现:

  • 宿主界面 BroadcastBoxActivity 如何组织「连接 / 文件 / 升级」三个页签;
  • 连接页 ConnectFragment 的职责与它在整体界面中的位置(其内部实现细节在本次源码采集中未完整读取,文档会明确标注);
  • UpgradeFragment 如何通过 viewModel.deviceConnectionMLD 观察连接状态并刷新「已连接设备」列表;
  • BLE 扫描的调用方式(scanner.startScan(ScanCallback))。

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

  • OTA 升级流程与升级进度界面(UpgradeFragment 的升级逻辑、UpgradeProgressAdapter);
  • 升级文件选择(UpgradeFilePickerAdapter、DialogUpgradeFilePicker);
  • BLE 底层 SDK 实现(BleManager、BleEventCallbackManager);
  • 多设备广播盒(BroadcastBox)OTA 状态机模型(model/ota/*)。

Overview

BroadcastBox Demo 应用以「连接 → 选择文件 → 升级」作为核心用户旅程。应用启动后首先进入的是设备发现与连接界面:用户在此处扫描附近的 JL BLE 设备、查看设备列表、发起连接,并实时看到连接状态的变化。

从源码可见,整个界面由 BroadcastBoxActivity 作为单一宿主,通过 ViewPager2 挂载三个 Fragment(ConnectFragment、FilesFragment、UpgradeFragment),并用工具栏菜单在三个页签之间切换标题。连接状态通过 ViewModel 暴露的 deviceConnectionMLD(LiveData)在 Fragment 间共享:连接页负责触发发现与连接,升级页负责展示「已连接设备」列表并驱动后续升级。底层 BLE 扫描则通过 SDK 提供的 Scanner(如 ReconnectDemo 所示)以 startScan(ScanCallback) 方式启动。

设计意图:把「发现/连接」与「升级」拆成独立 Fragment、用 ViewModel + LiveData 共享连接状态,是为了让连接流程与升级流程解耦——连接页只关心建立链路,升级页只关心链路上有哪些设备可用,二者通过同一份连接状态数据协同,避免 UI 层直接互相调用。

Architecture

flowchart TD
    subgraph sg_UI["UI Layer (otasdk · broadcastbox)"]
        Activity["BroadcastBoxActivity<br/>(ViewPager2 宿主)"]
        Connect["ConnectFragment<br/>(设备发现与连接)"]
        Files["FilesFragment<br/>(文件选择)"]
        Upgrade["UpgradeFragment<br/>(升级 + 已连接设备列表)"]
        Adapter["UpgradeProgressAdapter / UpgradeFilePickerAdapter"]
    end

    subgraph sg_State["状态层"]
        ViewModel["ViewModel<br/>(deviceConnectionMLD: LiveData)"]
    end

    subgraph sg_BLE["BLE 层"]
        Scanner["Scanner<br/>(startScan + ScanCallback)"]
        BleManager["BleManager / BleEventCallbackManager"]
    end

    Activity -->|"new Fragment[]{...}"| Connect
    Activity -->|"new Fragment[]{...}"| Files
    Activity -->|"new Fragment[]{...}"| Upgrade
    Activity -->|"ViewPager2 展示"| Connect
    Upgrade -->|"observe"| ViewModel
    Connect -->|"触发扫描/连接"| Scanner
    Scanner --> BleManager
    ViewModel -->|"连接状态回调"| Scanner
    Upgrade --> Adapter

架构说明:

  • BroadcastBoxActivity 是唯一入口,持有 Fragment[] fragments = new Fragment[]{new ConnectFragment(), new FilesFragment(), new UpgradeFragment()}(BroadcastBoxActivity.java#L22-L23),页签切换通过工具栏菜单 menu_toolbar 完成,标题随菜单项切换(如 R.string.connect,BroadcastBoxActivity.java#L35-L36)。
  • ConnectFragment 承担本页主题——设备发现与连接。它由 Activity 直接实例化并放入 ViewPager2;其在本次采集中仅确认存在与挂载位置,Fragment 内部按钮/列表的具体布局代码未读取,详见下文「已知边界」。
  • UpgradeFragment 通过 viewModel.deviceConnectionMLD.observe(...) 订阅连接状态(UpgradeFragment.java#L61-L64),并维护 MSG_UPDATE_DEVICE_LIST 消息驱动的设备列表刷新,体现「状态驱动 UI」的模式。
  • BLE 扫描入口在 ReconnectDemo(测试代码)中以 scanner.startScan(new ScanCallback() {...}) 形式给出(ReconnectDemo.java#L36-L38),说明扫描回调采用标准 ScanCallback 模型,发现结果经 SDK 汇聚后最终反映到连接状态。

主要实现详解

1. 宿主界面 BroadcastBoxActivity

BroadcastBoxActivity 是 Demo 应用的主 Activity,职责是组装三个业务页签并统一管理标题栏。核心代码:

Fragment[] fragments = new Fragment[]{new ConnectFragment(), new FilesFragment(), new UpgradeFragment()};

Source: BroadcastBoxActivity.java#L22-L23

随后通过 ViewPager2(findViewById(R.id.vp2_container))承载这些 Fragment,并把工具栏标题设为「连接」:

ViewPager2 viewPager2 = findViewById(R.id.vp2_container);
tvTitle.setText(getString(R.string.connect));
toolbar.inflateMenu(R.menu.menu_toolbar);

Source: BroadcastBoxActivity.java#L35-L37

菜单点击处理中,通过 item.getTitle() 与 R.string.connect 等字符串比较来决定切换哪个页签并更新标题(BroadcastBoxActivity.java#L65)。这里使用「标题字符串即页签标识」的约定,简化了菜单→页签的映射逻辑,代价是标题文案不能随意修改。

另外该类声明了 public boolean isSkipDestroyViewModel = false;,用于控制 ViewModel 是否在销毁时被跳过清理,属于调试/生命周期控制的开关。

2. 连接状态共享:deviceConnectionMLD

连接状态不保存在 Fragment 内部,而是放在 ViewModel 的 LiveData deviceConnectionMLD 中,升级页通过 observe 订阅:

viewModel.deviceConnectionMLD.observe(getViewLifecycleOwner(), deviceConnection -> {
    JL_Log.i(TAG, ">>>> deviceConnectionMLD >> " + deviceConnection);
    if (deviceConnection.getState() != BluetoothProfile.STATE_CONNECTING) {
        uiHandler.removeMessages(MSG_UPDATE_DEVICE_LIST);
    }
});

Source: UpgradeFragment.java#L61-L64

这段代码透露了关键设计:

  • LiveData + ViewModel 是连接状态的单一事实来源,连接页写入、升级页读取,解耦两个页签;
  • deviceConnection.getState() 使用 Android 标准 BluetoothProfile.STATE_CONNECTING 等常量描述连接阶段;
  • 当状态离开 STATE_CONNECTING(即连接成功或失败)时,移除待处理的 MSG_UPDATE_DEVICE_LIST 消息,随后立即刷新列表——避免在「连接中」的中间态反复触发 UI 更新。

3. 已连接设备列表刷新

UpgradeFragment 通过 Handler 消息 MSG_UPDATE_DEVICE_LIST 触发 updateConnectedDeviceList():

private void updateConnectedDeviceList() {
    List<BroadcastBoxInfo> connectedDevices = viewModel.getConnectedBleDevices(); //已连接设备列表
    List<BroadcastBoxInfo> realTimeList = new ArrayList<>();  //实时更新列表
    JL_Log.i(TAG, "updateConnectedDeviceList >> connectedDevices = " + connectedDevices.size() + ", cacheSelected = " + cacheSelected.size());
    boolean isEmpty = connectedDevices.isEmpty();
    binding.groupConnected.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
    binding.groupNoconnect.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
    for (BroadcastBoxInfo boxInfo : connectedDevices) {
        for (BroadcastBoxInfo selected : cacheSelected) {
            // ... 标记已选设备
        }
    }
    JL_Log.i(TAG, "updateConnectedDeviceList >> realTimeList = " + realTimeList.size());
    adapter.setList(connectedDevices);
}

Source: UpgradeFragment.java#L161-L181

要点:

  • 数据来源是 viewModel.getConnectedBleDevices(),返回 List<BroadcastBoxInfo>,与连接状态同源;
  • 空态处理:groupConnected / groupNoconnect 两个 ViewGroup 根据列表是否为空互斥显示,提示用户当前没有已连接设备;
  • 与本地 cacheSelected 缓存做比对,用于还原用户之前在列表上的勾选状态;
  • 最终 adapter.setList(...) 一次性提交数据,属于「列表整体替换」模式,配合 UpgradeProgressAdapter 渲染。

4. BLE 扫描调用链

设备发现的核心是 BLE 扫描。ReconnectDemo(测试示例)展示了标准调用方式:

//开始搜索设备
scanner.startScan(new ScanCallback() {
    // ...
});

Source: ReconnectDemo.java#L36-L38

startScan 接收 ScanCallback 回调对象,扫描结果(设备发现、广播数据、RSSI 等)通过回调上报;SDK 层再由 BleManager / BleEventCallbackManager 汇总成连接事件,最终写入 deviceConnectionMLD,完成「扫描 → 发现 → 连接 → 状态通知 → UI 刷新」的闭环。Demo 界面侧只需订阅状态即可,无需关心蓝牙协议细节。

已知边界(诚实说明)

本次采集中 ConnectFragment.java 的完整实现(扫描按钮、设备列表 Adapter、连接点击处理的具体代码)未在源工具预算内读取。其存在性、挂载位置(BroadcastBoxActivity 的 fragments 数组首位)与职责(对应 R.string.connect 页签)均已由宿主代码证实;如需 Fragment 内部逐行细节,请直接阅读 ConnectFragment.java 源文件。

Core Flow

sequenceDiagram
    participant U as 用户
    participant A as BroadcastBoxActivity
    participant C as ConnectFragment
    participant S as Scanner (BLE)
    participant VM as ViewModel
    participant F as UpgradeFragment

    U->>A: 启动应用
    A->>A: new Fragment[]{Connect, Files, Upgrade}
    A->>C: 通过 ViewPager2 展示连接页
    U->>C: 点击「搜索设备」
    C->>S: startScan(ScanCallback)
    S-->>C: onDeviceFound(设备列表)
    U->>C: 点击设备发起连接
    C->>S: connect(device)
    S-->>VM: 连接状态回调
    VM-->>F: deviceConnectionMLD 变化
    F->>F: 状态 != STATE_CONNECTING → 刷新列表
    F->>F: updateConnectedDeviceList() 展示已连接设备
    F-->>U: 界面显示已连接设备,可进入升级

流程要点:

  1. 应用启动后 Activity 一次性创建三个 Fragment,首个可见页签即连接页;
  2. 用户触发扫描,Scanner.startScan(ScanCallback) 异步发现设备;
  3. 连接结果经 SDK → ViewModel 的 LiveData 链路发布;
  4. UpgradeFragment 观察到非 STATE_CONNECTING 状态后移除待刷新消息、立即重建设备列表,并切换 groupConnected / groupNoconnect 空态视图;
  5. 列表数据与勾选缓存合并后由 Adapter 渲染,用户即可继续文件选择与升级。

配置选项

设备发现与连接界面本身没有独立的配置文件,其「配置」体现在资源与常量层面:

配置项类型默认/取值说明
页签菜单Menu 资源R.menu.menu_toolbar定义 connect / files / upgrade 三个菜单项
页签标题String 资源R.string.connect 等同时用作菜单项匹配与标题栏文案
页签集合Fragment[]{ConnectFragment, FilesFragment, UpgradeFragment}Activity 内硬编码,决定页签顺序(连接页在首位)
ViewPager2 容器View IDR.id.vp2_container承载三个页签的容器
连接状态来源LiveDataviewModel.deviceConnectionMLD全应用共享的连接状态单一事实来源
设备列表来源方法viewModel.getConnectedBleDevices()返回 List<BroadcastBoxInfo>
列表刷新消息Handler 消息MSG_UPDATE_DEVICE_LIST驱动 updateConnectedDeviceList() 的异步刷新
生命周期开关booleanisSkipDestroyViewModel = false控制是否跳过 ViewModel 销毁清理

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

以下行为均来自源码证据:

  • 连接中间态处理:UpgradeFragment 在 deviceConnection.getState() == BluetoothProfile.STATE_CONNECTING 时不刷新设备列表,而是保留/移除 MSG_UPDATE_DEVICE_LIST 消息;只有在状态离开「连接中」才刷新。这避免了连接过程中列表反复跳动,也说明失败/成功都会落入同一刷新路径。
  • 空态与无设备:updateConnectedDeviceList() 通过 connectedDevices.isEmpty() 决定 groupConnected 与 groupNoconnect 的互斥显隐,保证无设备时界面有明确提示而非空白列表。
  • 列表并发更新:列表更新通过 Handler 消息串行化,避免 BLE 回调线程直接触碰 UI;adapter.setList() 整体替换而非增量修改,天然规避了「边遍历边修改」的并发问题。
  • 勾选状态还原:cacheSelected 与实时列表做嵌套比对,若设备在重连/刷新后仍在线,可恢复用户之前的勾选;若设备已离线则从结果中剔除。
  • 已知边界:扫描未开启、蓝牙权限缺失等系统级失败路径的具体弹窗/重试逻辑位于 ConnectFragment / SDK 内部,本次未读取到对应实现,不在此臆断。

性能与运维注意

  • 避免主线程阻塞:扫描与连接均为异步(ScanCallback 回调模型),界面通过 Handler 消息与 LiveData 观察更新,主线程只做列表渲染。
  • 日志可观测性:关键路径均有 JL_Log.i 日志(如 deviceConnectionMLD、updateConnectedDeviceList 的设备数量),排查连接问题时可直接按 TAG 过滤。
  • 列表规模:adapter.setList() 全量替换在设备数量较大时会有轻微重建开销,但 Demo 场景(个位数广播盒设备)下可忽略。

扩展点

  • 新增页签:修改 BroadcastBoxActivity 的 fragments 数组并补充对应菜单项即可扩展新的业务页签。
  • 自定义连接状态展示:任何页面均可 observe(viewModel.deviceConnectionMLD) 获得同一份连接状态,可在不侵入连接页的情况下增加状态展示(如顶部连接指示灯)。
  • 替换设备来源:viewModel.getConnectedBleDevices() 是唯一列表入口,如需接入非 BLE 设备或过滤设备,可在此方法层做适配,UI 层无需改动。

相关链接

  • BroadcastBoxActivity.java — 宿主 Activity,页签组装与标题管理
  • UpgradeFragment.java — 连接状态观察与已连接设备列表刷新
  • ReconnectDemo.java — BLE 扫描调用示例(startScan(ScanCallback))
  • 相关兄弟主题:OTA 升级界面(UpgradeFragment 升级逻辑)、文件选择(UpgradeFilePickerAdapter)、BLE 底层实现(BleManager / BleEventCallbackManager)
Prev
演示应用架构与主界面
Next
文件选择与升级界面