☰
安卓加速度计跌倒检测APP实现:三段式算法与报警机制
2026/10/16 11:21:37 网站建设 项目流程

前阵子给一个居家健康监护方向的Demo项目做功能迭代,团队里反复商量需求,最后锁定的核心场景是:独居老人在家跌倒后,能不能在几分钟内通知到家人。市面上的方案并不少,有挂坠式的专业跌倒报警器,也有带摔倒检测的智能手环。挂坠式设备的优势是检测准,但问题很现实——用户得多戴一个设备,充电、佩戴、遗忘这些事在日常使用里都会发生。我们讨论到最后,决定换个思路:让用户口袋里那台手机直接充当跌倒检测传感器。

这篇文章就把我从零实现一个跌倒警报器APP的全过程拆开讲,包括传感器原理、判定算法、核心代码、误报调试和真机测试。适合两类人看:一类是刚接触安卓传感器开发、想快速做健康监护类Demo的开发者;另一类是硬件或产品相关从业者,想评估"手机方案做跌倒检测到底可不可行"。读完你至少能动手搭一个能跑通的原型,并且知道一整套参数该怎么调、坑在哪里。

1. 为什么手机能代替专业跌倒检测设备

1.1 加速度计究竟在测什么

很多人对加速度计有个误解,以为它测的是"速度的变化",其实它测量的是"比力",可以简单理解成设备感受到的受力情况。手机静止放在桌面上时,加速度计读数是9.8m/s²左右,方向竖直向上,这正好是重力加速度的量值。也就是说,加速度计在静止状态下测到的其实是地球引力给的"支撑力"。

这个细节对跌倒检测非常关键。当手机跟随人体一起自由下落时,手机和人体都在做同样的加速运动,加速度计反而读不到重力了,三轴读数会同时趋近于0。这就形成了跌倒信号里的第一个特征——失重阶段。等人体撞击地面,手机瞬间被强烈的冲击力带动,加速度计会在极短时间内测到一个远大于重力加速度的尖峰,比如20m/s²甚至更高,这是第二个特征——撞击阶段。撞击之后如果人躺在地上没有动作,加速度计读数又会回到接近9.8m/s²的水平,只是方向相对原先有了变化,这是第三个特征——静止阶段。

所以手机里那颗小小的加速度计,已经足够捕获跌倒动作的核心物理特征。剩下的问题只是如何把这三段特征从噪声里干净地识别出来。

1.2 三轴坐标与佩戴姿势的麻烦

安卓系统的传感器坐标系有约定:X轴指向屏幕右侧,Y轴指向屏幕上方,Z轴垂直于屏幕向外。手机竖屏放进口袋时,Z轴大致朝向身体,静息读数的基准值会偏向Z轴。但手机一旦横着放、倒着放,甚至拿在手里刷视频,静态基准就完全变了。

这就是手机方案和挂坠式设备最大的区别:挂坠的姿态相对固定,可以针对单一姿态优化算法;手机的姿态千变万化,如果直接拿原始单轴数据做判断,很容易误报漏报。所以在做算法设计时,我不会盯着某一个轴的原始读数看,而是先计算三轴合成加速度,也就是Math.sqrt(x*x + y*y + z*z),这个标量值不依赖手机朝向,无论手机是横是竖,静止时它都稳定在重力加速度附近。

用合加速度作为判断依据,是整个方案里最基础也最重要的一步。后面的失重判断、撞击判断,全部围绕这个合成值展开,而不是某个轴单独读数。这个简化让算法在面对不同佩戴姿势时依然能够保持稳定。

1.3 手机方案和专用硬件的取舍

专用跌倒检测设备在准确率上确实有优势,尤其是那些内置多传感器、专门针对跌倒场景调优的医疗级产品。但这类设备的推广一直有个绕不过去的坎:用户得记得戴。独居老人日常行动不便,挂坠很容易被摘下来放在床头柜上充电,一旦摘下来,再灵敏的传感器也等于零。

手机方案天然解决了"佩戴"这个问题。现代人几乎不会脱离开手机,尤其是独居老人,手机不仅是通讯工具,还是他们和外界最重要的联系渠道。手机方案的难点就变成了:硬件性能受限、佩戴位置不固定、系统后台限制多。但这些难点都可以通过算法和工程手段去弥补。

我当时的选择是两者结合:把手机方案作为主要终端,同时在代码层预留了接入外部BLE跌倒检测手环的接口。如果用户手头有手环,手环触发信号直接作为高优先级事件;如果没有手环,手机上的传感器算法作为兜底。这样既保证了易用性,也保留了升级空间。

2. 跌倒检测算法:一次跌倒留下的三条波形线索

2.1 一次典型跌倒的瞬时过程

我做过很多次模拟测试,在软垫上反复练习不同类型的跌倒,同时用手机记录传感器波形。排除掉各种干扰之后,一条清晰的跌倒信号通常包含三个阶段。

第一阶段是失重:人体失去平衡开始下坠,加速度计读数急剧下降,合成的合加速度从9.8m/s²掉到2m/s²以下,持续的时间通常在0.2到0.4秒左右。第二阶段是撞击:身体或手机撞到地面,合加速度在瞬间冲到20m/s²以上,甚至能达到30m/s²,这个尖峰非常短,只有几十毫秒。第三阶段是静止:撞击结束后如果人没有立即站起来,合加速度回到接近9.8m/s²的水平,但持续一段时间保持低幅值波动。

如果只用单一阈值判断,比如"合加速度超过25m/s²就报警",那弯腰捡东西、用力拍桌子、手机从口袋滑落都会触发误报。因为这些动作也可能产生单次高尖峰。跌倒之所以能被识别,关键在于它是"失重-撞击-静止"这个完整序列,而不是孤立的一个尖峰。所以检测算法的核心思想,是识别这个三段式序列。

2.2 失重、撞击、静止:三段式判定逻辑

我把检测逻辑实现成一个简单的状态机,在内存里维护一个有限状态集合。

初始状态是"空闲态",此时持续读取合加速度。一旦发现合加速度低于失重阈值,比如小于2.5m/s²,就进入"失重态",同时记录进入失重态的时间戳。在失重态中,如果在限定的时间窗口内,比如1.5秒内,合加速度突然冲到撞击阈值以上(比如20m/s²),就认为检测到了撞击,状态进入"撞击态"。如果在窗口内没有等到撞击,说明这次只是短暂的失重,状态回到空闲态。

进入撞击态之后,接下来要做的是"静止确认"。因为人真的跌倒后通常会有短暂的静止或微弱移动,而误报动作往往在撞击之后立刻结束。我设定的确认逻辑是:在撞击发生后的约3秒内,如果合加速度连续稳定在9.8m/s²附近(小于2m/s²的波动),并且这个状态保持1秒左右,才判定为一次真正需要报警的跌倒。

这个三段式逻辑看起来简单,但实际效果比我用过的很多复杂机器学习模型都要稳定。原因在于它抓住了跌倒动作的物理本质,而不是单纯拟合数据。

2.3 阈值参数不是拍脑袋定的

初始阈值我给出了一组参考值:

参数初始值说明
失重阈值合加速度 < 2.5 m/s²明显低于静止重力值,确认进入失重窗口
撞击阈值合加速度 > 20 m/s²约为重力加速度的2倍,排除普通挥手等动作
失重到撞击窗口1.5 秒从失重开始计算,超时未撞击则放弃
静止确认波动< 2.0 m/s²撞击后约3秒内,合加速度波动范围
静止保持时间≥ 1 秒确认人在撞击后没有立刻站起

这些值不是凭空定的。失重阈值取2.5,是因为正常行走时合加速度虽然会波动,但很少连续降到这个水平;弯腰捡东西时合加速度确实会下降,但一般不会跌破2.5并持续足够长时间。撞击阈值取20,是因为人在正常活动尤其是慢速活动中,合加速度峰值很少超过这个值,而硬质地面上的跌倒撞击几乎必然超过它。

不过我必须说明:这些参数只是起点,不同人群、不同使用方式都需要调整。比如腿脚不便的老人走路姿态更不稳定,撞击阈值可能需要下调到18甚至16;年轻人在实验垫上做模拟跌倒,峰值往往更大,阈值可以上调降低误报。参数必须做成外部可配置,不要写死在代码里。

3. 从传感器读数到报警短信:核心代码落地

3.1 工程准备与权限声明

这个原型项目的技术栈用的是原生安卓开发,Java语言,没有引入第三方SDK,核心依赖就是系统自带的传感器API、定位API和短信API。工程目录我就不展开了,直接说关键配置。

先在AndroidManifest里声明权限:

<uses-permission android:name="android.permission.SENSOR" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.VIBRATE" />

注意,SENSOR和VIBRATE属于普通权限,安装即授予。SEND_SMS、定位、通知属于动态权限,需要在App启动时逐项申请。定位权限建议同时申请精确和模糊两项,并且在前台服务里声明定位用途,否则部分系统版本会直接拦截前台服务启动。

权限这块有个容易踩的坑:很多国产定制系统里,SMS权限即使你动态申请了,系统默认也可能不弹窗,而是把选项埋在设置里的"应用权限管理"中。测试时如果发现短信发不出去,先检查这里。

3.2 采样率选型和后台线程

传感器监听的采样率直接决定识别效果和功耗。我测试过几种模式之后,选了SensorManager.SENSOR_DELAY_GAME,这个档位大约每20到50毫秒返回一次数据,已经足够捕获跌倒的失重和撞击尖峰。SENSOR_DELAY_UI大约60毫秒一次,对瞬时撞击峰值的捕捉不够稳,容易漏掉。SENSOR_DELAY_FASTEST虽然最灵敏,但功耗明显高,长时间挂后台会让手机发烫,没必要。

另一个细节是传感器回调默认跑在主线程,数据密集时容易卡顿,所以我推荐用Handler把回调指定到后台线程:

HandlerThread sensorThread = new HandlerThread("sensor-thread"); sensorThread.start(); Handler sensorHandler = new Handler(sensorThread.getLooper()); sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME, sensorHandler);

这样做的好处是,即便传感器数据再多,也不会干扰界面刷新。

3.3 状态机代码骨架

下面这段是我实际跑通的简化版代码,去掉了日志和部分边界处理,保留了核心判定流程:

private static final int STATE_IDLE = 0; private static final int STATE_FREE_FALL = 1; private static final int STATE_IMPACT = 2; private int state = STATE_IDLE; private long freeFallStartTime = 0; private long impactTime = 0; private static final float FREE_FALL_THRESHOLD = 2.5f; private static final float IMPACT_THRESHOLD = 20.0f; private static final long FALL_TO_IMPACT_WINDOW_MS = 1500; private static final long STILL_CONFIRM_WINDOW_MS = 3000; @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { return; } float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; double magnitude = Math.sqrt(x * x + y * y + z * z); long now = System.currentTimeMillis(); switch (state) { case STATE_IDLE: if (magnitude < FREE_FALL_THRESHOLD) { state = STATE_FREE_FALL; freeFallStartTime = now; } break; case STATE_FREE_FALL: if (magnitude > IMPACT_THRESHOLD) { state = STATE_IMPACT; impactTime = now; break; } if (now - freeFallStartTime > FALL_TO_IMPACT_WINDOW_MS) { state = STATE_IDLE; } break; case STATE_IMPACT: if (now - impactTime > STILL_CONFIRM_WINDOW_MS) { state = STATE_IDLE; } else if (isStillConfirmed(magnitude, now)) { triggerAlert(); state = STATE_IDLE; } break; } }

isStillConfirmed的判断逻辑,是在撞击之后的3秒窗口内,统计合加速度保持在9.8 ± 2.0范围内的时间,连续超过1秒就返回true。这个函数内部要维护一个时间累加器,我实际实现时用了一个简单的差值计数器,每来一条数据就判断当前读数是否在静止区间,是则累加,否则清零。

这里要特别提醒:判定逻辑一定要用时间戳和窗口,不要用"连续几帧"来判断,因为不同手机的采样率差异很大,同一段动作在不同设备上帧数差好几倍。

3.4 警报链路与误触取消机制

检测到真实跌倒后,直接发短信是最直观的通知方式,但流程不能一步走完。我在触发警报前先开启一次高音量警告音和振动,同时在屏幕上弹出倒计时,默认10秒内如果用户主动点了"我没事",就取消警报。这个设计非常有必要,因为算法再精确也会有误报,宁可让用户多按一次取消按钮,也不要让家属收到虚假的紧急短信。

倒计时结束后才真正执行警报逻辑,核心两步是获取位置和发送短信:

private void sendAlertMessage() { LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); Location location = null; if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location == null) { location = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } } String fallbackLocation = "未获取到定位"; if (location != null) { fallbackLocation = "纬度:" + location.getLatitude() + ",经度:" + location.getLongitude(); } String message = "【跌倒警报】我可能跌倒了,当前定位:" + fallbackLocation + ",请尽快确认我的情况。"; SmsManager smsManager = SmsManager.getDefault(); smsManager.sendTextMessage(emergencyPhone, null, message, null, null); }

这里有个很大的工程教训:GPS首次定位很慢,如果跌倒后才去等卫星信号,可能要几十秒甚至更久,警报短信就失去意义。所以我在前台服务里用一个定时任务,每60秒更新一次最近已知位置缓存到内存和本地SharedPreferences,警报时直接读取缓存,几乎零延迟。

4. 误报控制与功耗:被真实使用逼出来的优化

4.1 复盘那些典型的误报动作

第一版原型跑通后,我和几个同事在办公室做了大量日常动作测试,结果误报率让人头大。归纳起来,最常见的几类误报各有各的原因。

弯腰捡东西:合加速度确实会掉到很低,甚至接近失重状态,但由于弯腰动作很快,失重持续时间非常短,而且紧接着的"撞击"往往是手碰到地面,撞击幅值远小于真正的跌倒。这类误报在加入"失重到撞击窗口"判断后被过滤掉了大部分。

快速坐下:人坐下瞬间,臀部撞到椅子会产生一个明显的加速度尖峰,有时能超过20m/s²。但它的问题在于失重阶段不明显,身体从站立到坐下的垂直速度变化比跌倒小得多,合加速度不会跌到2.5以下。所以状态机根本不会进入失重态,直接挡住。

把手机拍在桌上:这个动作的撞击峰值又高又陡,但完全没有前置失重阶段,属于典型的"孤立尖峰",同样被状态机挡掉。而且撞击之后合加速度迅速回到9.8附近并很快稳定,不存在"长时间静止异常"的确认条件。

逐步排除误报的过程让我意识到,三段式序列判断比任何单点阈值都可靠得多。它本质上是在问三个问题:有没有失重?失重后有没有撞击?撞完之后人有没有动?三个答案都是"是",才值得报警。

4.2 动态基线与个性化参数

阈值参数虽然能挡住大多数误报,但不同用户的身体状态差异很大。后来我加了一个动态基线校准机制:用户首次开启应用后的前10秒,App进入静息采集模式,统计当前三轴合加速度的平均值,比如有的老人身体前倾坐着,静息值可能不是标准的9.8,而是9.3,那么后续的失重阈值和静止区间就基于这个动态基线计算。

这个机制特别重要。静止确认阶段判断"合加速度是否在9.8附近",如果用户手机放在裤兜里微微倾斜,合加速度本来就是10.1,用固定9.8判断就容易漏报。动态基线解决的就是这类真实佩戴差异。

同时我把所有阈值参数放到了设置页,用可拖动的滑块暴露出来,用户可以根据自己的行动能力调节检测灵敏度。比如行动迟缓的老人,平时走路合加速度波动本来就小,可以把撞击阈值调低;平时动作幅度大的用户,可以调高阈值减少误报。参数设置界面不大,但这个设计在整个项目验收时帮了大忙,测试人员可以实时调参而不用反复改代码重新编译。

4.3 长期待机的省电策略

传感器长时间高频采样,耗电是客观存在的。如果一直用SENSOR_DELAY_GAME挂后台,一部手机的续航会明显缩短,老年人往往又忘记充电,这会反过来导致设备关机,报警功能失效。

我的省电方案是"级联采样"。前台服务里默认用SENSOR_DELAY_NORMAL(约200毫秒一次),这个频率日常检测不到跌倒尖峰,但能感知到"运动能量急剧变化"这类粗粒度事件。当连续检测到合加速度突然下降或上升超过某个粗阈值时,才动态把采样频率切换到SENSOR_DELAY_GAME,持续几秒后如果没有确认跌倒,再自动降回低频率。

用这个策略,待机功耗大概降低了60%到70%,同时检测延迟没有明显恶化。因为在真正的跌倒动作里,失重阶段持续200到400毫秒,即便低频率采样漏掉了开头,高频采样的切换动作依然来得及捕捉后续的撞击尖峰和静止阶段。

还有一个系统层面的坑:安卓高版本的系统资源回收策略很激进,如果应用没有开启前台服务,灭屏几分钟后传感器就会被冻结。所以检测服务必须以前台服务方式运行,并在通知栏常驻一个不起眼的"守护中"通知。部分国产定制系统还会在后台管理界面里默认限制应用的后台运行,用户需要在电池优化白名单里手动把本应用加入,否则一切白搭。

5. 真机测试方法与我踩过的几个坑

5.1 怎么用"演戏"的方式测跌倒

算法调完参数,必须进行真机测试。我管这个方法叫"演员测试法":需要一个人反复模拟各种跌倒姿势,另外一个人记录每次测试的判定结果。

测试动作清单我整理成了一张表:

测试动作预期结果备注
正向平地滑倒报警重点测试标准场景
侧向摔倒报警撞击方向不同,合加速度仍应触发
缓慢下蹲后坐下不报警误报高危场景
弯腰捡手机不报警模拟日常捡物
快速坐下不报警办公室高频动作
手机从桌上摔落不报警设备本体跌倒但人无事
拍打手机不报警模拟碰撞外来物
跌倒后立刻爬起不报警或延迟报警报告先不生效,可手动解除

测试场地一定要铺厚软垫,别直接在硬地上真摔。我亲眼见过测试时把腰闪了的情况,做健康应用的人自己先受工伤,说出去都丢人。

每轮测试不要只看报不报警,还要把传感器日志打出来,记录触发那几秒钟的合加速度波形,方便复盘哪一段特征不满足预设条件。没有日志的误报排查等于瞎猜。

5.2 真机环境下的典型问题

测试阶段踩了几个坑,每个都值得单独说。

第一个是定位冷启动太慢。最初版本在触发警报时才去请求定位,实际上跌倒报警触发后,GPS首次定位通常需要几十秒,短信已经发出去了,家属收到的却是"未获取到定位"的占位文案。后来改成了每60秒缓存一次最近位置,才解决了这个问题。

第二个是SMS权限在不同定制系统上的表现不一致。部分定制系统里,即使你在代码里申请了发送短信权限,用户在权限弹窗里点了允许,实际发送时系统也可能静默拦截。这个问题我排查了很久,最后发现需要在系统设置的应用权限管理中手动开启"发送短信"选项。所以我在应用内做了一个权限自检引导页,首次启动时逐项检查SENSOR、定位、短信、通知是否真正可用,并给出跳转系统设置页的按钮。

第三个问题是连续报警风暴。如果用户跌倒后身体还在轻微移动,手机可能会连续触发多次"失重-撞击"判断,导致家属在一分钟内收到好几条报警短信。我加了一个冷却机制:一次真正触发报警后,5分钟内不再重复发送短信,只更新通知栏状态。这个机制在真实风险事件中也能避免冗余信息干扰,让家属更快聚焦第一条报警的位置和内容。

5.3 权限与隐私的合规处理

传感器本身不涉及隐私问题,但定位和短信都是敏感能力,必须慎重设计。我在做这个应用时坚持三个原则。

第一,权限最小化。只有用户明确在设置页里填写了紧急联系人电话,并且主动打开了"跌倒检测"开关,我才会申请定位和短信权限。应用不会在启动时把所有权限全部拉满,而是按功能使用路径逐步申请。

第二,数据最小化。本地只保存最近几秒钟的传感器数据和最近一次定位缓存,用于算法判定和日志回看,不做长期行为轨迹记录,更不会上传任何服务器。短信内容仅包含定位信息和一句固定求助语,不携带手机上其他任何用户数据。

第三,知情与撤回。设置页里要明确说明每一类权限的用途。用户关闭跌倒检测开关后,前台服务和定位缓存立即停止更新,已存储的缓存数据也要提供一键清空入口。合规处理看似增加工作量,但对健康类应用来说,这是上线前的底线,用户对这类应用的安全感比功能丰富度更敏感。

6. 这个原型还能往哪些方向扩展

原型稳定运行之后,我琢磨过不少扩展方向,如果是在团队项目里继续往下走,有几条路线相当值得尝试。

把检测结果同步到可穿戴设备。现在很多人有手环或手表,这些设备本身也有加速度计,如果手机能通过低功耗蓝牙把手环的传感器数据也纳入算法,那就能覆盖"手机离身"的盲区,比如用户去洗澡时把手机放在客厅,手环仍可以独立检测跌倒并通过手机发送警报。

引入机器学习分类器。纯规则状态机在可解释性方面很强,误报也好排查,但对复杂动作的区分能力有上限。要是数据量够,可以采集大量跌倒与非跌倒样本,训练一个轻量分类器,把合加速度之外的旋转角速度、姿态变化速率作为补充特征。我的经验是:先把规则状态机跑稳,再决定要不要叠加模型,直接上模型反而会因为数据质量差而难以调优。

现有的三段式状态机逻辑也可以移植到其他健康场景,比如癫痫发作、突然晕厥等异常状态识别。这类动作同样有"失去控制-撞击-异常静止"的物理特征,只是阈值和窗口参数不同。换一个场景,往往只是换一组参数的问题。

我也尝试过把算法嵌入到老人常用的健康管理类应用里,作为后台守护模块。因为这套检测逻辑不需要常驻高功耗,平时几乎零干扰,很适合作为通用健康API提供出去,让更多应用在不开发底层传感器逻辑的前提下获得跌倒检测能力。

最后说点个人体会。我在项目里调误报参数那阵子,最深的感触是:算法不能只对着实验室数据调,得跟着真人测试走。第一次联调时我在软垫上模拟跌倒,把阈值调到太灵敏,结果从床上坐起来都触发报警,家属收到好几次假消息,那叫一个尴尬。后来我养成了两个习惯:一是所有参数做成可调并保留本地日志,二是在设置界面里加了一个"检测灵敏度"选项,让使用者根据自己的状态调整。这些小功能不复杂,但决定了这个原型能不能真的被长期使用。做健康监护类应用,说到底拼的不是某一版算法有多惊艳,而是它在真实琐碎的生活里能不能稳定当好那个不添乱的守护者。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询