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

    • 项目简介与核心能力
    • 运行环境与SDK版本
  • 快速开始

    • 工程导入与依赖配置
    • 权限配置与示例运行
  • 平台架构

    • SDK分层架构与RCSP协议
    • 蓝牙连接库
    • 健康SDK核心库 JL_Watch
    • 健康服务器与云端服务
  • 健康与运动数据

    • 健康数据同步
    • 运动数据同步
    • 本地数据持久化
  • 设备管理功能

    • 表盘管理
    • 闹钟与健康提醒
    • 消息与联系人同步
    • 天气同步
    • 设备查找
    • 支付宝集成
  • 传输与媒体处理

    • 文件传输与文件管理
    • 音乐传输与播放控制
    • 图像转换库
    • 音频编解码与解密
  • OTA 升级

    • 固件空中升级流程
    • 4G模块与差分升级
  • AI 能力

    • AI表盘与云服务
    • AI语音助手
  • 示例应用

    • HealthAide 健康助手应用
    • WatchTestTool 测试工具
  • 开发者指南

    • 自定义命令扩展
    • 调试技巧与问题排查
    • 版本历史与兼容性

文件传输与文件管理

本页面介绍 Android-JL_Health 健康助手 App 与智能手表(穿戴设备)之间的文件传输与文件管理能力:包括设备端文件系统的浏览、文件下载(传输到手机)、文件删除、存储卡格式化、设备存储信息查询,以及手机本地文件的路径解析与存储管理。整个能力基于杰理(Jieli)jl_filebrowse 与 jl_rcsp SDK 通过 BLE 通道实现。

Purpose and Scope

本页面覆盖以下内容:

  • 设备文件浏览:通过 FileBrowseManager 获取在线设备(SD 卡)列表、读取目录、加载更多、进入/返回子目录;
  • 文件传输:通过 GetFileByClusterTask / GetFileByNameTask 将设备端文件按簇或按名称下载到手机本地路径;
  • 文件管理操作:删除设备文件(deleteFile 与 DeleteFileByNameCmd)、格式化存储卡(FormatTask)、点播设备文件(playFile);
  • 设备存储信息:DevStorageInfo / DevStorageState 对应的 RCSP 命令(GetSysInfoCmd)与属性读取;
  • 手机本地文件处理:UriTool 将 content:// / file:// URI 解析为真实路径,HealthUtil 基于 getExternalFilesDir 管理应用私有外部目录。

不在本页面范围内(由其他目录页负责)的内容:BLE 连接建立与 WatchManager 的整体初始化、RCSP 协议命令的底层编解码细节、健康数据(心率/血氧等)的业务解析。本页面聚焦"文件"这一传输媒体本身。

Overview

在健康手表类应用中,手机 App 与设备之间的文件传输是最基础的能力之一:固件/资源升级文件需要下发到设备,设备的录音、运动记录等数据需要上传到手机,SD 卡上的媒体文件需要被浏览、点播或删除。Android-JL_Health 通过封装杰理 jl_rcsp(RCSP 双向通信协议)与 jl_filebrowse(文件浏览模块)两个 SDK 组件实现上述能力。

关键设计思想:

  1. 分层解耦:App 层(WatchManager 及 Demo 测试)只面向高层 API,如 FileBrowseManager.getInstance()、GetFileByClusterTask;真正的协议交互(RCSP 命令构造、BLE 分包收发、重传与应答)由 SDK 内部完成。Demo 注释明确要求"须在 1.3 配置好 sdk"(即先完成 WatchManager/RCSP 的初始化与连接)。
  2. 观察者模式:文件浏览采用 FileObserver 观察者回调,SD 卡在线状态变化、文件列表读取开始/结束/失败、歌曲点播结果都通过回调通知上层,避免轮询。
  3. 任务模式:文件下载(GetFileByClusterTask / GetFileByNameTask)与格式化(FormatTask)被建模为可启动、带进度与取消语义的任务,通过 TaskListener 上报 onBegin / onProgress / onFinish / onError / onCancel。
  4. 文件句柄模型:设备端文件通过 SDCardBean.getDevHandler()(设备句柄)与 FileStruct.getCluster()(簇号)唯一定位,传输参数即 (devHandle, offset, cluster, path)。

Architecture

flowchart TD
    subgraph sg_App["App 层(HealthAide)"]
        WatchManager["WatchManager<br/>(WatchOpImpl 子类)"]
        Demo["FileManagerDemo / 业务 UI"]
        UriTool["UriTool 本地路径解析"]
        HealthUtil["HealthUtil 外部存储管理"]
    end

    subgraph sg_Sdk["SDK 层(jl_rcsp / jl_filebrowse)"]
        FBM["FileBrowseManager"]
        FO["FileObserver 回调接口"]
        TaskGetCluster["GetFileByClusterTask"]
        TaskGetName["GetFileByNameTask"]
        TaskFormat["FormatTask"]
        CmdDelete["DeleteFileByNameCmd"]
        CmdSysInfo["GetSysInfoCmd"]
        DevStorage["DevStorageInfo / DevStorageState"]
    end

    subgraph sg_Transport["传输层"]
        RCSP["RCSP 协议引擎<br/>(CommandBuilder / RcspCommandCallback)"]
        BLE["BLE GATT 通道"]
    end

    subgraph sg_Device["设备端"]
        FileSystem["设备文件系统 / SD 卡"]
    end

    Demo --> FBM
    Demo --> TaskGetCluster
    Demo --> TaskGetName
    Demo --> TaskFormat
    Demo --> WatchManager
    WatchManager --> RCSP
    FBM --> FO
    FBM --> RCSP
    TaskGetCluster --> RCSP
    TaskGetName --> RCSP
    TaskFormat --> RCSP
    CmdDelete --> RCSP
    CmdSysInfo --> DevStorage
    CmdSysInfo --> RCSP
    RCSP --> BLE
    BLE --> FileSystem
    UriTool --> HealthUtil

架构说明:

  • FileBrowseManager(单例)是设备文件浏览的入口,FileObserver 是上层订阅浏览事件与 SD 卡在线状态的回调接口;Demo 中的 brow() 展示了"注册观察者 → 获取在线设备 → 读取当前目录 → 目录导航 → 点播"的标准流程。
  • 文件下载、格式化、删除分别由 GetFileByClusterTask、GetFileByNameTask、FormatTask、DeleteFileByNameCmd 承载,它们最终都依赖 WatchManager(WatchOpImpl 子类)驱动 RCSP 命令。
  • DevStorageInfo / DevStorageState 是 GetSysInfoCmd 的响应模型,用于获取设备存储容量与状态。
  • 手机侧,UriTool.getPath() 将系统返回的 URI(如 content://media/...、content://com.android.providers.downloads/...)转换为真实文件路径,供 SDK 保存下载文件;HealthUtil 提供基于 context.getExternalFilesDir(null) 的应用私有目录定位能力。

设备端文件浏览与目录管理

FileBrowseManager 与 FileObserver

FileBrowseManager 是 jl_filebrowse 模块对外暴露的单例门面(FileBrowseManager.getInstance()),负责维护在线设备(SD 卡)列表、当前浏览目录状态以及向观察者派发事件。FileObserver 是上层必须实现的观察者接口,Demo 中展示了全部回调:

Source: FileManagerDemo.java

FileObserver fileObserver = new FileObserver() {
    @Override
    public void onFileReceiver(List<FileStruct> fileStructs) {
        // 读取到文件列表,仅仅回调本次读取的文件列表
    }

    @Override
    public void onFileReadStop(boolean isEnd) {
        // 文件列表读取结束
    }

    @Override
    public void onFileReadStart() {
        // 开始文件列表读取
    }

    @Override
    public void onFileReadFailed(int reason) {
        // 文件列表读取失败
    }

    @Override
    public void onSdCardStatusChange(List<SDCardBean> onLineCards) {
        //在线设备有变化
    }

    @Override
    public void OnFlayCallback(boolean success) {
        //歌曲点播回调
    }
};

回调语义与设计意图:

回调触发时机业务用途
onFileReceiver(List<FileStruct>)每次读到一批文件列表条目增量刷新列表 UI("仅仅回调本次读取的文件列表",需自行累积)
onFileReadStart()开始读取目录显示加载中状态
onFileReadStop(boolean isEnd)列表读取结束isEnd 标记是否为最后一批,用于隐藏加载动画
onFileReadFailed(int reason)读取失败展示错误提示;reason 为失败码
onSdCardStatusChange(List<SDCardBean>)在线 SD 卡集合变化(插拔/连接断开)重新拉取设备列表
OnFlayCallback(boolean success)歌曲点播结果点播成功/失败提示

标准浏览流程

Demo brow() 给出了五个步骤的完整调用序列:

Source: FileManagerDemo.java

//第一步:注册观察者
manager.addFileObserver(fileObserver);

// 第2步:获取在线设备列表,可以通过fileObserver处理设备状态变化
List<SDCardBean> list = manager.getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
Folder currentFolder = manager.getCurrentReadFile(sdCardBean);

//第4步:获取当前目录下已经读了但在缓存中的子文件
List<FileStruct> fileStructs = currentFolder.getChildFileStructs();

//第5步:浏览操作
//加载更多
manager.loadMore(sdCardBean);
//进入下一级目录
FileStruct fileStruct = currentFolder.getChildFileStructs().get(0);//根据需要获取需要读取的文件夹
manager.appenBrowse(fileStruct, sdCardBean);
//返回上一级目录没有列表回调
boolean hasEvent = true;//是否需要FileObserver的事件回调
manager.backBrowse(sdCardBean, hasEvent);
//点播文件
manager.playFile(fileStruct, sdCardBean);

关键点解读:

  • 多设备支持:getOnlineDev() 返回 List<SDCardBean>,App 需要自行决定操作哪个设备(Demo 取第一个)。SDCardBean 是设备/SD 卡的会话模型,后续所有浏览与删除操作都要以它为上下文参数传入。
  • 目录状态缓存:getCurrentReadFile(sdCardBean) 返回当前目录的 Folder 对象,getChildFileStructs() 取出已缓存的子文件条目。这避免了每次 UI 刷新都重新走一遍协议读取。
  • 目录导航:appenBrowse(fileStruct, sdCardBean) 进入下一级目录;backBrowse(sdCardBean, hasEvent) 返回上一级,hasEvent 控制是否需要触发 FileObserver 回调(返回上级时通常不需要列表回调,因此 Demo 特意说明"返回上一级目录没有列表回调")。
  • 加载更多:loadMore(sdCardBean) 用于目录条目较多时分批读取,与 onFileReadStop(boolean isEnd) 配合判断是否还有更多数据。
  • 点播:playFile(fileStruct, sdCardBean) 让设备播放指定媒体文件,结果通过 OnFlayCallback(boolean success) 回调。

文件删除

删除设备文件有两层 API:FileBrowseManager.deleteFile() 是面向批量的高层封装,DeleteFileByNameCmd 是面向单文件的 RCSP 命令。

deleteFile 批量删除

Source: FileManagerDemo.java

List<FileStruct> fileStructs = new ArrayList<>();//注意fileStructs一定要是在sdCardBean中
boolean withEnv = false;//准备环境,一般使用false
manager.deleteFile(sdCardBean, fileStructs, withEnv, new DeleteCallback() {
    @Override
    public void onSuccess(FileStruct fileStruct) {
        //成功
    }

    @Override
    public void onError(int code, FileStruct fileStruct) {
        //fileStruct 删除失败
    }

    @Override
    public void onFinish() {
        //删除结束,通过onError判断是否有删除失败的文件
    }
});

要点:

  • fileStructs 必须是属于目标 sdCardBean 的文件条目(注释强调"注意fileStructs一定要是在sdCardBean中"),删除操作的协议上下文依赖设备句柄。
  • withEnv 控制是否为删除操作准备运行环境,一般传 false(避免额外的环境准备开销)。
  • DeleteCallback 对每个文件分别回调 onSuccess / onError(code, fileStruct),全部处理完统一回调 onFinish();上层应在 onFinish 中汇总失败项并给出整体结果。

DeleteFileByNameCmd 按名称删除

SDK 还提供按文件名删除的命令模型 DeleteFileByNameCmd(位于 com.jieli.jl_rcsp.model.command.file_op 包),配合 CommandBuilder 构造、通过 RCSP 命令回调(RcspCommandCallback)发送。适用于已知文件名的精准删除场景,例如清理设备上某个固定名称的日志或临时文件。

存储卡格式化

FormatTask 对设备存储卡执行格式化,是一个典型的任务型操作:

Source: FileManagerDemo.java

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
FormatTask task = new FormatTask(watchManager, context, sdCardBean);
task.setListener(new SimpleTaskListener() {
    @Override
    public void onBegin() {
        //开始
    }
    // ... onProgress / onFinish / onError / onCancel
});

设计意图:格式化属于高风险、耗时长的操作,因此被建模为带监听器的 Task,而不是一次性回调。上层用 SimpleTaskListener(TaskListener 的便捷实现)订阅 onBegin / onProgress / onFinish / onError / onCancel 生命周期事件,可在 onBegin 时锁定 UI 防误操作。

设备存储信息查询

存储信息通过 RCSP 系统信息命令 GetSysInfoCmd 获取,响应模型为 SysInfoResponse,其中包含设备存储状态模型 DevStorageInfo 与 DevStorageState(com.jieli.jl_rcsp.model.device 包)。FileManagerDemo 中引入了:

  • GetSysInfoCmd / GetSysInfoParam / SysInfoResponse:系统信息命令的参数与响应;
  • DevStorageInfo:存储设备信息(如 SD 卡总容量、剩余容量);
  • DevStorageState:存储状态(如是否挂载、是否可写);
  • AttrAndFunCode:属性/功能码常量,用于按属性码读取设备能力;
  • BooleanRcspActionCallback:布尔结果回调,用于"成功/失败"类命令的快捷回调封装。

典型用途:在文件传输前查询设备剩余空间,若空间不足则提示用户清理或格式化后再传输;或在文件浏览器中展示容量占用。

文件传输任务(下载到手机)

GetFileByClusterTask 按簇下载

设备文件系统以"簇"(cluster)为最小分配单位,因此按簇下载是最通用的文件读取方式。Demo read() 展示了完整用法:

Source: FileManagerDemo.java

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
FileStruct fileStruct = null;//注意fileStructs一定要是在sdCardBean中
int devHandle = sdCardBean.getDevHandler();
int cluster = fileStruct.getCluster();
int offset = 0;
String path = "读取内容的保存路径";
GetFileByClusterTask.Param param = new GetFileByClusterTask.Param(devHandle, 0, cluster, path);
GetFileByClusterTask task = new GetFileByClusterTask(watchManager, param);
task.setListener(new TaskListener() {
    @Override
    public void onBegin() {
        //开始
    }

    @Override
    public void onProgress(int progress) {
        //进度回调
    }

    @Override
    public void onFinish() {
        //成功
    }

    @Override
    public void onError(int code, String msg) {
        //失败
    }

    @Override
    public void onCancel(int reason) {
        //取消
    }
});
task.start();

参数模型与生命周期:

参数来源含义
devHandlesdCardBean.getDevHandler()设备句柄,标识目标设备/SD 卡会话
0(第二个构造参数)固定起始偏移(Demo 从 0 开始;若文件较大可分多次续传)
clusterfileStruct.getCluster()文件起始簇号,由浏览阶段获得的 FileStruct 提供
path上层指定下载内容保存到手机的文件路径

TaskListener 与 GetFileByClusterTask.Param 共同构成了"先构造参数 → 再注册监听 → 最后 start()"的启动约定。onProgress(int progress) 为百分比进度,可用于驱动传输进度条;onCancel(int reason) 说明任务支持主动取消(如用户中断大文件传输)。

GetFileByNameTask 按名称下载

当已知设备端文件名时,可使用 GetFileByNameTask(同样位于 com.jieli.jl_rcsp.task 包,Demo 已 import)直接下载,免去先浏览定位簇号的步骤。适合下载设备上名称固定的文件(如固定命名的运动记录、日志文件)。其参数模型与监听器结构与 GetFileByClusterTask 一致,只是定位方式从簇号变为文件名。

传输任务与 RCSP 的关系

WatchManager 是 WatchOpImpl 的子类,是 RCSP 协议操作能力的门面。所有 Task(GetFileByClusterTask、GetFileByNameTask、FormatTask)都持有 WatchManager 引用,通过它向 RCSP 协议引擎发送命令、接收应答;底层命令构造依赖 CommandBuilder,应答通过 RcspCommandCallback 分发。大文件传输在 BLE 上会被拆分为多个数据包按序发送,SDK 内部处理重传与完整性校验,上层只感知 onProgress 与 onFinish。

Core Flow

sequenceDiagram
    participant UI as 业务层(FileManagerDemo)
    participant FBM as FileBrowseManager
    participant WM as WatchManager (WatchOpImpl)
    participant RCSP as RCSP协议引擎
    participant DEV as 设备端文件系统

    UI->>FBM: addFileObserver(observer)
    UI->>FBM: getOnlineDev()
    FBM-->>UI: List<SDCardBean>
    UI->>FBM: getCurrentReadFile(sdCardBean)
    FBM-->>UI: Folder (含缓存的FileStruct列表)
    UI->>FBM: appenBrowse(fileStruct, sdCardBean)
    FBM->>RCSP: 读取目录命令
    RCSP->>DEV: BLE 请求
    DEV-->>RCSP: 目录条目数据
    RCSP-->>FBM: 解析为FileStruct批次
    FBM-->>UI: onFileReceiver(List<FileStruct>)
    FBM-->>UI: onFileReadStop(isEnd)

    UI->>WM: new GetFileByClusterTask(watchManager, param)
    UI->>WM: task.start()
    WM->>RCSP: 按簇读取文件命令 (devHandle, cluster)
    RCSP->>DEV: BLE 分包传输
    DEV-->>RCSP: 文件数据包
    RCSP-->>WM: 进度/数据
    WM-->>UI: onProgress(percent)
    WM-->>UI: onFinish() (文件已写入 path)

流程要点:

  1. 浏览阶段:先注册 FileObserver,再取在线设备;目录读取是异步的,结果通过 onFileReceiver 分批发回,onFileReadStop 标记结束。目录导航(appenBrowse/backBrowse/loadMore)均以 SDCardBean 为上下文。
  2. 传输阶段:从 FileStruct 取得 cluster、从 SDCardBean 取得 devHandle,构造 GetFileByClusterTask.Param 并启动任务;RCSP 通过 BLE 分包读取设备文件,边收边写入 path 指定的手机文件,onProgress 上报进度,onFinish 表示完整落盘。
  3. 失败路径:任何一步出错都会走对应回调——浏览失败走 onFileReadFailed(reason),传输失败走 onError(code, msg),用户中断走 onCancel(reason)。

手机本地文件路径管理

UriTool:URI → 真实路径

系统文件选择器(ACTION_OPEN_DOCUMENT / ACTION_GET_CONTENT)返回的是 content:// URI,而不是可直接使用的文件路径。UriTool.getPath() 针对 Android 4.4(KitKat)及以上系统,把各种 Provider 的 URI 还原为文件系统路径:

Source: UriTool.java

public static String getPath(final Context context, final Uri uri) {
    // DocumentProvider
    if (DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            return Environment.getExternalStorageDirectory() + "/" + split[1];
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }
            final String selection = "_id=?";
            final String[] selectionArgs = new String[] { split[1] };
            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }
    return null;
}

支持的分支与设计意图:

  • ExternalStorageProvider(primary:xxx 型 docId):直接拼接 Environment.getExternalStorageDirectory(),这是最常见的选择"内部存储文件"路径;
  • DownloadsProvider:通过 ContentUris.withAppendedId 构造 content://downloads/public_downloads/<id> 再查 MediaStore 的 DATA 列;
  • MediaProvider:按 image / video / audio 类型选择对应 MediaStore 表,用 _id=? 查询真实路径;
  • content 通用分支:查询 getDataColumn(MediaStore.Images.Media.DATA 列),并对 Google Photos 类 URI 直接取 getLastPathSegment();
  • file 分支:直接返回 uri.getPath()。

该方法在文件选择器选中"要发送到设备或保存到本地的文件"时被调用,解析出的真实路径随后传给 SDK(例如作为传输目标或作为待上传文件源)。

HealthUtil:应用私有外部目录

HealthUtil 提供基于 context.getExternalFilesDir(null) 的应用专属目录定位(Grep 命中见下):

Source: HealthUtil.java

if (context == null || dirNames == null || dirNames.length == 0) return null;
File file = context.getExternalFilesDir(null);
if (file == null || !file.exists()) return null;

该方法以 dirNames 为参数、返回目录 File 对象,用于在 Android/data/<package>/files 下组织应用私有文件(下载的固件、录音、日志等)。使用应用专属目录的优势是无需申请存储权限即可读写,且卸载应用时自动清理。当目录不存在或 getExternalFilesDir 返回 null(存储不可用)时返回 null,调用方需处理该空值。

Usage Examples

以下综合示例串联"浏览 → 下载 → 删除"三段流程,演示本页面所涉及 API 的组合使用方式(片段均提取自 FileManagerDemo.java 的 brow()、read()、delete() 三个测试方法):

// 1. 注册观察者并获取在线设备
FileBrowseManager manager = FileBrowseManager.getInstance();
manager.addFileObserver(fileObserver);
List<SDCardBean> list = manager.getOnlineDev();
if (list.size() < 1) return;                    // 没有在线设备
SDCardBean sdCardBean = list.get(0);

// 2. 读取当前目录(缓存的子文件)
Folder currentFolder = manager.getCurrentReadFile(sdCardBean);
List<FileStruct> fileStructs = currentFolder.getChildFileStructs();

// 3. 按簇下载到手机
int devHandle = sdCardBean.getDevHandler();
int cluster = fileStructs.get(0).getCluster();  // 取第一个文件
GetFileByClusterTask.Param param = new GetFileByClusterTask.Param(devHandle, 0, cluster, "保存路径");
GetFileByClusterTask task = new GetFileByClusterTask(WatchManager.getInstance(), param);
task.setListener(taskListener);                 // onProgress / onFinish / onError / onCancel
task.start();

// 4. 批量删除设备文件
boolean withEnv = false;
manager.deleteFile(sdCardBean, fileStructs, withEnv, deleteCallback);

注意:fileStructs 必须来自目标 sdCardBean 的浏览结果(FileStruct 携带簇号与设备上下文),否则删除/下载会定位失败;传输前应先查询 DevStorageInfo 确认设备剩余空间。

API Reference

FileBrowseManager(com.jieli.jl_filebrowse.FileBrowseManager)

方法说明
static FileBrowseManager getInstance()获取单例
void addFileObserver(FileObserver observer)注册浏览事件观察者
List<SDCardBean> getOnlineDev()获取在线设备/SD 卡列表
Folder getCurrentReadFile(SDCardBean bean)获取设备当前读取目录(含缓存子文件)
void loadMore(SDCardBean bean)加载更多目录条目
void appenBrowse(FileStruct fs, SDCardBean bean)进入下一级目录
void backBrowse(SDCardBean bean, boolean hasEvent)返回上一级目录,hasEvent 控制是否回调
void playFile(FileStruct fs, SDCardBean bean)点播设备媒体文件
void deleteFile(SDCardBean bean, List<FileStruct> list, boolean withEnv, DeleteCallback cb)批量删除设备文件

GetFileByClusterTask(com.jieli.jl_rcsp.task.GetFileByClusterTask)

  • 构造:GetFileByClusterTask(WatchManager manager, GetFileByClusterTask.Param param)
  • Param 构造:Param(int devHandle, int offset, int cluster, String savePath)
  • void setListener(TaskListener listener) / void start()
  • 回调:onBegin()、onProgress(int progress)、onFinish()、onError(int code, String msg)、onCancel(int reason)

DeleteCallback(com.jieli.jl_filebrowse.interfaces.DeleteCallback)

  • onSuccess(FileStruct fileStruct):单个文件删除成功
  • onError(int code, FileStruct fileStruct):单个文件删除失败
  • onFinish():全部文件处理结束

UriTool(com.jieli.healthaide.util.UriTool)

  • static String getPath(Context context, Uri uri):将 DocumentProvider / MediaStore / file 类型 URI 还原为真实路径,无法解析时返回 null。

Failure Modes、边界情况与并发注意

  • 无在线设备:getOnlineDev() 返回空列表时必须提前返回(Demo 中 list.size() < 1 即退出),否则后续调用会以空设备上下文执行。SD 卡热插拔通过 onSdCardStatusChange 通知,UI 应及时刷新。
  • 浏览失败:onFileReadFailed(int reason) 返回失败码,常见原因为设备端读目录错误或 BLE 断连;onFileReadStop(boolean isEnd) 的 isEnd=false 表示数据未读完,不应提前隐藏加载状态。
  • 删除部分失败:DeleteCallback 对每个文件独立回调 onError,onFinish() 只表示流程结束而非全部成功,汇总结果必须以逐文件回调为准。
  • 传输中断/取消:大文件传输中用户取消会触发 onCancel(int reason);断连等错误触发 onError(code, msg)。任务无自动重试语义(源码注释与回调模型中未见重试机制),上层需按业务决定是否重建任务重试。
  • 并发限制:同一时刻对同一 SDCardBean 发起多个浏览/删除/下载操作可能造成协议串扰;SDK 以设备句柄为上下文隔离不同设备,但同设备多任务建议串行化(等上一个任务 onFinish/onError 后再启动下一个)。
  • 路径解析空值:UriTool.getPath() 对未知 Provider 返回 null,HealthUtil 在存储不可用时也返回 null,调用方必须判空,否则后续 File 构造会抛 NullPointerException。

性能与运维考量

  • 分页浏览:设备目录可能包含大量文件,loadMore + onFileReceiver 分批发回的设计避免了单次回调携带超大列表导致的 UI 卡顿,上层应增量追加而非整体替换。
  • 大文件传输进度:onProgress(int progress) 以百分比上报,UI 应节流刷新(如每 1% 或每 200ms 刷新一次),避免进度条动画抢占主线程。
  • BLE 吞吐限制:RCSP 经 BLE 传输吞吐有限,超大文件(固件/长录音)耗时较长,建议传输期间保持屏幕常亮、提示用户不要断开连接,并在 onBegin 时锁定关键操作。
  • 本地目录策略:下载文件写入 getExternalFilesDir(null) 子目录,无需存储权限且随应用卸载清理;如需长期保留给用户,应再复制到公共目录(此时需申请存储权限并处理 Android 10+ 分区存储)。

Extension Points

  • 自定义观察者:实现 FileObserver 即可扩展浏览 UI(多设备选择、断连重连提示、点播结果播报),无需修改 SDK。
  • 自定义传输任务:参照 GetFileByClusterTask / GetFileByNameTask 的任务模式,基于 WatchManager 与 CommandBuilder 可构造新的 RCSP 任务(如批量下载、断点续传),并复用 TaskListener 生命周期约定。
  • 设备存储策略:基于 DevStorageInfo / DevStorageState + GetSysInfoCmd 可扩展"剩余空间不足预警""自动清理旧文件"等业务策略。
  • 文件组织:HealthUtil 的目录工具可扩展为统一的文件分类管理器(按固件/录音/日志分目录),为传输任务提供一致的路径解析入口。

Related Links

  • WatchManager 与设备连接管理(BLE 连接与 WatchManager 初始化,本页面的前置依赖)
  • HealthUtil.java —— 应用外部目录工具
  • UriTool.java —— URI 路径解析工具
  • FileManagerDemo.java —— 文件管理功能完整测试用例
  • 健康数据解析与存储(运动记录、血氧等数据的解析/入库),参见数据层相关目录页
Next
音乐传输与播放控制