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

    • 环境要求与权限配置
    • 集成SDK依赖
    • 运行示例应用
  • 核心架构与协议

    • RCSP协议与数据通道
    • 蓝牙连接与设备管理
    • TWS双耳功能
    • 基础功能接口与自定义命令
  • 设备功能控制

    • 设备音乐控制与ID3信息
    • 文件浏览与传输
    • FM收音与发射
    • 灯光控制
    • 闹钟与时间管理
    • 查找设备与防丢
    • ANC与噪声处理
    • 按键功能设置
    • 彩屏仓控制
    • AI翻译
  • 音效与音频处理

    • 均衡器音效调节
    • 录音与语音控制
    • Line-in、SPDIF与声卡功能
    • 音频编解码库
  • 扩展功能库

    • OTA固件升级
    • 数据加密与解密
    • 图片与动图格式转换
  • 示例应用 btsmart

    • 应用架构与界面导航
    • 设备功能适配与数据层
    • 设备配置JSON与资源文件
  • 参考与版本

    • 错误码参考
    • 版本历史与更新日志
    • 开发文档中心导航

设备功能适配与数据层

本页面向 btsmart 示例应用(PiHome V1.13.0 / SDK V4.2.0)中的数据层(data 包),重点解析设备功能适配器的实现机制:com.jieli.btsmart.data.adapter 下 20+ 个 RecyclerView 适配器、数据模型(model)与监听器(listeners),以及它们如何通过 BRVAH(BaseQuickAdapter)、Glide、DataBinding 与杰理蓝牙 SDK(RCSPController、DeviceStatusManager)协同工作,把设备功能、设备列表、设置项等数据渲染到界面。

Purpose and Scope

本页覆盖的内容:

  • data/adapter 包中适配器的共同基座(BaseQuickAdapter 封装)与典型实现(FunctionAdapter、DeviceListAdapter、CommonListAdapter 等)。
  • 数据模型(SettingsItem、HistoryDevice)与监听器接口(OnDeleteItemListener、OnLocationItemListener)在适配器中的角色。
  • BindingUtil 中面向 DataBinding 的自定义 @BindingAdapter(含列表数据注入 setRecycleViewData)。
  • 适配器与杰理蓝牙 SDK、Glide 之间的数据流与设备状态机映射。

不涉及的兄弟主题(各有独立页面):蓝牙协议与 RCSP 命令封装、UI 页面(ui.* 包中的 Activity/Fragment)、tool 包中的业务工具(如 ProductUtil、AppUtil)、OTA 固件升级流程本身。本页只在必要时提及这些依赖以说明数据流向。

Overview

btsmart 示例应用采用经典的 MVC/MVP 分层:ui 包中的页面(如设备连接页、功能设置页、闹钟设置页)通过 Presenter(如 IDeviceConnectContract.IDeviceConnectPresenter)获取数据,再交给 data.adapter 包中的适配器渲染到 RecyclerView。

数据层 com.jieli.btsmart.data 按职责分为三个子包:

子包职责代表类型
adapter列表渲染适配器,全部继承 BRVAH 的 BaseQuickAdapter<T, BaseViewHolder>DeviceListAdapter、FunctionAdapter、CommonListAdapter
model列表项的纯数据模型model.settings.SettingsItem、model.device.HistoryDevice
listeners列表项交互回调接口OnDeleteItemListener、OnLocationItemListener

“设备功能适配”体现在两个层面:

  1. 功能列表适配:FunctionAdapter、FunctionListAdapter、FuncSettingsAdapter 等把抽象的 SettingsItem(名称、值、图标)绑定到具体布局,实现同一份功能数据在不同设备/页面的差异化展示。
  2. 设备形态适配:DeviceListAdapter 根据设备型号(充电仓待机/工作、左右耳连接/待机、产品 Logo 等 ProductModel 常量)与连接状态(断开/已连接/使用中/连接中/强制升级)动态渲染不同外观,适配多形态 TWS/音箱/穿戴设备。

Architecture

flowchart TD
    subgraph sg_UI["UI 层 (ui.* 包)"]
        UI["设备连接页 / 功能设置页 / 闹钟页"]
        Presenter["Presenter 接口<br/>(IDeviceConnectContract 等)"]
    end

    subgraph sg_Data["数据层 com.jieli.btsmart.data"]
        subgraph sg_Adapter["adapter 包"]
            FA["FunctionAdapter"]
            DLA["DeviceListAdapter"]
            CLA["CommonListAdapter"]
            Other["其他 17+ 适配器"]
        end
        subgraph sg_Model["model 包"]
            SI["SettingsItem"]
            HD["HistoryDevice"]
        end
        subgraph sg_Lsn["listeners 包"]
            L1["OnDeleteItemListener"]
            L2["OnLocationItemListener"]
        end
    end

    subgraph sg_Frame["框架/第三方依赖"]
        BRVAH["BaseQuickAdapter (BRVAH)"]
        Glide["Glide 图片加载"]
        DB["DataBinding + BindingUtil"]
    end

    subgraph sg_SDK["杰理蓝牙 SDK / jl_http"]
        RCSP["RCSPController"]
        DSM["DeviceStatusManager"]
        BU["BluetoothUtil"]
        PM["ProductModel 常量"]
    end

    UI --> Presenter
    Presenter -->|"setAdapter / setNewInstance"| FA
    Presenter -->|"setAdapter / setNewInstance"| DLA
    Presenter -->|"setAdapter"| Other
    FA --> SI
    DLA --> HD
    DLA --> L1
    DLA --> L2
    FA --> BRVAH
    DLA --> BRVAH
    CLA --> BRVAH
    Other --> BRVAH
    DLA --> RCSP
    DLA --> DSM
    DLA --> BU
    DLA --> PM
    DLA --> Glide
    UI --> DB

架构说明:

  • UI 层只面向 Presenter 与适配器,不直接操作蓝牙协议;数据层适配器是 UI 与数据之间的“翻译器”。
  • **BRVAH(com.chad.library.adapter.base)**是所有适配器的共同基座,提供 setNewInstance、getData、notifyDataSetChanged、getItemPosition 等列表操作能力,以及 convert() 钩子用于逐项绑定视图。
  • **设备状态相关适配器(DeviceListAdapter)**直接依赖杰理蓝牙 SDK 的 RCSPController(单例,负责设备连接管理)、DeviceStatusManager(设备使用状态查询)、BluetoothUtil(蓝牙工具)与 jl_http 的 ProductModel 常量,这是“设备功能适配”的核心数据来源。
  • BindingUtil 通过 @BindingAdapter 把 DataBinding 表达式与视图操作解耦,其中 setRecycleViewData 允许在 XML 中直接注入列表数据到已绑定的 BaseQuickAdapter。

数据层包结构总览

data/adapter 包共包含 20+ 个适配器,按业务领域可归类如下(均为 BaseQuickAdapter 子类):

业务领域适配器数据模型/用途
设备列表DeviceListAdapter、DeviceListAdapterModify、HistoryBtDeviceAdapter、DualDevAdapterHistoryDevice;设备历史/双设备/编辑模式
功能设置FunctionAdapter、FunctionListAdapter、FuncSettingsAdapter、AppSettingsAdapter、LedSettingsAdapter、HeadsetKeyAdapterSettingsItem;按键、LED、App 设置
闹钟AlarmAdapter、AlarmRepeatAdapter、AlarmDefaultBellAdapter、BellFileListAdapter闹钟列表、重复规则、默认铃声、铃声文件
文件管理FileListAdapter、FileRouterAdapter设备文件浏览与路由跳转
OTA/固件FirmwareOtaAdapterOTA 固件选择
其他FittingHistoryAdapter、FMFreqCollectAdapter、CommonListAdapter佩戴历史、FM 频率采集、通用设置项

适配器统一采用“构造时传入布局 → convert() 中按模型字段绑定视图”的模式。CommonListAdapter 是通用型设置列表(标题 + 值 + 分割线),FunctionAdapter 是按键功能设置列表,DeviceListAdapter 是最复杂的设备卡片列表。

(继续阅读:下一节深入剖析各核心适配器的实现与控制流。)

核心适配器实现剖析

适配器基座:BaseQuickAdapter 封装

数据层所有适配器都继承 com.chad.library.adapter.base.BaseQuickAdapter<T, BaseViewHolder>(BRVAH 库)。该基类封装了 RecyclerView.Adapter 的样板代码:

  • 数据管理:setNewInstance(List<T>) 全量替换数据、getData() 获取当前列表、addData()/remove() 增删。
  • 视图复用:convert(BaseViewHolder helper, T item) 是唯一必须实现的抽象方法,每次条目绑定视图时回调。
  • 常用辅助:getItemPosition(item) 定位条目位置、getItemCount() 获取数量、notifyDataSetChanged() 刷新。

BRVAH 的 BaseViewHolder 提供了链式绑定方法(setText、setImageResource、getView、setVisible 等),使适配器代码保持简短且类型安全。选择 BRVAH 而非原生 RecyclerView.Adapter 的设计意图是:20+ 个适配器共享同一套数据操作 API,减少重复代码,并让 UI 层可以统一通过 setNewInstance 注入数据。

FunctionAdapter:按键功能设置适配

FunctionAdapter 是“设备功能适配”最直接的体现——把 SettingsItem(按键名称、当前值、图标资源)渲染到 item_key_settings_two 布局:

public class FunctionAdapter extends BaseQuickAdapter<SettingsItem, BaseViewHolder> {

    public FunctionAdapter() {
        super(R.layout.item_key_settings_two);
    }

    @Override
    protected void convert(@NonNull BaseViewHolder helper, SettingsItem item) {
        if (item != null) {
            helper.setImageResource(R.id.iv_key_settings_two_img, item.getResId());
            helper.setText(R.id.tv_key_settings_two_key, item.getName());
            helper.setText(R.id.tv_key_settings_two_value, item.getValue());
            ImageView imageView = helper.getView(R.id.iv_key_settings_two_icon);
            imageView.setVisibility(item.isShowIcon() ? View.VISIBLE : View.GONE);
        }
    }
}

来源:FunctionAdapter.java#L19-L39

设计要点:

  • 构造时直接传入布局 ID,不接收初始数据,数据由外部随后 setNewInstance 注入。
  • convert() 中先判空(item != null)再绑定,避免 RecyclerView 复用时的空指针。
  • 图标显隐由模型自描述:SettingsItem.isShowIcon() 控制 iv_key_settings_two_icon,把“是否显示图标”的决策下沉到数据模型,适配器只做机械绑定——这是适配器与业务解耦的典型手法。
  • 被注释的分割线逻辑(view_key_settings_two_line)表明曾经支持按条目位置隐藏末行分割线,该能力最终由 CommonListAdapter 显式实现。

CommonListAdapter:通用设置列表

CommonListAdapter 是通用型设置列表适配器,核心是内部类 ValueType 定义的值类型枚举(IMAGE = 0、TEXT = 1):

public final class CommonListAdapter extends BaseQuickAdapter<SettingsItem, BaseViewHolder> {

    public final static class ValueType {
        public static final int IMAGE = 0;
        public static final int TEXT = 1;
    }

    public CommonListAdapter(List<SettingsItem> list) {
        super(R.layout.item_common_list, list);
    }

    @Override
    protected void convert(@NonNull BaseViewHolder helper, SettingsItem item) {
        helper.setText(R.id.item_title, item.getName());
        int position = getItemPosition(item);
        if (getItemCount() > 0 && position == getItemCount() - 1) {//不显示最后一条
            helper.getView(R.id.item_divider).setVisibility(View.GONE);
        } else {
            helper.getView(R.id.item_divider).setVisibility(View.VISIBLE);
        }

        switch (item.getType()) {
            case ValueType.IMAGE:
                helper.setVisible(R.id.item_value, false);
            default:
                break;
            case ValueType.TEXT:
                TextView tvText = helper.getView(R.id.item_value);
                tvText.setText(item.getValue());
                helper.setVisible(R.id.item_next_logo, false);
                break;
        }
    }
}

来源:CommonListAdapter.java#L21-L55

设计要点:

  • 通过 SettingsItem.getType() 分支渲染“图片值”或“文本值”两种行样式,同一布局复用两种语义。
  • 最后一条隐藏分割线(position == getItemCount() - 1),是列表视觉规范在适配器层的落实;getItemPosition(item) 由 BRVAH 提供,无需手写线性查找。
  • switch 中 IMAGE 分支只有 setVisible(R.id.item_value, false) 后 break 落在 default 上,语法上利用 fall-through 保证 IMAGE 类型不显示文本值控件。

DeviceListAdapter:设备卡片列表与状态机

DeviceListAdapter 是数据层最复杂的适配器,负责设备连接页的设备卡片渲染、编辑模式与多设备形态适配:

public class DeviceListAdapter extends BaseQuickAdapter<HistoryDevice, BaseViewHolder> {
    private final static String TAG = DeviceListAdapter.class.getSimpleName();
    private final IDeviceConnectContract.IDeviceConnectPresenter mPresenter;
    private final RCSPController mRCSPController = RCSPController.getInstance();

    private volatile boolean isEditMode;
    private OnDeleteItemListener mOnDeleteItemListener;
    private OnLocationItemListener mOnLocationItemListener;

    private final static int DEVICE_STATUS_DISCONNECTED = 0;
    private final static int DEVICE_STATUS_CONNECTED = 1;
    private final static int DEVICE_STATUS_USING = 2;
    private final static int DEVICE_STATUS_CONNECTING = 3;
    private final static int DEVICE_STATUS_MANDATORY_UPGRADE = 4;

    public DeviceListAdapter(@NonNull IDeviceConnectContract.IDeviceConnectPresenter presenter) {
        super(R.layout.item_device_list);
        mPresenter = presenter;
    }

    public void setOnDeleteItemListener(OnDeleteItemListener onDeleteItemListener) {
        mOnDeleteItemListener = onDeleteItemListener;
    }

    public void setOnLocationItemListener(OnLocationItemListener onLocationItemListener) {
        mOnLocationItemListener = onLocationItemListener;
    }

    public boolean isEditMode() {
        return isEditMode;
    }

    @SuppressLint("NotifyDataSetChanged")
    public void setEditMode(boolean editMode) {
        if (editMode && !checkDataArrayHasDisconnectDevice()) return;
        if (isEditMode != editMode) {
            if (editMode) {
                if (getData().size() == 1 && getItem(0) != null &&
                        mPresenter.isConnectedDevice(getItem(0).getDevice().getAddress())) {
                    //因为条件不成立,不能进入编辑模式。
                    return;
                }
            }
            isEditMode = editMode;
            notifyDataSetChanged();
        }
    }

来源:DeviceListAdapter.java#L55-L100

关键设计:

  • 依赖注入:构造时要求传入 IDeviceConnectContract.IDeviceConnectPresenter,适配器通过 Presenter 查询连接状态(如 isConnectedDevice(address)),保持对 UI 与蓝牙层的间接依赖。
  • 单例 RCSP:RCSPController.getInstance() 是杰理 SDK 的全局控制器,适配器用它判断设备连接状态、触发连接等。
  • 编辑模式守卫:setEditMode(true) 有两个前置校验——①列表中没有已断开设备则拒绝进入;②列表中只有一个设备且它已连接则拒绝进入(防止把当前正在使用的设备移入可删除/可排序状态)。校验失败时静默返回,不抛异常。
  • 并发标记:isEditMode 声明为 volatile,因编辑模式可能由列表项长按回调与 UI 线程同时读写,volatile 保证可见性。
  • 状态常量:5 个设备状态(断开/已连接/使用中/连接中/强制升级)映射到卡片 UI 的不同表现;适配器根据 RCSPController、DeviceStatusManager 与 ProductModel 常量(充电仓待机/工作、左右耳连接/待机、产品 Logo)选择产品图片与状态文案,实现不同形态设备的自适应渲染。
stateDiagram-v2
    [*] --> DISCONNECTED: 设备记录加载
    DISCONNECTED --> CONNECTING: 用户点击连接
    CONNECTING --> CONNECTED: 连接成功
    CONNECTING --> DISCONNECTED: 连接失败/超时
    CONNECTED --> USING: 被占用/正在使用
    CONNECTED --> MANDATORY_UPGRADE: 固件强制升级
    USING --> DISCONNECTED: 断开
    MANDATORY_UPGRADE --> CONNECTED: 升级完成

状态机说明:设备卡片的状态迁移由蓝牙 SDK 回调驱动(连接成功/失败、占用变化、升级事件),适配器在 convert() 时读取当前状态渲染对应 UI;编辑模式(isEditMode)是该状态机的“平行维度”,仅在存在可编辑(已断开)设备时才可进入。

BindingUtil:DataBinding 数据注入

BindingUtil 位于 util 包,但承担数据层的绑定职责——通过 @BindingAdapter 扩展 XML 绑定表达式,其中 setRecycleViewData 直接向 BaseQuickAdapter 注入数据:

@BindingAdapter("android:selected")
public static void setViewSelected(View view, boolean selectedState) { ... }

@BindingAdapter("android:src")
public static void setSrc(ImageView view, int resId) { ... }

@BindingAdapter("android:loadSrc")
public static void setSrc(ImageView view, String url) { ... }

@BindingAdapter("android:loadGifSrc")
public static void setLoadGifSrc(ImageView view, int resourceId) { ... }

@BindingAdapter("android:loadFilletSrc")
public static void setFilletSrc(ImageView view, String url) { ... }

@BindingAdapter("android:setRecycleViewData")
public static void setRecycleViewData(View view, List list) {
    RecyclerView recyclerView = (RecyclerView) view;
    BaseQuickAdapter adapter = (BaseQuickAdapter) recyclerView.getAdapter();
    adapter.setNewInstance(list);
}

来源:BindingUtil.java#L22-L67

设计要点:

  • 重写系统 android:src 以支持 Glide 异步加载(URL/资源 ID/圆角/GIF 变体),把图片加载策略从 XML/代码中抽离。
  • android:setRecycleViewData 是 MVVM 风格的列表绑定:XML 中 app:setRecycleViewData="@{viewModel.deviceList}" 即可全量刷新 RecyclerView,前提是 getAdapter() 返回的是 BaseQuickAdapter——这正是数据层统一基座带来的红利。

Core Flow:设备列表渲染与编辑流程

sequenceDiagram
    participant A as 设备连接页 (ui.device)
    participant P as IDeviceConnectPresenter
    participant DA as DeviceListAdapter
    participant R as RCSPController
    participant D as DeviceStatusManager

    A->>DA: new DeviceListAdapter(presenter)
    A->>DA: setOnDeleteItemListener / setOnLocationItemListener
    A->>P: 加载历史设备列表
    P-->>A: List<HistoryDevice>
    A->>DA: setNewInstance(historyDeviceList)
    loop 每个设备条目
        DA->>DA: convert(helper, item)
        DA->>R: getInstance() 查询连接状态
        DA->>D: 查询设备使用状态
        D-->>DA: 状态 (连接中/已连接/使用中/强制升级)
        DA->>DA: 按 ProductModel 选择产品图/状态文案
    end
    A->>DA: setEditMode(true)
    DA->>DA: checkDataArrayHasDisconnectDevice() 校验
    alt 存在已断开设备 且 非单机已连接
        DA->>DA: isEditMode = true
        DA->>DA: notifyDataSetChanged() 显示删除/排序控件
    else 校验不通过
        DA-->>A: 静默返回 (不进入编辑模式)
    end

流程要点:

  1. 页面构造适配器时注入 Presenter(IDeviceConnectContract.IDeviceConnectPresenter),并注册删除/排序回调监听器——事件“上抛”到页面层处理,适配器不持有业务逻辑。
  2. 数据加载走 Presenter(MVP 惯例),页面拿到 List<HistoryDevice> 后通过 setNewInstance 一次性注入。
  3. 每个条目渲染时,convert() 实时向 RCSPController/DeviceStatusManager 查询设备状态,配合 ProductModel 常量选择产品图片,因此设备状态变化后只需再次 notifyDataSetChanged 即可整体刷新。
  4. 编辑模式是“有条件的”UI 状态:只有存在已断开设备、且不是“唯一已连接设备”时才允许进入,避免用户误删正在使用的设备。

Usage Examples

示例一:创建并填充功能设置列表

// 功能设置页创建 FunctionAdapter 并注入设置项
FunctionAdapter adapter = new FunctionAdapter();
adapter.setNewInstance(settingsItems);   // List<SettingsItem>
recyclerView.setAdapter(adapter);

来源:FunctionAdapter.java#L19-L23

示例二:设备列表编辑模式的进入条件

@SuppressLint("NotifyDataSetChanged")
public void setEditMode(boolean editMode) {
    if (editMode && !checkDataArrayHasDisconnectDevice()) return;
    if (isEditMode != editMode) {
        if (editMode) {
            if (getData().size() == 1 && getItem(0) != null &&
                    mPresenter.isConnectedDevice(getItem(0).getDevice().getAddress())) {
                //因为条件不成立,不能进入编辑模式。
                return;
            }
        }
        isEditMode = editMode;
        notifyDataSetChanged();
    }
}

来源:DeviceListAdapter.java#L87-L100

示例三:通用设置项的值类型分支

switch (item.getType()) {
    case ValueType.IMAGE:
        helper.setVisible(R.id.item_value, false);
    default:
        break;
    case ValueType.TEXT:
        TextView tvText = helper.getView(R.id.item_value);
        tvText.setText(item.getValue());
        helper.setVisible(R.id.item_next_logo, false);
        break;
}

来源:CommonListAdapter.java#L43-L54

示例四:DataBinding 注入列表数据

@BindingAdapter("android:setRecycleViewData")
public static void setRecycleViewData(View view, List list) {
    RecyclerView recyclerView = (RecyclerView) view;
    BaseQuickAdapter adapter = (BaseQuickAdapter) recyclerView.getAdapter();
    adapter.setNewInstance(list);
}

来源:BindingUtil.java#L63-L67

以上示例展示了数据层的三种典型用法:纯代码装配(示例一)、带业务守卫的列表状态控制(示例二)、类型化列表项渲染(示例三)以及 XML 声明式数据注入(示例四)。新增设备功能时,通常只需:新建/复用 SettingsItem 模型 → 选择或新建适配器 → 在页面中 setNewInstance 注入。

API Reference

FunctionAdapter extends BaseQuickAdapter<SettingsItem, BaseViewHolder>

成员签名说明
构造器FunctionAdapter()使用 R.layout.item_key_settings_two 布局,无初始数据
重写void convert(BaseViewHolder helper, SettingsItem item)绑定图标、名称、值;按 isShowIcon() 控制图标显隐

CommonListAdapter extends BaseQuickAdapter<SettingsItem, BaseViewHolder>

成员签名说明
常量ValueType.IMAGE = 0 / ValueType.TEXT = 1列表项值类型
构造器CommonListAdapter(List<SettingsItem> list)使用 R.layout.item_common_list 布局并携带初始数据
重写void convert(BaseViewHolder helper, SettingsItem item)绑定标题;末项隐藏分割线;按类型渲染图片/文本值

DeviceListAdapter extends BaseQuickAdapter<HistoryDevice, BaseViewHolder>

成员签名说明
构造器DeviceListAdapter(IDeviceConnectContract.IDeviceConnectPresenter presenter)必须注入 Presenter,用于查询连接状态
设置void setOnDeleteItemListener(OnDeleteItemListener l)注册删除回调(编辑模式下点击删除)
设置void setOnLocationItemListener(OnLocationItemListener l)注册排序/位置调整回调
查询boolean isEditMode()当前是否处于编辑模式
操作void setEditMode(boolean editMode)切换编辑模式;带前置守卫校验(存在已断开设备、非唯一已连接设备),校验失败静默返回
常量DEVICE_STATUS_DISCONNECTED=0 / CONNECTED=1 / USING=2 / CONNECTING=3 / MANDATORY_UPGRADE=4设备卡片状态

各适配器还继承了 BaseQuickAdapter 的公共 API:setNewInstance(List<T>)、getData()、getItem(int)、getItemCount()、getItemPosition(T)、notifyDataSetChanged()、addData(T)、remove(T) 等。

Failure Modes、边界与并发

编辑模式守卫(防误操作)

setEditMode(true) 的两道守卫(无已断开设备、单机且已连接)失败时静默返回而非抛异常,调用方(长按回调)不会崩溃,但 UI 表现为“无法进入编辑模式”。若列表为空,checkDataArrayHasDisconnectDevice() 返回 false,同样拒绝进入。

空数据与空条目

  • FunctionAdapter.convert() 显式判空 item != null,防止 RecyclerView 复用脏数据导致 NPE。
  • CommonListAdapter 中 getItemCount() > 0 && position == getItemCount() - 1 同时保护空列表与末项分割线逻辑。

并发与可见性

  • isEditMode 声明为 volatile:编辑模式的读写可能来自 UI 线程(列表项长按)与主线程状态回调,volatile 保证跨线程可见性但不保证原子性;由于切换逻辑集中在 setEditMode 且无复合读改写,当前设计下是安全的。
  • RCSPController 为进程级单例(getInstance()),多个适配器/页面共享同一实例,其内部线程模型由 SDK 保证;适配器仅在主线程读取状态。

类型不匹配风险

BindingUtil.setRecycleViewData 将 getAdapter() 强制转换为 BaseQuickAdapter。若 RecyclerView 绑定了非 BRVAH 适配器,会在 XML 数据绑定触发时抛出 ClassCastException——这是数据层统一基座的隐含约束:所有 RecyclerView 适配器必须是 BaseQuickAdapter 子类,新增页面时须遵守。

Performance 与运行注意事项

  • 全量刷新为主:setEditMode 使用 notifyDataSetChanged()(并标注 @SuppressLint("NotifyDataSetChanged")),编辑模式切换时整体重绘卡片;列表规模为历史设备(通常个位数),成本可接受。大列表场景应改用 BRVAH 的差量更新 API。
  • 图片加载:DeviceListAdapter 通过 Glide 加载产品图,并设置 DiskCacheStrategy 与 SIZE_ORIGINAL(见其导入与静态常量),减少重复网络/磁盘读取。
  • 数据注入时机:setNewInstance 是 O(n) 全量替换;数据绑定场景下每次列表变化都会触发,应避免高频调用(如每帧刷新)。
  • 日志:适配器持有 TAG = DeviceListAdapter.class.getSimpleName(),异常路径可据此在 logcat 中过滤定位。

Extension Points

  1. 新增功能适配器:继承 BaseQuickAdapter<T, BaseViewHolder>,构造传入新布局,重写 convert() 绑定模型字段。若列表项语义仍是“名称/值/图标”,直接复用 FunctionAdapter 或 CommonListAdapter + SettingsItem 即可,无需新类。
  2. 设备形态适配:DeviceListAdapter 依据 ProductModel 常量(充电仓待机/工作、左右耳连接/待机、产品 Logo)选择产品图与文案;新增设备形态时扩展该映射逻辑(配合 ProductUtil/DefaultResFactory),适配器结构无需变更。
  3. 交互回调:删除与排序通过 OnDeleteItemListener、OnLocationItemListener 上抛,页面实现即可扩展行为(如二次确认弹窗、位置持久化),适配器保持可复用。
  4. 值类型扩展:CommonListAdapter.ValueType 目前仅 IMAGE/TEXT;新增类型(如开关、进度条)时在 convert() 的 switch 中追加分支即可,注意保持 switch 的 fall-through 语义清晰。
  5. DataBinding 自定义绑定:BindingUtil 的 @BindingAdapter 是集中扩展点,可新增 android:xxx 绑定属性(如圆角图、GIF、列表差量更新)而无需改动各页面代码。

Tests

本目录源码中未发现针对 data.adapter 包的单元测试文件(仓库测试主要覆盖蓝牙 SDK 与工具类)。适配器逻辑以手工/仪器化验证为主:编辑模式守卫、状态常量映射、分割线末项隐藏等行为通过设备连接页交互验证。新增适配器时建议至少覆盖:空数据渲染、setEditMode 边界、ValueType 分支。

Related Links

  • SettingsItem 数据模型 —— 功能/设置列表项模型
  • HistoryDevice 数据模型 —— 历史设备模型(包装蓝牙 SDK 的 HistoryBluetoothDevice)
  • IDeviceConnectContract —— 设备连接页 MVP 契约(Presenter 注入适配器)
  • RCSPController —— 杰理蓝牙 SDK 全局控制器(设备连接状态来源)
  • BindingUtil —— DataBinding 自定义绑定适配
  • 相关兄弟页面:蓝牙连接与 RCSP 命令、UI 页面层(ui.*)、设备工具(tool.*)
Prev
应用架构与界面导航
Next
设备配置JSON与资源文件