心脏类疾病的突发性极强,从出现症状到危及生命往往只有短短几分钟。智能手表、心率带等可穿戴设备普及之后,基于Android手机做一套心脏病发作的风险检测与预警系统成为很多开发者感兴趣的方向。这篇文章就从实际开发的角度,把心率数据采集、异常检测算法、本地预警与紧急通知这几个核心环节逐一拆开讲清楚,并附上可以直接运行的代码示例。

一、心率数据从哪里来:BLE蓝牙心率传感器接入
Android手机本身并不直接测量心率,主流方案是通过蓝牙低功耗(BLE)连接心率带、手环或者带心率传感器的智能手表。蓝牙技术联盟为这类设备定义了标准的心率服务(Heart Rate Service),UUID为0000180d-0000-1000-8000-00805f9b34fb,其中心率特征值的UUID为00002a37-0000-1000-8000-00805f9b34fb。只要设备符合这个标准,我们的App就可以用统一的方式读取数据,不需要针对每个厂商单独适配。
接入流程大致分四步:扫描设备、建立连接、发现服务、订阅心率特征值的通知。需要注意的是,心率特征值的数据格式有两种,第一个字节的最低位为0时表示心率用一个字节表示,为1时表示用两个字节(uint16)表示,解析时必须区分,否则剧烈运动时心率超过255就会出现错误读数。另外,BLE操作在Android上有严格的线程限制,所有连接和读写操作都要放在Binder线程回调里完成,不能放到主线程,否则系统会直接抛出异常。
// 订阅心率特征值通知的核心代码
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices(); // 连接成功后必须先发现服务
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
BluetoothGattService hrService = gatt.getService(
UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"));
BluetoothGattCharacteristic hrChar = hrService.getCharacteristic(
UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb"));
gatt.setCharacteristicNotification(hrChar, true);
// 还需向描述符写入ENABLE_NOTIFICATION_VALUE,不同写法略有差异
BluetoothGattDescriptor descriptor = hrChar.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
byte[] data = characteristic.getValue();
int flag = data[0] & 0x01;
int heartRate;
if (flag == 0) {
heartRate = data[1] & 0xFF; // uint8格式
} else {
heartRate = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF); // uint16格式
}
onNewHeartRate(heartRate);
}
};权限方面,Android 12以上的版本需要申请BLUETOOTH_SCAN和BLUETOOTH_CONNECT运行时权限,Android 11及以下则使用BLUETOOTH加上定位权限。建议在代码里按SDK版本做分支处理,否则在高版本设备上会出现扫描不到设备的问题。
二、异常检测算法:从简单阈值到滑动窗口
拿到心率流之后,最简单的异常判断是设一个阈值区间,比如静息状态下正常心率在50到100之间,如果持续高于120或低于40就触发告警。这种单点阈值实现起来最容易,但缺点也很明显:传感器偶尔出现一个坏点、或者用户跑了两步,都会造成误报。误报太频繁会让用户关掉通知功能,整个预警系统就失去了意义。
比较务实的改进方案是滑动窗口加连续确认机制。维护一个最近N个采样的队列,只有当窗口内超过80%的采样都超出阈值,并且这种状态持续了一定时间(比如30秒),才判定为异常。这样既能过滤掉单点噪声,又不会因为反应太慢而耽误预警。更进一步,可以计算心率变异性(HRV),也就是相邻心跳间隔的变化程度。心脏病发作前,心率变异性通常会呈现明显异常,这个指标比单纯的心率数值更有参考价值。
// 滑动窗口 + 连续确认的异常检测示例
private static final int WINDOW_SIZE = 30; // 最近30个采样点
private static final int ALARM_THRESHOLD = 24; // 至少24个点异常才告警
private static final int HIGH_HR = 120;
private static final int LOW_HR = 40;
private final Deque<Integer> window = new ArrayDeque<>();
public boolean checkAbnormal(int heartRate) {
window.addLast(heartRate);
if (window.size() > WINDOW_SIZE) {
window.removeFirst();
}
if (window.size() < WINDOW_SIZE) {
return false; // 数据不足时不判断,避免误报
}
long abnormalCount = window.stream()
.filter(hr -> hr > HIGH_HR || hr < LOW_HR)
.count();
return abnormalCount >= ALARM_THRESHOLD;
}需要特别强调的一点是,手机App的检测算法无论做得多精细,都只能算风险提示,不能替代医疗诊断设备。这个定位一定要在产品层面说清楚,既是对用户负责,也是规避合规风险的必要措施。
三、后台监测与紧急通知的实现
检测逻辑写好之后,接下来要解决的是持续运行的问题。Android从8.0开始对后台进程的限制越来越严格,普通的Service在应用退到后台后很快就会被系统回收。做健康监测类应用,目前比较可靠的组合是前台服务加FOREGROUND_SERVICE_HEALTH类型(Android 14起要求声明),前台服务会挂一个常驻通知,系统就不会轻易杀死进程。同时配合WorkManager做兜底,即便进程被杀,工作管理器也会在条件合适时把服务拉起来。
告警触发后的动作设计同样重要。除了本地震动和声音提醒,紧急通知是这类应用的核心价值。最直接的方案是发送短信给预设的紧急联系人,并附带当前定位,方便家属或急救人员快速找到用户。定位用FusedLocationProviderClient获取即可,短信则需要SEND_SMS权限,注意这个属于敏感权限,上架应用商店时需要提供使用场景说明。下面是触发告警后发送带位置的求助短信的示例。
// 检测到异常后发送求助短信
public void sendEmergencySms(String contact, double lat, double lng) {
String message = "健康预警:检测到心率异常,请尽快联系本人。"
+ "位置信息:https://maps.google.com/?q=" + lat + "," + lng;
SmsManager smsManager;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
smsManager = getSystemService(SmsManager.class);
} else {
smsManager = SmsManager.getDefault();
}
// 长短信需要分段发送
ArrayList<String> parts = smsManager.divideMessage(message);
smsManager.sendMultipartTextMessage(contact, null, parts, null, null);
}还有一个容易忽略的细节是电池优化。部分国产手机厂商的定制系统会把不在白名单内的应用强行清理掉,即便你用了前台服务也无济于事。实践中的做法是引导用户把应用加入电池优化白名单,或者在设置页提供跳转到对应厂商后台管理界面的入口,这部分可以参考各个厂商的推送接入文档来做适配。
四、开发中容易踩的坑与优化建议
第一个坑是传感器数据质量。廉价心率带的原始数据抖动很大,直接进算法会产生大量误报。建议在算法层之前先加一个简单的滤波,比如去掉明显不可能的值(心率低于20或高于250),再做三点的中值滤波,成本低但效果明显。第二个坑是重连逻辑,BLE连接非常脆弱,用户走出几米远、手机息屏、系统省电策略都可能造成断连,必须写好自动重连机制,并给用户清晰的连接状态提示。
第三个坑是数据存储。长期监测会产生大量心率记录,如果全部塞进SQLite单表,查询性能会逐渐恶化。可以按天分表,或者只保留聚合统计值,明细数据定期归档。最后,如果打算接入医疗级的心电采集设备做更准确的判断,可以研究Antidysrhythmia相关的私有协议或者厂商SDK,但复杂度会比标准心率服务高一个量级,建议在产品初期先用标准心率数据把整个链路跑通,再逐步升级数据源。