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

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

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

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

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

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

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

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

设备配置JSON与资源文件

本文档介绍 JieLi(杰理)Android 蓝牙示例工程 btsmart 中,随应用打包在 assets 目录下的设备配置 JSON 文件与配套资源文件(充电仓动画、屏幕图片等),以及 App 侧读取、复制这些资源的实现方式。

Purpose and Scope

本页面向 code/PiHome_V1.13.0_SDK_V4.2.0/btsmart 示例应用中的 设备配置资产(device config assets) 子系统,覆盖:

  • assets 目录下按芯片平台(AC693x / AC695x / AC696x / AC697x)与设备形态(耳机、音箱、声卡、颈挂)划分的设备配置 JSON 文件;
  • 按 PID/VID 键控的设备专项配置(如 sound_card/sound_card_pid_0x59_vid_0x02.json)与 supplierconfig.json 供应商配置;
  • 充电仓(Charging Case / 充电盒)UI 资源文件(开机动画、锁屏/解锁动画、屏幕图片);
  • 应用侧读取与复制这些资源的工具方法(AppUtil.getTextFromAssets、AppUtil.copyAssets、ChargingBinUtil.copyAssets)。

以下内容不属于本页范围,请参见对应页面:蓝牙协议解析与 SDK 交互(JL_Bluetooth SDK 能力)、充电仓业务逻辑(ChargingBinUtil 的完整通信协议)、设备固件升级流程。

Overview

杰理(JieLi)蓝牙生态中,同一款 App 需要兼容多种主控芯片(AC693x、AC695x、AC696x、AC697x…)与多种设备形态(耳机、颈挂耳机、音箱、TWS 音箱、声卡、充电仓)。不同芯片/形态的设备在能力集(如 EQ 段数、LED 效果、按键映射、音频参数)上差异很大,因此工程采用 "配置与代码分离" 的策略:把每类设备的参数描述写成 JSON 资产文件,随 APK 打包进 assets,App 在运行时读取对应文件并按需下发/应用。

资源文件方面,充电仓(充电盒)带屏幕的设备需要一组按分辨率组织的 UI 素材(开机动画、锁屏动画、解锁动画、静态图片),例如 charging_case/320x172/ 下的 GIF/PNG。这些资源同样打包在 assets 中,由 ChargingBinUtil 在需要时复制到应用私有目录供显示。

核心设计意图(WHY):

  1. 按芯片族分文件:避免单个大 JSON 相互干扰,新增芯片只需新增一个资产文件,App 代码无需改动;
  2. 按 PID/VID 键控:同一芯片族下不同厂商/产品的差异化配置通过 pid_xxx_vid_xxx.json 定位,实现"一个 App 适配多家公版产品";
  3. txt 扩展名规避打包/校验问题:部分配置以 .txt 结尾(如 ac696x_soundbox_json.txt),内容仍是 JSON 文本,目的是避免某些构建工具对 assets/*.json 的压缩/混淆处理;
  4. assets 只读 → 私有目录复制:Android assets 只能流式读取、不可写,需要落盘修改(如充电仓动画按需加载)时必须先复制到 getFilesDir() 等可写目录。

Architecture

flowchart TD
    subgraph sg_Assets["btsmart/src/main/assets(APK 内只读资产)"]
        subgraph sg_Json["设备配置 JSON"]
            CFG1["ac693x_headset_json.txt<br/>ac693x_headset_neck_json.txt"]
            CFG2["ac695x_sound_card.json<br/>sound_card/config.json"]
            CFG3["ac696x_soundbox_json.txt<br/>ac696x_soundbox_tws_json.txt"]
            CFG4["ac697x_headset_json.txt"]
            CFG5["supplierconfig.json"]
            CFG6["sound_card/sound_card_pid_0x59_vid_0x02.json"]
        end
        subgraph sg_Res["充电仓 UI 资源"]
            RES1["charging_case/320x172/boot/ANI1.gif"]
            RES2["charging_case/320x172/screen/anim/lock/ANI2..7.gif"]
            RES3["charging_case/320x172/screen/anim/unlock/ANI2..7.gif"]
            RES4["charging_case/320x172/screen/image/VIE0.png"]
        end
    end

    subgraph sg_Code["App 侧加载工具(com.jieli.btsmart.util)"]
        UTIL1["AppUtil.getTextFromAssets()<br/>读配置 JSON 文本"]
        UTIL2["AppUtil.copyAssets()<br/>复制单个/整目录资源"]
        UTIL3["ChargingBinUtil.copyAssets()<br/>递归复制充电仓资源"]
    end

    subgraph sg_Runtime["运行时使用方"]
        R1["设备能力解析 / 参数下发"]
        R2["充电仓屏幕显示(锁屏/解锁/开机)"]
    end

    CFG1 --> UTIL1
    CFG2 --> UTIL1
    CFG3 --> UTIL1
    CFG4 --> UTIL1
    CFG5 --> UTIL1
    CFG6 --> UTIL1
    UTIL1 --> R1
    RES1 --> UTIL2
    RES2 --> UTIL3
    RES3 --> UTIL3
    RES4 --> UTIL3
    UTIL2 --> R2
    UTIL3 --> R2

架构说明:

  • assets 目录是唯一的资产来源,src/main/assets 下所有文件原样进入 APK;
  • 配置 JSON 层按芯片族(ac693x/ac695x/ac696x/ac697x)与设备形态(headset 耳机、soundbox 音箱、sound_card 声卡、neck 颈挂、tws 双发)划分文件;supplierconfig.json 与 pid/vid 文件提供供应商级/产品级覆盖;
  • 资源层以 charging_case/{分辨率}/{用途}/{动画|图片} 三级目录组织,320x172 为屏幕分辨率;
  • 加载工具层提供两条路径:文本型配置走 getTextFromAssets(流式读取为字符串,交给 JSON 解析);二进制资源走 copyAssets(流式写入到私有目录,递归处理子目录);
  • 运行时使用方:配置 JSON 被设备能力解析逻辑消费,充电仓资源被屏幕显示逻辑消费。

(注:以上 JSON 文件的字段级 schema 位于各资产文件中;字段的完整枚举需直接阅读对应文件,本页聚焦文件组织与加载机制。)

设备配置 JSON 资产清单

assets 目录中的配置 JSON 资产位于 btsmart/src/main/assets 下,按芯片族与设备形态命名,文件清单如下:

文件(相对 src/main/assets/)芯片族设备形态说明
ac693x_headset_json.txtAC693x头戴耳机耳机设备参数(文本形式存储的 JSON)
ac693x_headset_neck_json.txtAC693x颈挂耳机颈挂形态差异化参数
ac695x_sound_card.jsonAC695x声卡声卡设备参数
ac696x_soundbox_json.txtAC696x音箱单音箱参数
ac696x_soundbox_tws_json.txtAC696xTWS 音箱双发(TWS)音箱参数
ac697x_headset_json.txtAC697x头戴耳机较新芯片族耳机参数
supplierconfig.json全平台供应商级供应商默认配置(全局兜底)
sound_card/config.jsonAC695x声卡声卡模块基础配置
sound_card/sound_card_pid_0x59_vid_0x02.jsonAC695x声卡按 PID=0x59 / VID=0x02 键控的产品级专项配置

命名约定解读(WHY):

  1. 芯片族前缀(ac693x、ac695x…):杰理 SDK 按芯片系列提供不同协议与能力集,文件名直接映射芯片族,App 依据当前连接的设备芯片型号选择文件;
  2. 形态后缀(headset/neck/soundbox/sound_card/tws):同一芯片族内部,不同形态的 HID/音频/灯效参数差异大,按形态拆文件便于维护与增量下发;
  3. .txt 扩展名:ac693x_headset_json.txt 等文件内容为 JSON 文本,但使用 .txt 后缀。这是规避构建工具对 assets/**/*.json 的压缩/资源混淆处理、以及避免某些扫描工具误判的常见做法——内容仍是标准 JSON,读取方不依赖扩展名;
  4. PID/VID 键控:sound_card_pid_0x59_vid_0x02.json 表明声卡模块支持"公版固件 + 产品参数表"模式:App 通过蓝牙获取设备的 PID(产品 ID)与 VID(厂商 ID),再定位专项配置;未命中时回退到 sound_card/config.json 或 supplierconfig.json。这是实现"一个 APK 适配多家公版"的关键机制。

注意:以上文件的具体字段(如 EQ 段数、LED 颜色表、按键功能映射等)属于设备参数协议的一部分,字段级完整枚举请直接查看对应资产文件与 SDK 协议文档。

充电仓资源文件组织

充电仓(带屏幕的充电盒)UI 资源集中在 assets/charging_case/320x172 下,目录结构如下:

charging_case/
└── 320x172/                          # 屏幕分辨率 320x172
    ├── boot/                         # 开机动画
    │   └── ANI1.gif
    └── screen/
        ├── anim/                     # 屏幕动画
        │   ├── lock/                 # 锁屏动画 ANI2..ANI7.gif
        │   └── unlock/               # 解锁动画 ANI2..ANI7.gif
        └── image/                    # 静态图片
            └── VIE0.png

组织原则(WHY):

  • 分辨率目录在最外层:充电仓屏幕可能有多种分辨率(如 320x172、更高分辨率等),按分辨率分目录可在同一 APK 内携带多套素材,运行时按设备实际屏幕选择,避免拉伸失真;
  • boot / anim/lock / anim/unlock / image 语义化子目录:对应充电仓显示状态机——开盖/充电时的开机动画、上锁/解锁切换时的过渡动画、以及静态画面(如 LOGO、电量图)。ChargingBinUtil 按语义读取,代码不关心具体帧文件名;
  • GIF 序列帧:ANI*.gif 为逐帧动画素材,App 侧通过 ImageView/自定义绘制按帧播放;VIE0.png 为静态位图,可直接解码显示。

加载机制实现解析

文本型配置:AppUtil.getTextFromAssets

配置 JSON 以文本形式读取,入口是 AppUtil.getTextFromAssets:

public static String getTextFromAssets(Context context, String fileName) {
    if (context == null || fileName == null) return null;
    try {
        inputStream = context.getAssets().open(fileName);
        // ...读取缓冲...
        int len = inputStream.read(buffer);
        // Convert the buffer into a string.
        // JL_Log.i("zzc", "getTextFromAssets : " + len);
    }
    // ...finally 关闭流...
}

Source: AppUtil.java

要点:

  • 入参是 assets 内的相对路径(如 ac695x_sound_card.json);
  • 通过 context.getAssets().open(fileName) 打开流,读取后转换为字符串;
  • 空指针/IO 异常被捕获,返回 null,由调用方决定回退策略;
  • 读取到的字符串交给上层 JSON 解析(如 org.json.JSONObject 或 Gson 映射成能力模型),解析失败即视为配置缺失,走默认参数。

二进制资源复制:AppUtil.copyAssets

普通资源复制入口为 AppUtil.copyAssets:

/**
 * 复制assets资源
 * @param context 上下文
 * @param oldPath assets路径
 * @param newPath 复制资源路径
 */
public static void copyAssets(@NonNull Context context, String oldPath, String newPath) {
    try {
        // 流式读取并写入目标文件
    } catch (Exception e) {
        // 异常处理
    }
}

Source: AppUtil.java

递归复制:ChargingBinUtil.copyAssets

充电仓资源是多级目录,需要递归复制。ChargingBinUtil.copyAssets 的核心逻辑:

public static void copyAssets(@NonNull Context context, String oldPath, String newPath) {
    try {
        String[] fileNames = context.getAssets().list(oldPath);// 获取assets目录下的所有文件及目录名
        if (fileNames == null || fileNames.length == 0) {// 如果是文件
            InputStream is = context.getAssets().open(oldPath);
            File file = new File(newPath);
            // ...输出流写文件...
        }
        // ...目录则创建目标目录...
        for (String fileName : fileNames) {
            copyAssets(context, oldPath + File.separator + fileName, newPath + File.separator + fileName);
        }
    } catch (Exception e) {
        // 异常处理
    }
}

Source: ChargingBinUtil.java

递归算法解读:

  1. context.getAssets().list(oldPath) 列出目标路径下的所有条目;
  2. 若列表为空,说明当前条目是文件:assets.open(oldPath) 打开输入流,写入 new File(newPath) 对应的输出流;
  3. 若列表非空,说明是目录:先创建目标目录,再对每个子项递归调用自身;
  4. 终止条件即"目录 → 文件"的边界,最终把 charging_case/320x172 整棵树复制到应用私有目录。

为什么必须复制而不是直接播放 assets 文件? Android 的 assets 没有文件路径可被 File/Glide/SurfaceView 直接引用(只能经 AssetManager 流式打开),且充电仓动画可能需要按设备状态动态替换;先复制到 getFilesDir() 等可写目录,即可用常规文件 API、缓存到磁盘,也便于后续版本更新时整体替换。

Core Flow:配置与资源的加载流程

设备配置 JSON 读取流程

sequenceDiagram
    participant App as btsmart App
    participant AM as AssetManager (assets)
    participant Parser as JSON 解析/能力模型
    participant Device as 蓝牙设备

    App->>App: 连接设备,获取芯片族 + PID/VID
    App->>AM: getTextFromAssets("ac695x_sound_card.json")
    AM-->>App: JSON 文本 / null
    alt 芯片级配置存在
        App->>Parser: 解析芯片级参数
        Parser-->>App: 能力模型
    else 读取失败/解析失败
        App->>AM: 回退 supplierconfig.json / 默认参数
    end
    App->>Device: 按能力模型下发/同步参数

流程说明:

  1. App 建立蓝牙连接后,先从设备信息中确定芯片族(如 AC695x)与产品标识(PID/VID);
  2. 调用 AppUtil.getTextFromAssets() 读取对应芯片族/形态的配置文件;
  3. 若芯片级文件缺失或解析失败,回退到 supplierconfig.json(供应商全局配置),再失败则使用代码内默认参数;
  4. 声卡模块还会按 PID/VID 尝试加载专项文件(sound_card_pid_0x59_vid_0x02.json),命中则覆盖基础配置——即"芯片级 → 供应商级 → 产品级"的配置覆盖链;
  5. 最终将解析出的能力模型用于 UI 显示(EQ、灯效、按键设置等)与设备参数同步。

充电仓资源加载流程

sequenceDiagram
    participant App as btsmart App
    participant CB as ChargingBinUtil
    participant FS as 应用私有目录 (filesDir)
    participant UI as 充电仓屏幕 UI

    App->>CB: 检测到充电仓(带屏)设备
    CB->>FS: copyAssets("charging_case/320x172", filesDir/charging_case)
    FS-->>CB: 资源落盘完成
    CB->>UI: 按状态加载 boot/ANI1.gif
    UI->>UI: 开盖/充电 → 播放开机动画
    CB->>UI: 状态切换 → 加载 anim/lock 或 anim/unlock 序列
    UI->>UI: 静态画面 → image/VIE0.png

流程说明:

  1. 连接带屏充电仓后,ChargingBinUtil.copyAssets() 将 assets/charging_case/{分辨率} 整树复制到应用私有目录;
  2. 屏幕状态机(开机、锁屏、解锁、静态)各自从对应语义目录取素材;
  3. 复制是幂等且可重复的(覆盖式写入),保证升级后新资源能替换旧资源。

Usage Examples

读取设备配置 JSON 文本

以下代码展示了从 assets 读取声卡配置文本的用法(摘自 AppUtil 的资产读取实现,调用方模式):

// 读取芯片级声卡配置
String config = AppUtil.getTextFromAssets(context, "ac695x_sound_card.json");
if (config != null) {
    // 交给 JSON 解析器构建能力模型
    JSONObject obj = new JSONObject(config);
}

Source: AppUtil.java

递归复制充电仓资源

// 将充电仓 320x172 全套素材复制到私有目录(ChargingBinUtil 内部实现)
ChargingBinUtil.copyAssets(context,
        "charging_case/320x172",
        new File(context.getFilesDir(), "charging_case/320x172").getAbsolutePath());

Source: ChargingBinUtil.java

注:上例中的调用参数(源目录、目标目录)与 ChargingBinUtil 公开方法签名一致,实际业务调用点位于充电仓连接/展示逻辑中,字段级细节可查阅 ChargingBinUtil 完整源码。

Configuration Options

设备配置资产没有集中的"配置项"概念,其配置能力体现在文件选择策略上:

选择维度依据文件示例说明
芯片族设备芯片型号ac695x_sound_card.json按芯片族选择基础配置
设备形态设备类别ac696x_soundbox_tws_json.txt耳机/音箱/声卡/颈挂各自独立
供应商级供应商标识supplierconfig.json全局兜底配置
产品级PID/VIDsound_card_pid_0x59_vid_0x02.json最高优先级覆盖
屏幕分辨率充电仓硬件charging_case/320x172/资源目录即配置

覆盖优先级从低到高:芯片族/形态基础配置 → supplierconfig.json → PID/VID 专项配置。命中规则与回退逻辑由 App 的设备能力解析模块实现(本页聚焦资产组织,命中代码见 SDK 相关能力页)。

API Reference

AppUtil.getTextFromAssets(Context context, String fileName): String

从 assets 读取文本文件内容。

  • 参数:
    • context (Context):应用上下文,用于获取 AssetManager;
    • fileName (String):assets 下的相对路径(如 ac695x_sound_card.json)。
  • 返回:文件文本内容;context/fileName 为空或读取失败时返回 null。
  • 设计意图:统一文本型资产的读取入口,把 IO 与异常处理收敛到工具类,调用方只需关心"拿到 JSON 字符串"。

Source: AppUtil.java

AppUtil.copyAssets(@NonNull Context context, String oldPath, String newPath): void

将 assets 下的单个文件或目录复制到目标路径(@NonNull 约束 context)。

  • 参数:
    • context (Context):应用上下文;
    • oldPath (String):assets 源路径;
    • newPath (String):目标路径(可为应用私有目录)。
  • 返回:无。
  • 异常:内部捕获 Exception,失败不向上抛出(静默失败,由调用方校验目标文件是否存在)。

Source: AppUtil.java

ChargingBinUtil.copyAssets(@NonNull Context context, String oldPath, String newPath): void

递归复制 assets 目录树(充电仓素材专用),自动处理"文件 vs 目录"分支。

  • 参数:同上。
  • 返回:无。
  • 算法:AssetManager.list() 判空区分文件/目录,目录递归、文件流式写入;每次调用幂等覆盖。
  • 异常:内部捕获 Exception,失败后调用方应检查目标目录完整性。

Source: ChargingBinUtil.java

Failure Modes、边界情况与并发

配置资产缺失或解析失败

  • 表现:getTextFromAssets 返回 null,或 JSON 解析抛异常。
  • 处置:按覆盖链回退(芯片级 → supplierconfig.json → 代码内默认参数)。若全部缺失,设备按默认参数工作,App 不应崩溃。
  • 边界:文件名大小写敏感(AssetManager 按精确路径匹配),文件名与设备芯片族不匹配时会静默回退,排查时需核对设备上报的芯片族与资产文件名。

资源复制失败与目录不完整

  • 表现:copyAssets 内部捕获异常,不向上抛出;目标目录可能只有部分文件。
  • 处置:调用方应在播放动画前校验关键文件(如 boot/ANI1.gif)是否存在;File.exists() 校验失败时重试复制或降级为无动画显示。
  • 边界:AssetManager.list() 返回空数组与返回 null 均视为"文件"分支——空目录会被当作文件打开而失败,因此资产目录不应存在空目录。

版本升级后的资源覆盖

  • 充电仓素材复制是覆盖式写入,新版本 APK 中资源变化后,首次启动重新复制即可生效;但若资源文件被设备端缓存(如已发送到充电仓屏幕),需要触发重新下发。

并发与线程

  • getTextFromAssets/copyAssets 均为同步阻塞 IO,应在工作线程(非主线程)调用,避免 ANR;
  • 复制过程没有加锁,同一目标路径并发复制可能导致写竞争;实际使用中由充电仓连接流程串行触发,若需并发防护应在调用方加互斥。

Performance 与运维注意

  • 包体:充电仓 GIF/PNG 素材占用 APK 体积,按分辨率分目录会让包体随素材套数线性增长;裁剪素材应保持 320x172 比例,避免运行时缩放开销;
  • 启动开销:首次连接充电仓时的全量复制在低端设备上耗时明显,建议异步执行并加进度提示;后续可对比版本号/文件清单做增量复制(当前实现为全量覆盖式);
  • 内存:GIF 逐帧播放时注意帧解码内存,大图应使用 BitmapFactory.Options.inSampleSize 按需降采样。

Extension Points

  1. 新增芯片族:在 assets 下新增 ac6xxx_xxx.json(或 .txt),命名遵循"芯片族_形态"约定,并在设备能力解析处注册芯片族到文件名的映射即可,App 主体代码零改动;
  2. 新增公版产品:添加 sound_card_pid_{pid}_vid_{vid}.json 专项文件,按 PID/VID 命中后覆盖基础配置;
  3. 新增充电仓分辨率:新增 charging_case/{新分辨率}/ 目录,复制路径按分辨率参数化即可,无需改动复制逻辑;
  4. 素材换肤/动态下发:copyAssets 的目标目录可替换为服务器下载路径,只要保持 boot/anim/image 目录语义,显示层无需修改。

Tests

本仓库未发现针对资产加载工具的独立单元测试文件;相关行为通过示例应用运行期验证(连接真实设备后观察配置生效与充电仓动画显示)。建议补充的测试覆盖:

  • getTextFromAssets 对不存在文件返回 null、对正常文件返回完整文本;
  • copyAssets 递归复制后目录树与源一致、文件内容逐字节一致;
  • 覆盖链回退:缺失芯片级文件时正确落到 supplierconfig.json。

Related Links

  • AppUtil.java(资产读取/复制工具)
  • ChargingBinUtil.java(充电仓资源复制)
  • assets 目录(全部配置 JSON 与资源)
  • ac695x_sound_card.json(声卡配置示例)
  • sound_card/config.json(声卡基础配置)
  • supplierconfig.json(供应商全局配置)

说明:本页基于目录结构、文件命名与工具方法源码整理;各配置 JSON 的字段级协议内容(EQ、灯效、按键映射等参数项)位于对应资产文件中,属设备参数协议范畴,如需逐字段解读请结合 SDK 协议文档阅读。

Prev
设备功能适配与数据层