4. 移动端数据采集应用开发
移动端是DHT(Decentralized Health Trial,去中心化临床试验)远程数据采集链条中离受试者最近的一环。它要同时完成四件事:与DHT设备建立可靠通信、把采集到的数据安全缓存、在网络不稳定时保证离线可用与后续同步、以及让不同年龄和能力的受试者都能顺利完成操作。本章围绕Android与iOS两端,讲清这四件事在工程上具体怎么做。
一、与DHT设备的通信方式选择
DHT设备通常包括可穿戴传感器(心率、体温、血氧、活动量)、电子药盒、家用血压计、血糖仪、肺功能仪等。移动端与它们的通信主要有三条路径:
- BLE(低功耗蓝牙):最常用。适合心率带、体温贴、电子药盒等低功耗设备。Android使用
BluetoothGatt,iOS使用CoreBluetooth。特点是需要配对、连接、发现服务与特征值,再订阅通知或主动读取。 - USB/串口或厂商SDK:部分血压计、血糖仪通过USB OTG或厂商提供的Android/iOS SDK接入,数据格式由厂商定义。
- 近场或二维码中转:设备无蓝牙时,可通过设备屏幕显示二维码、手机扫码录入,或NFC触碰读取。
以BLE为例,一次完整采集的典型流程是:扫描广播 → 连接设备 → 发现服务(Service)与特征值(Characteristic)→ 订阅通知或读取 → 解析字节数组 → 写入本地缓存。下面这张图展示了移动端与DHT设备之间的信号链与数据流关系。
二、数据缓存与离线同步
移动端不能假设网络始终可用。受试者可能在地铁、电梯、偏远地区完成测量,此时数据必须先落到本地,等网络恢复后再上传。缓存设计要解决三个问题:存什么、怎么标记状态、怎么保证不丢不重。
建议每条采集记录至少包含以下字段:record_id(客户端生成的UUID)、device_id、metric_type、value、unit、measured_at(设备时间)、received_at(手机接收时间)、sync_status(pending/synced/failed)、retry_count。其中record_id是幂等键,后端以它去重,避免重复上传造成数据膨胀。
同步策略推荐“先本地后远端、批量优先、失败退避”:网络可用时按时间顺序批量提交,每批不超过50条;单条失败不阻塞整批,标记为failed并进入重试队列;重试采用指数退避(如1s、2s、4s、8s,上限5分钟),超过阈值后提示用户手动重试。Android可用WorkManager,iOS可用BGTaskScheduler,在系统允许的后台窗口内完成补传。
sync_status和measured_at建索引,保证待同步队列查询和按时间排序的效率。三、用户交互设计
DHT受试者往往不是技术人员,交互设计要降低认知负担。核心原则是:状态可见、操作可逆、错误可恢复。
- 连接状态:用颜色和文字同时表达。例如“已连接”用绿色圆点加文字,“搜索中”用蓝色旋转图标,“连接失败”用橙色并给出“重试”按钮。不要只靠颜色区分,色盲用户无法识别。
- 采集过程:显示实时数值或进度条,让用户知道设备正在工作。例如血压计采集时显示“正在充气…”,完成后显示收缩压/舒张压/脉搏三个数值。
- 异常处理:设备未佩戴、电量低、信号弱时给出具体原因和下一步动作,而不是笼统的“失败”。
- 确认与撤销:手动录入的数据在提交前允许修改;已提交但未同步的记录允许删除,删除操作写入本地日志以便审计。
四、无障碍适配
临床试验人群可能包含老年人、视障或运动功能受限者,无障碍不是可选项。Android和iOS都提供了系统级无障碍框架,工程上要落实以下几点:
| 适配项 | Android做法 | iOS做法 |
|---|---|---|
| 屏幕阅读器 | 为控件设置contentDescription,自定义View实现ExploreByTouchHelper | 设置accessibilityLabel、accessibilityHint、accessibilityTraits |
| 触控目标 | 最小48dp×48dp | 最小44pt×44pt |
| 动态字体 | 使用sp单位,响应系统字体缩放 | 使用Dynamic Type,支持UIFontMetrics |
| 对比度 | 正文与背景对比度≥4.5:1 | 同左,并支持“增强对比度”开关 |
| 语音提示 | 关键操作配合TalkBack播报 | 配合VoiceOver播报 |
此外,采集流程应支持“一键完成”:对于行动不便的受试者,减少滑动、长按、多指手势,改用大按钮单击。表单输入尽量用选择器代替键盘输入,减少打字负担。
五、一个可参考的采集与同步闭环
以电子药盒为例:受试者打开药盒,药盒通过BLE广播“已开盖”事件;App连接后读取事件时间戳,生成一条medication_event记录,写入本地缓存并标记pending;若当前有网络,同步引擎立即上传,成功后标记synced;若无网络,记录留在队列中,App界面显示“待同步1条”,网络恢复后自动补传。整个过程用户只需打开药盒,无需额外操作。开发时可用以下伪代码验证幂等逻辑:
// 伪代码:幂等写入与同步
fun saveEvent(event: Event) {
val existing = db.findByRecordId(event.recordId)
if (existing != null) return // 已存在,直接返回
db.insert(event.copy(syncStatus = "pending"))
}
fun syncPending() {
val batch = db.queryPending(limit = 50)
for (e in batch) {
try {
api.upload(e) // 后端以 recordId 去重
db.markSynced(e.recordId)
} catch (ex: Exception) {
db.markFailed(e.recordId, retry = e.retryCount + 1)
}
}
}
判断同步是否成功,不能只看HTTP 200,还要核对后端返回的record_id列表与本地批次是否一致;若存在缺失,应将该条重新置为pending。只有本地队列为空且后端确认全部接收,才算完成一次完整闭环。
移动端开发的质量,最终体现在“受试者是否愿意持续使用”和“数据是否完整可信”这两件事上。通信稳定、缓存可靠、交互清晰、无障碍到位,四者缺一不可。