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

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

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

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

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

固件文件管理与监听

本文档介绍 JL OTA Android SDK 中固件文件的管理方式与文件监听机制,涵盖固件文件路径/数据的配置入口、基于 android.os.FileObserver 的监听器实现、回调分发辅助类以及相关生命周期管理。

Purpose and Scope

本页面聚焦 SDK 中"固件文件"这一概念的完整生命周期:固件文件如何被指定(路径或字节数据)→ 如何被读取/校验 → 如何被监听(目录/文件变化)→ 回调如何分发到业务层。

范围包括:

  • com.jieli.otasdk.tool.file 包下的文件监听三件套:FileObserverCallback、OtaFileObserver、OtaFileObserverHelper
  • BluetoothOption 中固件文件配置入口 setFirmwareFilePath / setFirmwareFileData 及其用法
  • 文件监听的注册、启动、停止、销毁等生命周期

不在本页面范围(由其他目录页覆盖)的内容:

  • 蓝牙连接与 OTA 传输细节 → 参见蓝牙管理相关页面
  • OTA 升级状态机与进度回调 → 参见 OTA 流程相关页面
  • 设备信息解析(版本号、UID 等)→ 参见设备信息相关页面

Overview

在 OTA(Over-The-Air)升级流程中,固件文件是升级动作的输入。SDK 允许调用方通过两种方式提供固件:

  1. 文件路径:setFirmwareFilePath(String path) —— 传入本地存储中的 OTA 文件路径,SDK 按需读取文件内容;
  2. 文件字节数据:setFirmwareFileData(byte[] data) —— 直接传入固件数据,适用于文件已加载到内存的场景。

无论采用哪种方式,固件文件通常存放在应用私有目录(如 MainApplication.getOTAFileDir() 所示)或外部存储中。当文件由外部进程(例如通过 USB/下载任务写入、或由设备管理工具同步)写入时,业务层需要感知文件何时就绪、何时被修改或删除,以便触发后续的解析与升级。这正是 OtaFileObserver 系列类存在的意义:通过 Android 系统级 FileObserver 能力,以事件驱动的方式监听固件文件目录的变化,并把事件回调到主线程供 UI 层消费。

该能力是 @Deprecated 的旧式辅助工具,但依然完整地展示了 SDK 中"文件监听 → 回调分发"的设计模式,新代码可基于 OtaFileObserver + FileObserverCallback 自行实现等价逻辑。

Architecture

flowchart TD
    subgraph sg_External["外部写入方"]
        FS["外部进程 / 下载任务"]
        App["业务 App / UI 层"]
    end

    subgraph sg_SDK["JL OTA SDK (tool.file)"]
        Helper["OtaFileObserverHelper<br/>(单例 / @Deprecated)"]
        Observer["OtaFileObserver<br/>(extends FileObserver)"]
        Callback["FileObserverCallback<br/>(interface)"]
        Handler["Handler(MainLooper)"]
    end

    subgraph sg_System["Android 系统"]
        FsObserver["android.os.FileObserver"]
        Dir["固件文件目录<br/>(watchPath)"]
    end

    FS -->|"写入/修改/删除"| Dir
    FsObserver -->|"inotify 事件"| Observer
    Observer -->|"onEvent(event, path)"| Helper
    Helper -->|"mHandler.post 切主线程"| Handler
    Handler -->|"遍历分发"| Callback
    Callback -->|"onChange(event, path)"| App
    App -->|"registerFileObserverCallback"| Helper
    App -->|"setFirmwareFilePath / setFirmwareFileData"| Dir

架构说明:

  • OtaFileObserver 是唯一直接与 Android 系统交互的类,它继承自 android.os.FileObserver,系统通过 inotify 机制在目录内容变化时回调 onEvent(int event, String path)。
  • OtaFileObserverHelper 采用双重校验锁(DCL)单例,内部持有唯一的 OtaFileObserver 实例和一份回调列表 ArrayList<FileObserverCallback>,负责把底层监听事件扇出(fan-out)给所有注册的业务回调。
  • 回调分发通过 Handler(Looper.getMainLooper()) 切回主线程执行,避免业务回调在系统文件监听线程中运行导致的线程安全问题。
  • 业务层(App/UI)只需面向 FileObserverCallback 接口编程,无需感知系统 FileObserver 细节,体现了观察者模式 + 依赖倒置的设计意图。

核心实现分析

1. 回调契约:FileObserverCallback

FileObserverCallback 是文件监听事件的唯一回调契约,定义于 FileObserverCallback.java:

public interface FileObserverCallback {

    void onChange(int event, String path);
}

来源:FileObserverCallback.java

设计意图:event 是 Android FileObserver 定义的事件掩码(如 FileObserver.CREATE、FileObserver.MODIFY、FileObserver.DELETE 等),path 是发生变化的文件相对路径。接口保持极简,使调用方可以自行组合事件掩码与路径判断,决定对哪种变化做出响应(例如只关心 CREATE 事件来触发固件解析)。

2. 底层监听器:OtaFileObserver

OtaFileObserver 直接继承 android.os.FileObserver,是系统能力的最小封装,见 OtaFileObserver.java:

public class OtaFileObserver extends FileObserver {

    private FileObserverCallback mFileObserverCallback;

    public OtaFileObserver(String path) {
        super(path);
    }

    public void setFileObserverCallback(FileObserverCallback fileObserverCallback) {
        mFileObserverCallback = fileObserverCallback;
    }

    @Override
    public void onEvent(int event, @Nullable String path) {
        if(mFileObserverCallback != null && null != path){
            mFileObserverCallback.onChange(event, path);
        }
    }
}

来源:OtaFileObserver.java

实现要点:

  • 构造时绑定目录:super(path) 把监听的绝对路径交给系统 FileObserver。注意 Android 的 FileObserver 监听的是目录,事件回调中的 path 是相对该目录的文件名。
  • 空值防护:onEvent 中同时检查回调非空与 path 非空,避免空指针;path == null 时事件被丢弃(系统在部分事件上可能不提供路径)。
  • 单一回调槽位:该类只持有一个 FileObserverCallback 引用,多回调的扇出逻辑由上层 OtaFileObserverHelper 完成。

3. 单例分发器:OtaFileObserverHelper

OtaFileObserverHelper 是文件监听的门面(Facade),被标记为 @Deprecated,完整实现见 OtaFileObserverHelper.java。

3.1 单例与状态

@Deprecated
public class OtaFileObserverHelper {

    private volatile static OtaFileObserverHelper instance;
    private OtaFileObserver mOtaFileObserver;
    private boolean isWatching;
    private String watchPath;

    private final ArrayList<FileObserverCallback> mFileObserverCallbacks = new ArrayList<>();
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    ...
    public static OtaFileObserverHelper getInstance() {
        if (null == instance) {
            synchronized (OtaFileObserverHelper.class) {
                if (null == instance) {
                    instance = new OtaFileObserverHelper();
                }
            }
        }
        return instance;
    }
}

来源:OtaFileObserverHelper.java

设计要点:

  • DCL 双重校验锁单例:volatile 修饰 instance 防止指令重排,保证多线程下只初始化一次。单例意味着全进程共享一个监听器实例和一套回调列表。
  • 主线程 Handler:mHandler = new Handler(Looper.getMainLooper()) 是所有回调最终执行的线程锚点。
  • 回调列表:ArrayList<FileObserverCallback> 保存所有注册的业务回调。

3.2 路径更新与回调绑定

public void updateObserverPath(String observerPath) {
    if (isWatching()){
        stopObserver();
    }
    watchPath = observerPath;
    mOtaFileObserver = new OtaFileObserver(watchPath);
    mOtaFileObserver.setFileObserverCallback((event, path) -> mHandler.post(() -> {
        if (!mFileObserverCallbacks.isEmpty()) {
            for (FileObserverCallback callback : new ArrayList<>(mFileObserverCallbacks)) {
                callback.onChange(event, path);
            }
        }
    }));
}

来源:OtaFileObserverHelper.java

关键行为:

  • 换路径先停监听:若当前正在监听,先 stopObserver(),避免新旧监听器并存。
  • Lambda 绑定回调:系统 onEvent 触发后,通过 mHandler.post(...) 把事件投递到主线程队列。
  • 防并发修改的快照遍历:分发时使用 new ArrayList<>(mFileObserverCallbacks) 拷贝副本,防止遍历期间其他线程调用 unregisterFileObserverCallback 触发 ConcurrentModificationException。
  • 空列表短路:没有注册回调时直接跳过循环,降低主线程开销。

3.3 注册 / 注销 / 启停 / 销毁

public void registerFileObserverCallback(FileObserverCallback callback) {
    if (callback != null && !mFileObserverCallbacks.contains(callback)) {
        mFileObserverCallbacks.add(callback);
    }
}

public void unregisterFileObserverCallback(FileObserverCallback callback) {
    if (callback != null && !mFileObserverCallbacks.isEmpty()) {
        mFileObserverCallbacks.remove(callback);
    }
}

public void startObserver() {
    if (mOtaFileObserver != null) {
        mOtaFileObserver.startWatching();
        isWatching = true;
    }
}

public void stopObserver() {
    mOtaFileObserver.stopWatching();
    isWatching = false;
}

public void destroy() {
    stopObserver();
    mOtaFileObserver.setFileObserverCallback(null);
    mFileObserverCallbacks.clear();
    instance = null;
}

来源:OtaFileObserverHelper.java

行为分析:

  • registerFileObserverCallback 通过 contains 去重,同一回调重复注册只保留一份;空指针直接忽略。
  • unregisterFileObserverCallback 对 callback == null 与空列表均有防护。
  • startObserver 仅在监听器已创建(路径已更新)时生效,并把 isWatching 置为 true;stopObserver 未判空,暗示调用方需保证 mOtaFileObserver 非空(先 updateObserverPath 再启停的常规顺序)。
  • destroy 是完整清理:停止监听 → 解绑回调 → 清空列表 → 置空单例,使下一次 getInstance() 重建全新状态。

4. 固件文件配置入口

固件文件通过 BluetoothOption 配置,示例见 OtaDemo.java:

bluetoothOption.setFirmwareFilePath(firmwarePath); //设置本地存储OTA文件的路径
//        bluetoothOption.setFirmwareFileData(firmwareData);//设置本地存储OTA文件的数据

来源:OtaDemo.java

以及升级前重新设置路径的用法:

otaManager.getBluetoothOption().setFirmwareFilePath(filePath);
//* 进行OTA升级,然后根据回调进行UI更新

来源:OtaDemo.java

设计意图:路径方式让 SDK 按需流式读取文件(对大固件更友好),字节数据方式则把文件内容完全交给调用方内存管理。二者择一使用,配置后即作为后续 OTA 流程的固件输入源。

核心流程

文件监听事件流

sequenceDiagram
    participant FS as 外部写入方
    participant Dir as 固件目录(watchPath)
    participant FO as OtaFileObserver
    participant H as OtaFileObserverHelper
    participant Handler as Handler(MainLooper)
    participant App as 业务回调(FileObserverCallback)

    App->>H: updateObserverPath(path)
    App->>H: registerFileObserverCallback(cb)
    App->>H: startObserver()
    H->>FO: startWatching()
    FS->>Dir: 写入/修改固件文件
    Dir->>FO: inotify 事件
    FO->>FO: onEvent(event, path)
    Note over FO: 检查 callback!=null && path!=null
    FO->>H: callback.onChange(event, path)
    H->>Handler: mHandler.post(Runnable)
    Handler->>App: onChange(event, path) (主线程)
    App->>App: 解析/刷新固件 UI
    App->>H: unregisterFileObserverCallback(cb)
    App->>H: stopObserver() / destroy()
    H->>FO: stopWatching()

流程说明:

  1. 业务层先 updateObserverPath 指定要监听的固件目录(若正在监听会先停止旧监听)。
  2. 注册业务回调(可注册多个,内部去重),随后 startObserver 启动系统监听。
  3. 外部进程写入/修改/删除目录中的文件时,Android inotify 机制驱动 OtaFileObserver.onEvent。
  4. onEvent 空值校验通过后,把事件转发给 Helper 中绑定的 Lambda 回调。
  5. Lambda 通过 mHandler.post 将事件投递到主线程,遍历回调快照依次调用 onChange(event, path)。
  6. 业务层在回调中判断事件类型与路径,决定是否触发固件解析或 UI 刷新。
  7. 页面销毁或不再需要监听时,注销回调并 stopObserver(或整体 destroy 重置单例)。

固件文件配置流程

flowchart LR
    A["initOTA(context, firmwarePath)"] --> B["构建 OtaManager"]
    B --> C["构建 BluetoothOption"]
    C --> D{"选择固件输入方式"}
    D -->|"文件路径"| E["setFirmwareFilePath(path)"]
    D -->|"字节数据"| F["setFirmwareFileData(data)"]
    E --> G["OTA 升级流程读取固件"]
    F --> G

流程依据:OtaDemo.java

Usage Examples

示例一:初始化 OTA 并指定固件文件路径

public void initOTA(Context context, String firmwarePath) {
    OtaManager otaManager = new OtaManager();
    BluetoothOption bluetoothOption = new BluetoothOption();
    bluetoothOption.setPriority(BluetoothOTAConfigure.PREFER_BLE) //请按照项目需要选择
            .setUseAuthDevice(true) //具体根据固件的配置选择
            .setBleIntervalMs(500) //默认是500毫秒
            .setUseReconnect(false); //是否自定义回连方式,默认为false,走SDK默认回连方式,客户可以根据需求进行变更
    bluetoothOption.setFirmwareFilePath(firmwarePath); //设置本地存储OTA文件的路径
//        bluetoothOption.setFirmwareFileData(firmwareData);//设置本地存储OTA文件的数据
}

来源:OtaDemo.java

该示例展示了固件文件的推荐配置方式:在构建 OtaManager 与 BluetoothOption 时即设置固件路径,注释中保留了 setFirmwareFileData 的备选方案供按需切换。

示例二:升级前重新绑定固件文件

public void OtaFirmware(final String filePath) {
    //1.构建OTAManager对象
    ...
    otaManager.getBluetoothOption().setFirmwareFilePath(filePath);
    //* 进行OTA升级,然后根据回调进行UI更新
}

来源:OtaDemo.java

该示例说明固件路径可以在升级动作前随时重新设置——SDK 读取固件发生在升级流程内部,调用方只需保证在触发升级前路径有效且文件完整。

示例三:自定义文件监听(基于 OtaFileObserver + FileObserverCallback)

结合三个工具类的最小监听实现模式(摘自各文件定义):

// 1. 实现回调契约
FileObserverCallback callback = (event, path) -> {
    // 主线程回调,可安全更新 UI
    if ((event & FileObserver.CREATE) != 0) {
        // 固件文件已创建,触发解析
    }
};

// 2. 通过单例 Helper 注册并启动
OtaFileObserverHelper helper = OtaFileObserverHelper.getInstance();
helper.updateObserverPath(otaFileDir);   // 指定固件目录
helper.registerFileObserverCallback(callback);
helper.startObserver();

// 3. 页面销毁时清理
helper.unregisterFileObserverCallback(callback);
helper.destroy();

接口与实现来源:FileObserverCallback.java、OtaFileObserverHelper.java

Configuration Options

固件文件管理与监听相关的可配置项如下:

配置项类型默认值说明
BluetoothOption.setFirmwareFilePath(String)String无(必填其一)本地固件文件路径,SDK 按需读取
BluetoothOption.setFirmwareFileData(byte[])byte[]无(必填其一)固件字节数据,路径与数据二选一
BluetoothOption.setBleIntervalMs(int)int500BLE 发送间隔(毫秒),影响固件传输节奏
BluetoothOption.setUseAuthDevice(boolean)boolean依固件而定是否使用认证设备流程
BluetoothOption.setUseReconnect(boolean)booleanfalse是否自定义回连方式;false 走 SDK 默认回连
BluetoothOption.setPriority(...)枚举依项目而定蓝牙通道优先级(如 BluetoothOTAConfigure.PREFER_BLE)
OtaFileObserverHelper.updateObserverPath(String)String无要监听的固件文件目录绝对路径
OtaFileObserverHelper.registerFileObserverCallback(FileObserverCallback)回调无注册监听回调(内部去重)
OtaFileObserverHelper.startObserver()—未监听启动系统文件监听

配置依据:OtaDemo.java、OtaFileObserverHelper.java

API Reference

interface FileObserverCallback

文件监听事件的回调契约。

void onChange(int event, String path)

参数:

  • event (int):Android FileObserver 事件掩码,如 FileObserver.CREATE、MODIFY、DELETE、MOVED_TO 等,可组合判断。
  • path (String):发生变化的文件相对监听目录的路径;Helper 分发时保证非空。

说明: 通过 OtaFileObserverHelper 注册时,回调在主线程执行,可安全更新 UI。


class OtaFileObserver extends android.os.FileObserver

系统文件监听的最小封装。

OtaFileObserver(String path)

参数:

  • path (String):需要监听的目录绝对路径。

说明: 直接透传给 FileObserver 构造器,未指定事件掩码,使用系统默认全事件监听。

void setFileObserverCallback(FileObserverCallback callback)

参数:

  • callback (FileObserverCallback):事件回调,仅支持单个槽位。

void onEvent(int event, @Nullable String path)

重写行为: 当回调非空且 path 非空时调用 callback.onChange(event, path);否则静默丢弃事件。


class OtaFileObserverHelper(@Deprecated)

文件监听单例辅助类。

static OtaFileObserverHelper getInstance()

返回: 进程内唯一实例(DCL 双重校验锁,volatile 保证可见性)。

void updateObserverPath(String observerPath)

参数:

  • observerPath (String):新监听目录。

行为: 若正在监听先停止旧监听;重建 OtaFileObserver 并绑定主线程分发 Lambda。

void registerFileObserverCallback(FileObserverCallback callback)

参数:

  • callback (FileObserverCallback):待注册回调;空指针忽略,重复注册去重。

void unregisterFileObserverCallback(FileObserverCallback callback)

参数:

  • callback (FileObserverCallback):待注销回调;空指针或空列表时安全返回。

boolean isWatching()

返回: 是否处于监听状态。

String getWatchPath()

返回: 当前监听的目录路径,未设置时为 null。

void startObserver()

行为: 监听器存在时调用 startWatching() 并置 isWatching = true。

void stopObserver()

行为: 调用 stopWatching() 并置 isWatching = false。注意:未判空,需保证先 updateObserverPath。

void destroy()

行为: 停止监听 → 解绑回调 → 清空回调列表 → 置空单例,完整重置状态。


BluetoothOption 固件配置方法

void setFirmwareFilePath(String path)

参数: path (String):本地固件文件路径。

说明: 与 setFirmwareFileData 二选一;路径方式适合大固件按需读取。

void setFirmwareFileData(byte[] data)

参数: data (byte[]):固件完整字节数据。

说明: 与 setFirmwareFilePath 二选一;数据方式把文件内容交由调用方内存管理。

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

  • 监听器未创建就启停:stopObserver() 与 startObserver() 未对 mOtaFileObserver 判空。若调用方在 updateObserverPath 之前直接调用 stopObserver(),会触发空指针。正确用法是先更新路径再启停(源码中 updateObserverPath 内部已保证该顺序)。
  • path == null 事件被丢弃:Android 部分 FileObserver 事件可能不带路径,OtaFileObserver.onEvent 对此类事件静默忽略。依赖 path 的业务逻辑需自行处理该情况。
  • 回调遍历的并发安全:分发时通过 new ArrayList<>(mFileObserverCallbacks) 快照遍历,规避了回调在遍历中被注销导致的 ConcurrentModificationException;但注册/注销本身并非线程安全,建议在主线程操作。
  • 重复注册去重:registerFileObserverCallback 用 contains 判重,同一回调多次注册只生效一次,避免事件重复分发。
  • 单例状态残留:destroy() 将 instance 置空,但已持有旧引用的调用方若继续调用其方法,会操作已清理的对象(回调列表为空、监听已停),行为是"安全空转"而非崩溃。
  • 目录级监听粒度:FileObserver 监听的是目录,回调中的 path 是相对文件名;业务层需自行过滤非固件文件(如校验扩展名)以免误触发。

性能与运维考量

  • 主线程分发代价:所有回调经 mHandler.post 在主线程串行执行。高频文件事件(如固件分块写入产生的连续 MODIFY)会积压主线程队列,回调内应避免耗时操作,可做事件合并或节流。
  • 单例生命周期:Helper 全进程共享,建议在 Application 或 OTA 专属页面级管理注册/注销,避免页面重建导致回调泄漏。
  • 事件风暴防护:SDK 示例注释中保留了 JL_Log.d 调试日志(默认关闭),生产环境无需开启;对大规模文件写入场景,建议在回调中按事件类型聚合处理。

扩展点

  • 自定义监听粒度:OtaFileObserver 构造器目前未传事件掩码(系统默认全事件)。业务方可继承 FileObserver 并按需传入掩码,例如只监听 CREATE | MOVED_TO 以捕获固件文件就绪时刻。
  • 自定义回调实现:FileObserverCallback 为单方法接口,可配合 Java 8 Lambda 或实现类实现多态分发(如按 event 类型路由到不同处理器)。
  • 替代 Helper 的自建分发:由于 OtaFileObserverHelper 已 @Deprecated,新代码可自行组合 OtaFileObserver + 自己的注册表/线程策略,保持同样的观察者模式而获得更强的可控性。
  • 固件输入方式扩展:setFirmwareFilePath / setFirmwareFileData 双入口为调用方提供了"流式读取"与"内存直传"两种固件供给策略,可据此对接下载完成通知、加密固件解包等前置处理。

Tests

仓库中 otasdk/src/test/java/com/jieli/otasdk/OtaDemo.java 提供了固件文件配置与 OTA 初始化的演示级测试代码,覆盖:

  • 通过路径方式配置固件文件并初始化 OTA(initOTA)
  • 升级前重新设置固件路径(OtaFirmware)
  • 设备强制升级场景中基于版本号/UID 判断固件支持性(回调注释说明)

该文件是 SDK 面向集成方的典型用法范例,可作为固件文件管理入口的参考集成模板。文件监听三件套(FileObserverCallback / OtaFileObserver / OtaFileObserverHelper)本身未附带独立单元测试,其行为需通过真机目录操作验证。

Related Links

  • OtaFileObserver.java — 系统文件监听封装
  • OtaFileObserverHelper.java — 单例分发辅助类
  • FileObserverCallback.java — 回调接口定义
  • OtaDemo.java — 固件文件配置与 OTA 初始化示例
  • 蓝牙连接与 OTA 传输细节 → 参见蓝牙管理相关目录页
  • OTA 升级流程与进度回调 → 参见 OTA 流程相关目录页
Prev
OTA 升级流程与状态模型