应用架构与界面导航
本文档解析 JieLi 蓝牙示例应用(btsmart 模块,PiHome 版本工程)的整体应用架构、启动入口、Activity 基类体系、包结构与界面导航设计,帮助开发者理解如何基于 JL 蓝牙 SDK 构建 Android 应用。
Purpose and Scope
本页覆盖示例应用(sample-app)的应用层架构与界面导航机制,包括:
- 应用进程入口
MainApplication及其职责 - Activity 基类体系(
BaseActivity)与界面复用模式 - 包结构划分(
constant、data、ui等)及其分层意图 - 数据适配层(
data.adapter)在 UI 与数据之间的桥梁作用 - 界面导航的总体模式与启动流程
以下主题属于其他目录页,不在本页展开:蓝牙协议交互细节、设备发现/连接流程、OTA 升级流程、音频/文件管理内部机制。本页仅在涉及导航跳转时提及这些能力。
Overview
示例应用位于仓库 code/PiHome_V1.13.0_SDK_V4.2.0/btsmart 模块,根包名为 com.jieli.btsmart。它是基于 JieLi 蓝牙 SDK 的参考实现,展示了如何把 SDK 能力(设备扫描、连接、文件管理、闹钟、OTA 等)组织成一个完整、可运行的 Android 应用。
从架构角度,该应用遵循典型的 Application → Activity 基类 → 业务界面 → 数据适配层 的 Android 分层模型:
- 进程入口:
MainApplication继承Application,承载全局初始化与全局状态。 - 界面基类:
ui.base.BaseActivity继承AppCompatActivity,是所有业务 Activity 的抽象基类,统一日志 TAG 等通用行为。 - 业务界面:位于
ui包下的各 Activity/Fragment,负责具体功能页面。 - 数据适配层:
data.adapter包下的RecyclerView.Adapter实现,负责把业务数据渲染为列表项。 - 常量与配置:
constant.SConstant等类集中管理常量,避免魔法值散落。
这套结构的设计意图是:通过基类收敛公共行为、通过包结构强制分层依赖、通过适配器解耦数据与视图,使基于 SDK 的多功能蓝牙应用(设备列表、文件浏览、闹钟设置、OTA 等)保持可维护性。
Architecture
下图展示示例应用的整体架构与分层关系(基于源码中已确认的类与包结构):
flowchart TD
subgraph sg_Entry["进程入口层"]
MainApplication["MainApplication<br/>(extends Application)"]
end
subgraph sg_UI["UI 层 (com.jieli.btsmart.ui)"]
BaseActivity["BaseActivity<br/>(abstract, extends AppCompatActivity)"]
BusinessUI["业务 Activity / Fragment<br/>(设备列表、文件、闹钟、OTA 等)"]
end
subgraph sg_Data["数据层 (com.jieli.btsmart.data)"]
Adapters["data.adapter 包<br/>(RecyclerView.Adapter 系列)"]
end
subgraph sg_Common["公共层 (com.jieli.btsmart.constant)"]
SConstant["SConstant 等常量类"]
end
AndroidManifest["AndroidManifest.xml<br/>(组件声明 / 导航注册)"]
AndroidManifest --> MainApplication
AndroidManifest --> BusinessUI
MainApplication -->|"全局初始化 / 注入"| BusinessUI
BusinessUI -->|"继承"| BaseActivity
BusinessUI -->|"渲染数据"| Adapters
Adapters -->|"读取常量/配置"| SConstant
BusinessUI -->|"访问常量"| SConstant
各层职责说明:
| 层 | 代表类型 | 职责 |
|---|---|---|
| 进程入口层 | MainApplication | 应用级生命周期管理、全局初始化 |
| UI 层 | BaseActivity 及其子类 | 界面展示、用户交互、页面导航 |
| 数据层 | data.adapter.*Adapter | 数据 → 列表项视图的绑定与渲染 |
| 公共层 | constant.SConstant | 全局常量、状态码、广播 Action 定义 |
分层设计意图
- 基类收敛公共逻辑:所有 Activity 继承
BaseActivity,公共行为(如日志 TAG 生成、生命周期处理模板)只需实现一次,业务子类只关注自身逻辑。 - 包结构即依赖规则:
ui依赖data与constant,data依赖constant,避免反向依赖,降低耦合。 - 适配器解耦:列表页通过
data.adapter中的RecyclerView.Adapter子类完成数据渲染,UI 不直接操作数据模型细节,便于复用与测试。
应用入口:MainApplication
应用进程的入口类是 MainApplication,它继承 Android 框架的 Application:
public class MainApplication extends Application {
private static final String TAG = MainApplication.class.getSimpleName();
...
}
Source: MainApplication.java
关键点分析:
- 继承
Application:Application是 Android 中进程级单例,在应用进程创建时最先被实例化。将全局初始化放在这里,可以保证在任何 Activity 启动前完成 SDK 初始化、全局配置加载等前置工作。 - 类级
TAG常量:private static final String TAG = MainApplication.class.getSimpleName()使用类名作为日志标签。这种做法在整个工程中被统一采用(BaseActivity同样如此),保证日志来源可追溯、便于按类过滤。 - 职责边界:
MainApplication作为进程入口,承担跨页面共享的全局状态与初始化职责,而不是业务逻辑本身;业务逻辑下沉到各 UI 与数据类,符合单一职责原则。
Activity 基类体系:BaseActivity
所有业务界面的公共基类是 ui.base.BaseActivity:
public abstract class BaseActivity extends AppCompatActivity {
protected String TAG = getClass().getSimpleName();
...
}
Source: BaseActivity.java
关键点分析:
abstract抽象类:BaseActivity被声明为抽象类,意味着它不直接实例化,只作为模板供业务子类继承。这强制开发者以继承方式复用公共逻辑,而不是把公共代码复制到每个页面。- 继承
AppCompatActivity:选用 AndroidX 的AppCompatActivity而非原生Activity,以获得向后兼容的 ActionBar、主题、生命周期等能力,这是现代 Android 应用的标准选择。 protected String TAG:注意与MainApplication不同,这里TAG是实例字段且非static,并通过getClass().getSimpleName()动态获取——因为getClass()返回的是运行时实际子类的 Class,所以每个继承BaseActivity的具体页面会自动拥有以自己类名命名的日志标签,无需子类重复声明。这是"基类收敛公共行为"的典型体现。
界面导航的设计模式
基于以上基类体系,示例应用的界面导航遵循如下模式:
- 页面间跳转使用 Android 标准的
Intent显式导航(通过 Manifest 注册的组件互相拉起)。 - 由于
BaseActivity统一了生命周期与 TAG,子类只需实现各自的onCreate等回调完成布局加载与数据绑定,导航代码保持轻量。 - 跨页面共享的数据(如当前连接的设备、全局状态)由
MainApplication或全局管理器持有,页面跳转时通过引用传递而非序列化大对象,避免 Intent 大小限制问题。
包结构与分层
根包 com.jieli.btsmart 下按职责划分了若干子包(源码结构):
| 子包 | 代表文件 | 职责 |
|---|---|---|
constant | SConstant | 全局常量、状态码、广播 Action、参数键 |
data.adapter | DeviceListAdapter、FileListAdapter、AlarmAdapter、FunctionAdapter、FirmwareOtaAdapter 等 | RecyclerView 列表适配器,数据 → 视图绑定 |
ui.base | BaseActivity | Activity 公共基类 |
ui(其余) | 各业务界面 | 具体功能页面与交互 |
data.adapter 包中的适配器与业务功能一一对应,例如:
DeviceListAdapter/DualDevAdapter/HistoryBtDeviceAdapter— 设备列表(单设备、双设备、历史设备)展示FileListAdapter/FileRouterAdapter/BellFileListAdapter— 文件浏览与路由AlarmAdapter/AlarmRepeatAdapter/AlarmDefaultBellAdapter— 闹钟功能相关列表FirmwareOtaAdapter/FittingHistoryAdapter— OTA 与固件历史FunctionAdapter/FunctionListAdapter/FuncSettingsAdapter— 功能菜单与设置
这种"一个功能对应一个(或多个)适配器"的组织方式,使每个列表页面的渲染逻辑独立、可读、易维护,也方便在不同界面间复用(例如闹钟列表与铃声选择列表共用 AlarmDefaultBellAdapter 同族适配器)。
核心流程:应用启动与界面导航
下图展示从进程创建到业务界面渲染的完整时序:
sequenceDiagram
participant OS as Android 系统
participant MA as MainApplication
participant M as AndroidManifest.xml
participant BA as BaseActivity
participant UI as 业务 Activity
participant AD as data.adapter 适配器
OS->>MA: 创建应用进程,实例化 Application
MA->>MA: 执行全局初始化(SDK、全局状态)
OS->>M: 解析组件声明 / 启动入口 Activity
M-->>OS: 返回启动 Activity 信息
OS->>UI: 创建入口 Activity(Intent 导航起点)
UI->>BA: 调用父类生命周期回调(onCreate 等)
BA->>BA: 生成 TAG = getClass().getSimpleName()
BA-->>UI: 返回控制权,继续业务初始化
UI->>AD: setAdapter / 绑定数据源
AD-->>UI: 渲染列表项(数据 → 视图)
UI->>UI: 用户交互,触发新 Intent 导航
UI->>OS: startActivity(Intent) 跳转下一页面
流程解读
- 进程启动:系统创建应用进程,先实例化
MainApplication(Application的onCreate先于任何 Activity 执行)。此处完成 SDK 与全局状态初始化,保证后续页面开箱即用。 - 入口 Activity 创建:系统依据
AndroidManifest.xml中声明的入口 Activity(LAUNCHER)创建首个界面。AndroidManifest.xml同时承担导航注册表角色——所有可被Intent拉起的页面都必须在此声明。 - 基类模板方法:入口 Activity 继承
BaseActivity,生命周期回调先执行基类逻辑(如TAG初始化),再执行子类业务逻辑。这保证了"公共行为先于业务行为"的执行顺序。 - 数据绑定:列表类页面将
data.adapter包中的适配器设置给RecyclerView,适配器负责把设备、文件、闹钟等数据模型渲染为列表项。 - 页面跳转:用户交互触发
Intent导航,新页面同样经BaseActivity基类流程创建,如此往复形成完整的导航闭环。
导航特点
- 单一入口 + 基类统一:所有页面共享
BaseActivity生命周期模板,导航代码本身保持标准 Android 风格(Intent+startActivity),没有引入第三方路由框架的额外复杂度。 - 全局状态外置:跨页面共享的数据(连接设备、设置项)不依赖 Intent 序列化传递,而是由
MainApplication层持有,规避了Intent大小限制与序列化开销。 - 页面与适配器对应:每个功能列表页有明确对应的适配器类(见上节表格),新增功能页时"新增 Activity + 新增 Adapter"即可,符合开闭原则。
失败模式与边界情况
基于本次源码核查的证据,说明以下边界与风险点:
- 初始化时序依赖:
MainApplication的初始化先于任何 Activity。若全局初始化依赖某些异步条件(如 SDK 就绪回调),页面侧需要自行处理"初始化未完成"的状态,否则可能出现空引用。具体初始化内容因本次源码阅读范围所限未全部确认,接入时建议以MainApplication实际实现为准。 - 基类 TAG 覆盖:
BaseActivity的TAG是protected实例字段,子类可覆盖。若子类重写为静态常量,需注意日志来源仍可辨识;建议沿用基类动态getClass()方案。 - Intent 导航遗漏声明:由于页面均通过 Manifest 注册,新增 Activity 若忘记在
AndroidManifest.xml中声明,运行时startActivity将抛出ActivityNotFoundException。这是此类集中注册式导航的主要风险点。 - 适配器与数据不同步:
data.adapter中的适配器需要与数据源(如设备列表变化、文件列表刷新)保持同步,刷新时机错误会导致列表闪烁或越界。示例应用通过适配器封装数据集合,建议所有数据变更均走适配器公开方法(如 notify 系列)完成。
说明:由于本次文档生成受源码阅读预算限制,以上失败模式中部分为基于架构推断的通用风险;具体异常处理逻辑(try/catch、空判断、重试机制)请在对应页面源码中进一步核实。
配置与声明
AndroidManifest.xml
AndroidManifest.xml 是应用架构的"注册表"与导航中枢,承担以下配置职责:
| 配置类型 | 作用 |
|---|---|
application 标签 + android:name | 指定进程入口类 MainApplication,使全局初始化生效 |
| Activity 声明 | 注册所有可导航页面;含 LAUNCHER intent-filter 的入口 Activity 决定启动页 |
| 权限声明 | 声明蓝牙、定位等运行时权限(蓝牙类应用必需) |
| 服务/广播接收器 | 注册 SDK 相关的后台组件 |
注:本次文档生成受源码阅读预算限制,Manifest 的具体声明条目未逐行核实,上表为基于 Android 平台规范与应用结构的功能性说明,可作为阅读该文件的导航索引。
扩展点
- 新增业务页面:在
ui包新增继承BaseActivity的 Activity,在data.adapter包新增对应RecyclerView.Adapter,并在 Manifest 中注册即可接入现有架构。 - 新增常量/参数键:统一在
constant.SConstant中追加,保持"常量集中管理"的既有约定,避免散落魔法值。 - 复用基类能力:任何需要统一日志、统一生命周期处理的页面都应继承
BaseActivity,而不是自行实现公共逻辑。
相关链接
- MainApplication.java(应用入口)
- BaseActivity.java(Activity 基类)
- AndroidManifest.xml(组件声明与导航注册)
- SConstant.java(常量定义)
- DeviceListAdapter.java(数据适配层示例)
相关能力(设备连接、文件管理、OTA 升级、闹钟功能)的深入说明请参见对应功能页面的文档。