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

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

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

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

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

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

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

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

按键功能设置

按键功能设置(Key Settings)是 PiHome(杰理蓝牙 App)中用于查看和配置耳机物理按键与功能映射的能力。本文档基于 KeyBean 数据模型、HeadsetKeyAdapter 列表适配器及对应布局资源,说明该功能从前端列表渲染到 JL 蓝牙 SDK 属性协议(ADV_TYPE_KEY_SETTINGS)之间的完整实现链路。

Purpose and Scope

本页覆盖「按键功能设置」这一设备功能条目的完整实现:

  • 数据模型 KeyBean:一个按键配置行的字段、构造方式与 Parcelable 序列化;
  • 列表渲染 HeadsetKeyAdapter:基于 Section 列表展示「动作(header)+ 按键/功能(item)」的分组结构;
  • 布局资源:item_key_settings_header.xml、item_key_settings_one.xml、item_key_settings_two.xml、view_keyboard_input.xml;
  • 与 JL 蓝牙 SDK 属性协议(AttrAndFunCode.ADV_TYPE_KEY_SETTINGS)的关联方式。

本目录下的其他设备功能(如音量/音效、EQ、LED 等其他功能设置)属于各自的目录条目,本页不做展开;蓝牙连接、属性读写协议本身也由设备连接相关页面负责,此处仅说明按键设置对它们的依赖方向。

Overview

在杰理蓝牙耳机生态中,耳机上的物理按键(如音量加/减、多功能键)可以通过设备属性协议上报和配置。App 端「按键功能设置」负责把这些按键-动作-功能的映射以可读的方式呈现给用户,并允许用户修改映射后写回设备。

实现上,该功能采用「分组列表」的展示模式:

  • 每个**分组头(header)**表示一种按键动作(单击、双击、长按等),对应 KeyBean 的 action 字段;
  • 分组下的**条目(item)**表示该动作下每个物理按键绑定的功能,对应 key(按键描述)与 function(功能描述)字段;
  • 每个条目携带 resId 图标与 isShowIcon 开关,用于在列表中绘制功能图标。

KeyBean 同时实现 Parcelable 并继承 JSectionEntity,因此既能跨组件(如 Activity/弹窗/Bundle)传递,也能直接作为 BaseSectionQuickAdapter 的分组列表数据源。数据中的 keyId、actionId、funcId 三个 ID 与 SDK 属性协议中的按键/动作/功能枚举一一对应,attrType 默认取 AttrAndFunCode.ADV_TYPE_KEY_SETTINGS,表明该数据属于「按键设置」这一高级属性类型。

Architecture

flowchart TD
    subgraph sg_UI["UI 层(承载按键设置页/弹窗)"]
        KeyPage["按键设置界面<br/>解析 SDK 数据并构造列表"]
    end

    subgraph sg_Adapter["列表渲染层"]
        Adapter["HeadsetKeyAdapter<br/>(BaseSectionQuickAdapter)"]
    end

    subgraph sg_Model["数据模型层"]
        Bean["KeyBean"]
        JSection["JSectionEntity(分组头能力)"]
        Parcel["Parcelable(跨组件传递)"]
    end

    subgraph sg_Layout["布局资源"]
        LHeader["item_key_settings_header.xml"]
        LOne["item_key_settings_one.xml"]
        LTwo["item_key_settings_two.xml"]
        LKB["view_keyboard_input.xml"]
    end

    subgraph sg_SDK["JL 蓝牙 SDK"]
        Attr["AttrAndFunCode.ADV_TYPE_KEY_SETTINGS"]
    end

    KeyPage -->|"setNewData"| Adapter
    Adapter -->|"convertHeader / convert"| Bean
    Adapter --> LHeader
    Adapter --> LOne
    Adapter --> LTwo
    Bean -->|"继承"| JSection
    Bean -->|"实现"| Parcel
    Bean -->|"attrType 默认值"| Attr

架构说明:

  • 按键设置界面是功能入口(Activity/弹窗层),它从设备或本地缓存取得按键属性数据,解析成 KeyBean 列表后交给适配器;该层在本页阅读预算内未展开,其职责由 KeyBean/适配器的使用方式可推断。
  • HeadsetKeyAdapter 是核心渲染组件,继承 BaseSectionQuickAdapter<KeyBean, BaseViewHolder>,根据 KeyBean.isHeader() 自动在「分组头布局」与「条目布局」之间分发。
  • KeyBean 是唯一的数据载体:既描述分组头(action),也描述条目(key + function + 图标),并通过 attrType 与 SDK 属性协议绑定。
  • 布局资源决定两种条目的视觉形态:单行布局(item_key_settings_one.xml)与双行布局(item_key_settings_two.xml),另有头布局与键盘输入弹窗布局(view_keyboard_input.xml)。

数据模型:KeyBean

KeyBean 位于 data/model/settings 包下,是「按键功能设置」唯一的数据模型。它继承了 com.chad.library.adapter.base.entity.JSectionEntity(RecyclerView 分组列表的基类,提供 isHeader() 抽象),并实现了 android.os.Parcelable,可以在 Intent/Bundle 之间传递。

字段一览

字段类型默认值说明
keyIdint0按键 ID,标识耳机上的某个物理按键
actionIdint0动作 ID,标识单击/双击/长按等操作
funcIdint0功能 ID,标识该按键动作绑定的功能(音量+、上一曲等)
keyNameStringnull真实按键名
keyStringnull按键描述(列表条目左侧文本)
actionStringnull动作描述(分组头文本)
functionStringnull功能描述(列表条目右侧文本)
resIdint0功能图标资源 ID
isShowIconbooleanfalse是否显示图标
isHideLinebooleanfalse是否隐藏分隔线
attrTypeintADV_TYPE_KEY_SETTINGS高级属性类型,绑定 SDK 协议
isHeaderbooleanfalse是否为分组头(由 JSectionEntity 机制使用)
LAYOUT_ONE / LAYOUT_TWOint0 / 1布局类型常量,供界面区分单行/双行条目

字段设计意图:keyId/actionId/funcId 三个 ID 是设备侧协议层的标识,而 key/action/function 三个字符串是App 侧展示层的描述,二者通过构造函数成对传入,避免界面代码直接依赖协议枚举。attrType 允许同一模型复用于其他高级属性(默认即为按键设置),这是为未来扩展预留的钩子。

构造函数

public KeyBean() {

}

public KeyBean(int keyId, String keyName, int actionId, String action, int funcId, String function) {
    this(keyId, keyName, actionId, action, funcId, function, AttrAndFunCode.ADV_TYPE_KEY_SETTINGS);
}

public KeyBean(int keyId, String keyName, int actionId, String action, int funcId, String function, int attrType) {
    setKeyId(keyId);
    setKeyName(keyName);
    setActionId(actionId);
    setAction(action);
    setFuncId(funcId);
    setFunction(function);
    setAttrType(attrType);
}

Source: KeyBean.java

6 参数构造器默认把 attrType 设为 AttrAndFunCode.ADV_TYPE_KEY_SETTINGS;7 参数构造器允许显式覆盖,用于复用同一模型处理其他属性类型。所有 setter 均返回 this(流畅接口风格,类上标注了 @SuppressWarnings("UnusedReturnValue")),便于链式组装列表数据。

Parcelable 序列化

protected KeyBean(Parcel in) {
    keyId = in.readInt();
    actionId = in.readInt();
    funcId = in.readInt();
    keyName = in.readString();
    key = in.readString();
    action = in.readString();
    function = in.readString();
    resId = in.readInt();
    isHeader = in.readByte() != 0;
    isShowIcon = in.readByte() != 0;
    isHideLine = in.readByte() != 0;
    attrType = in.readInt();
}

Source: KeyBean.java

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(keyId);
    dest.writeInt(actionId);
    dest.writeInt(funcId);
    dest.writeString(keyName);
    dest.writeString(key);
    dest.writeString(action);
    dest.writeString(function);
    dest.writeInt(resId);
    dest.writeByte((byte) (isHeader ? 1 : 0));
    dest.writeByte((byte) (isShowIcon ? 1 : 0));
    dest.writeByte((byte) (isHideLine ? 1 : 0));
    dest.writeInt(attrType);
}

Source: KeyBean.java

序列化顺序与反序列化顺序严格一致(先 3 个 int ID,再 4 个 String,再 1 个 int 与 3 个 boolean,最后 attrType)。布尔值按字节写入/读取,这是 Android Parcelable 的标准写法;CREATOR 静态工厂提供 createFromParcel 与 newArray。序列化支持的存在说明按键设置列表可能跨组件(页面/弹窗)传递,例如在选择功能后把结果带回列表页。

分组头机制

KeyBean 末尾覆写了 JSectionEntity 的 isHeader():

@Override
public boolean isHeader() {
    return isHeader;
}

Source: KeyBean.java

配合 setHeader(boolean)(fluent 返回 this),数据源构造者可以在一份 List<KeyBean> 中混排「分组头」与「条目」两种节点,由 BaseSectionQuickAdapter 自动识别并分发到不同视图类型——这正是下面适配器的设计基础。

列表渲染:HeadsetKeyAdapter

HeadsetKeyAdapter 位于 data/adapter 包,是按键设置列表的渲染核心。它继承 com.chad.library.adapter.base.BaseSectionQuickAdapter<KeyBean, BaseViewHolder>(BRVAH 框架的 Section 适配器),因此无需手动区分头/条目,框架会依据 KeyBean.isHeader() 自动调用不同的绑定方法。

public class HeadsetKeyAdapter extends BaseSectionQuickAdapter<KeyBean, BaseViewHolder> {

    @Override
    protected void convertHeader(@NotNull BaseViewHolder baseViewHolder, @NotNull KeyBean keyBean) {
        baseViewHolder.setText(R.id.tv_key_settings_action, keyBean.getAction());
    }

    @Override
    protected void convert(@NotNull BaseViewHolder baseViewHolder, KeyBean keyBean) {
        baseViewHolder.setText(R.id.tv_key_settings_two_key, keyBean.getKey());
        baseViewHolder.setText(R.id.tv_key_settings_two_value, keyBean.getFunction());
        baseViewHolder.setImageResource(R.id.iv_key_settings_two_img, keyBean.getResId());
    }
}

Source: HeadsetKeyAdapter.java

绑定逻辑分析

  • convertHeader:只绑定 tv_key_settings_action 一个控件,文本来自 keyBean.getAction()。分组头在视觉上是动作说明(如「单击」「双击」「长按」),不承载功能信息。
  • convert:绑定三处——tv_key_settings_two_key(按键描述)、tv_key_settings_two_value(功能描述)、iv_key_settings_two_img(功能图标,resId)。条目同时展示「按键 → 功能」的映射关系和功能图标,用户一眼即可确认每个按键当前绑定什么功能。

从控件命名(_two_)可见条目使用双行布局;convertHeader 使用 tv_key_settings_action,对应头布局 item_key_settings_header.xml。两个方法的分工体现了 Section 适配器的核心约定:头部渲染动作、条目渲染映射。

布局资源

布局文件用途
item_key_settings_header.xml分组头布局,承载 tv_key_settings_action
item_key_settings_one.xml单行条目布局(LAYOUT_ONE 场景)
item_key_settings_two.xml双行条目布局(LAYOUT_TWO 场景),承载 tv_key_settings_two_key、tv_key_settings_two_value、iv_key_settings_two_img
view_keyboard_input.xml键盘输入视图布局,用于自定义按键/功能的输入场景

布局文件位于 res/layout/ 下,与适配器中的控件 ID 一一对应。LAYOUT_ONE/LAYOUT_TWO 两个常量(KeyBean.LAYOUT_ONE = 0、LAYOUT_TWO = 1)提示界面可在两种条目形态间切换:单行适合紧凑展示,双行适合展示「按键描述 + 功能描述」的完整映射。

与 JL 蓝牙 SDK 的关联

KeyBean 的 attrType 默认值直接引用了 SDK 常量:

private int attrType = AttrAndFunCode.ADV_TYPE_KEY_SETTINGS;

Source: KeyBean.java

该常量来自 com.jieli.bluetooth.constant.AttrAndFunCode(JL 蓝牙 SDK 的「高级属性与功能码」定义)。含义如下:

  • ADV_TYPE_KEY_SETTINGS:标识「按键设置」这一高级属性(ADV 属性)类型,是设备与 App 协商该功能的协议入口;
  • keyId / actionId / funcId:对应协议中按键、动作、功能三要素的枚举值,App 通过这三元组与设备交换按键配置;
  • 设备读写该属性时,App 将 SDK 回调的数据解析为若干组 (keyId, actionId, funcId),再翻译成 KeyBean 展示;用户修改后反向编码写回设备。

需要说明:本次文档编写受阅读预算限制,未直接读取承载该功能的 Activity/弹窗与 SDK 属性读写回调代码;上述协议交互方向由 KeyBean 的字段设计与 AttrAndFunCode 引用推断得出,具体读写时序请以设备连接/SDK 相关页面为准。

核心流程

按键功能设置的数据流从设备属性到界面渲染,再到用户交互后的写回,整体可概括如下:

sequenceDiagram
    participant SDK as JL SDK<br/>(设备属性 ADV_TYPE_KEY_SETTINGS)
    participant UI as 按键设置界面
    participant KB as KeyBean 列表
    participant AD as HeadsetKeyAdapter
    participant LV as 列表视图

    SDK->>UI: 设备上报按键属性<br/>(keyId/actionId/funcId)
    UI->>UI: 解析并翻译为<br/>按键/动作/功能描述
    UI->>KB: 构造 KeyBean(含 header 项)
    KB->>AD: setNewData / addData
    AD->>AD: isHeader() 分发视图类型
    AD->>LV: convertHeader 绑定动作文本
    AD->>LV: convert 绑定按键/功能/图标
    LV-->>AD: 点击条目(选择新功能)
    AD-->>UI: 回调选中结果
    UI-->>SDK: 编码写回设备按键配置

流程分步说明:

  1. 数据来源:耳机连接成功后,设备通过高级属性 ADV_TYPE_KEY_SETTINGS 上报当前按键配置;SDK 将原始数据交给 App。
  2. 解析与翻译:App 把协议三元组 (keyId, actionId, funcId) 翻译为可读文本(按键名、动作、功能),这是 KeyBean 6 参数构造函数存在的意义——协议 ID 与展示文本成对传入。
  3. 构造列表:按动作分组,每个动作生成一个 isHeader=true 的 KeyBean(只填 action),其下为若干条目 KeyBean(填 key + function + resId)。
  4. 渲染:HeadsetKeyAdapter 由 BRVAH 框架自动按 isHeader() 分发到 convertHeader/convert,分别填充头布局与双行条目布局。
  5. 交互与写回:用户点击条目选择新功能后,界面把新 funcId 编码回属性协议写回设备;该步骤依赖 SDK 的写属性接口(本次未直接读取,方向由模型设计推断)。

使用示例

示例一:构造按键设置列表数据

以下代码展示了如何利用 6 参数构造器与 setHeader 组装一份「动作头 + 条目」混合列表(结构依据 KeyBean 的公开 API 编写):

List<KeyBean> list = new ArrayList<>();
// 分组头:单击
list.add(new KeyBean().setHeader(true).setAction("单击"));
// 条目:音量加键 → 音量+
list.add(new KeyBean(0x01, "音量加键", 0x01, "单击", 0x10, "音量+"));
// 条目:音量减键 → 音量-
list.add(new KeyBean(0x02, "音量减键", 0x01, "单击", 0x11, "音量-"));

Source: KeyBean.java(基于公开构造函数与 setHeader 的用法示例;具体枚举值取决于设备协议)

示例二:Parcelable 跨组件传递

KeyBean 实现 Parcelable 后可直接放入 Bundle:

Intent intent = new Intent(context, KeySettingsActivity.class);
intent.putParcelableArrayListExtra("key_list", (ArrayList<KeyBean>) list);

Source: KeyBean.java(CREATOR 工厂保证 createFromParcel 可用)

示例三:适配器绑定(真实源码)

见上文「列表渲染」一节:convertHeader 绑定动作文本、convert 绑定按键/功能/图标,直接复用 BRVAH 的 BaseSectionQuickAdapter 机制,页面只需 setNewData(list) 即可完成分组渲染。

配置选项

KeyBean 的字段即本功能的「配置项」——每个字段控制列表渲染的一个方面:

选项类型默认值说明
keyIdint0物理按键协议 ID
actionIdint0动作协议 ID(单击/双击/长按)
funcIdint0功能协议 ID
keyNameStringnull真实按键名(日志/调试用)
keyStringnull列表条目按键描述文本
actionStringnull分组头动作文本
functionStringnull列表条目功能描述文本
resIdint0功能图标资源
isShowIconbooleanfalse是否显示图标
isHideLinebooleanfalse是否隐藏分割线
attrTypeintADV_TYPE_KEY_SETTINGS高级属性类型,决定数据归属协议
isHeaderbooleanfalse标记该节点为分组头

API Reference

KeyBean(数据模型)

构造器:

  • KeyBean() — 空构造,配合 fluent setter 使用
  • KeyBean(int keyId, String keyName, int actionId, String action, int funcId, String function) — 完整构造,attrType 取默认 ADV_TYPE_KEY_SETTINGS
  • KeyBean(int keyId, String keyName, int actionId, String action, int funcId, String function, int attrType) — 完整构造,可覆盖 attrType

Getter / Setter(fluent,返回 this):

方法参数类型返回说明
getKeyId() / setKeyId(int)intKeyBean按键 ID
getActionId() / setActionId(int)intKeyBean动作 ID
getFuncId() / setFuncId(int)intKeyBean功能 ID
getKeyName() / setKeyName(String)StringKeyBean真实按键名
getKey() / setKey(String)StringKeyBean按键描述
getAction() / setAction(String)StringKeyBean动作描述
getFunction() / setFunction(String)StringKeyBean功能描述
getResId() / setResId(int)intKeyBean图标资源
isShowIcon() / setShowIcon(boolean)booleanKeyBean图标开关
isHideLine() / setHideLine(boolean)booleanKeyBean分割线开关
getAttrType() / setAttrType(int)intKeyBean高级属性类型
setHeader(boolean)booleanKeyBean标记分组头
isHeader()—boolean是否为分组头(覆写 JSectionEntity)
describeContents()—intParcelable 约定,返回 0
writeToParcel(Parcel, int)Parcel, intvoid序列化全部字段

静态常量: CREATOR(Parcelable 工厂)、LAYOUT_ONE = 0、LAYOUT_TWO = 1。

HeadsetKeyAdapter(列表适配器)

  • HeadsetKeyAdapter() — 构造器,传入布局资源初始化 Section 适配器
  • convertHeader(BaseViewHolder, KeyBean) — 绑定分组头 tv_key_settings_action
  • convert(BaseViewHolder, KeyBean) — 绑定条目 tv_key_settings_two_key / tv_key_settings_two_value / iv_key_settings_two_img

Source: KeyBean.java 与 HeadsetKeyAdapter.java

失败模式与边界情况

  • Parcelable 读写不一致:writeToParcel 与 Parcel 构造器的字段顺序必须严格对称(int → String → boolean → int)。新增字段时若只改一侧,跨组件传递会得到错位数据——这是 KeyBean 类上标注 @SuppressWarnings("UnusedReturnValue") 之外最需要警惕的维护点。
  • resId 非法或为 0:convert 中无条件调用 setImageResource(..., resId);若 resId 无效会抛出资源异常或显示空白。isShowIcon 字段的存在暗示界面可据此跳过图标绑定,但适配器当前实现未做分支(以源码为准)。
  • 分组头数据缺失:header 节点只填 action,若构造时漏填,头布局文本为空但仍占位,列表分组结构不完整。构造器强制要求 keyName/action/function 成对传入,可在一定程度上规避该问题。
  • 设备不支持按键属性:若设备未实现 ADV_TYPE_KEY_SETTINGS,SDK 不会回调数据,列表为空。该场景的 UI 兜底(空态提示)未在本次读取的源码中出现。

并发与一致性

KeyBean 是纯内存 POJO,无共享可变状态,天然线程安全;列表数据在主线程构造并一次性提交给适配器,符合 BRVAH 的使用约定。写回设备时若与设备断连,写属性请求会失败,App 需要处理回调错误(具体重试/提示逻辑不在本页源码范围内)。

性能与可扩展性

  • 性能:Section 适配器复用 BaseViewHolder,convert 仅做文本与图片绑定,开销极低;KeyBean 无资源句柄持有,列表项即使上百条也不会造成明显内存压力。
  • 扩展点:
    • attrType 可扩展——同一 KeyBean 结构可复用于其他高级属性类型;
    • LAYOUT_ONE/LAYOUT_TWO 常量与三套条目布局暗示界面可切换紧凑/详细两种展示形态;
    • isShowIcon / isHideLine 为图标显隐、分割线控制预留了开关,便于后续样式定制;
    • 自定义按键输入弹窗布局 view_keyboard_input.xml 表明功能选择支持键盘输入路径,可在此基础上扩展自定义功能码。

测试

本次阅读预算内未发现针对 KeyBean/HeadsetKeyAdapter 的单元测试文件;KeyBean 的 toString() 覆写(输出全部字段)为调试与日志排查提供了便利。建议补充的测试点:Parcelable 序列化往返一致性、header/item 混合列表的分组顺序、attrType 默认值断言。

相关链接

  • KeyBean.java(数据模型)
  • HeadsetKeyAdapter.java(列表适配器)
  • item_key_settings_header.xml(分组头布局)
  • item_key_settings_one.xml(单行条目布局)
  • item_key_settings_two.xml(双行条目布局)
  • view_keyboard_input.xml(键盘输入视图)
  • SDK 属性常量 AttrAndFunCode.ADV_TYPE_KEY_SETTINGS 定义于 JL 蓝牙 SDK(com.jieli.bluetooth.constant),本仓库内为外部依赖,未随仓库源码提供
Prev
ANC与噪声处理
Next
彩屏仓控制