13、自由落体检测:如何判断手机是否在掉落?

自由落体检测,听起来挺酷的对吧?其实在很多手机里,这个功能已经默默存在好多年了。比如你手机不小心掉下去,系统能识别出来,然后自动关闭屏幕或者触发保护机制。我最早接触这个场景,是在做一款老人机防摔应用的时候。

说白了,自由落体检测的核心就一句话:判断加速度是否接近零。嗯,你没听错,是接近零,而不是很大。

为什么自由落体时加速度是零?

你可能会觉得奇怪:东西往下掉,速度越来越快,加速度应该很大才对啊?

这里有个关键点要搞清楚。加速度传感器测量的是相对于自由落体的加速度,也就是所谓的「比力」。当手机静止放在桌上时,传感器感受到的是 1g 的重力加速度,方向向上。当手机自由落体时,它和重力一起往下掉,传感器就感受不到重力了,读数就变成了 0。

我在项目中遇到过有人问:「那手机掉下去的时候,传感器读数应该是 0 吗?」
答案是:理论上是的,但实际上会有一些噪声和偏差。

自由落体的数学特征

我们来看一下三轴加速度的合成值:

a_total = sqrt(ax² + ay² + az²)

正常情况下,手机静止时,a_total ≈ 9.8 m/s²(也就是 1g)。
自由落体时,a_total ≈ 0 m/s²。

所以检测逻辑其实很简单:

  • 如果 a_total 持续低于某个阈值(比如 0.5g),就认为手机在掉落
  • 如果持续时间足够长(比如 200ms 以上),就触发落体事件
关键参数:
  • 阈值:通常取 0.3g ~ 0.5g
  • 持续时间:100ms ~ 500ms 不等
  • 退出条件:a_total 恢复 > 0.8g 或检测到撞击

代码实现:一个简单的自由落体检测器

下面是我个人习惯用的一个检测类。它不复杂,但足够应对大多数场景。

public class FreeFallDetector {
    private static final float FREE_FALL_THRESHOLD = 3.0f; // m/s²,约 0.3g
    private static final long FREE_FALL_DURATION_MS = 200; // 持续 200ms 才算掉落

    private long fallStartTime = 0;
    private boolean isFalling = false;
    private OnFreeFallListener listener;

    public interface OnFreeFallListener {
        void onFreeFallStarted();
        void onFreeFallEnded();
    }

    public void onSensorChanged(SensorEvent event) {
        float ax = event.values[0];
        float ay = event.values[1];
        float az = event.values[2];

        double total = Math.sqrt(ax * ax + ay * ay + az * az);

        if (total < FREE_FALL_THRESHOLD) {
            if (!isFalling) {
                fallStartTime = System.currentTimeMillis();
                isFalling = true;
            } else {
                long elapsed = System.currentTimeMillis() - fallStartTime;
                if (elapsed >= FREE_FALL_DURATION_MS) {
                    if (listener != null) {
                        listener.onFreeFallStarted();
                    }
                }
            }
        } else {
            if (isFalling) {
                isFalling = false;
                if (listener != null) {
                    listener.onFreeFallEnded();
                }
            }
        }
    }
}
小提示:实际项目中,建议在 onFreeFallEnded 里检测是否发生了撞击。如果紧接着出现一个很大的加速度峰值(比如 > 5g),那基本就是摔地上了。

自由落体检测的完整流程

我画了一张流程图,帮你理清整个判断逻辑:

开始监听 读取三轴加速度 计算 a_total a_total < 阈值? 持续 >= 200ms? 触发落体 重置计时

避坑指南:我曾经踩过的几个坑

自由落体检测看起来简单,但实际做起来有不少细节。我把自己踩过的坑分享给你:

坑一:阈值设得太低
我曾经把阈值设成 0.1g,结果手机放在软沙发上稍微晃一下就触发落体。后来改成 0.3g,配合持续时间过滤,误报率才降下来。
坑二:没有考虑传感器噪声
加速度传感器本身有噪声,静止时读数也不是完美的 9.8。建议先做低通滤波,或者用滑动窗口平均,不然很容易误判。
坑三:持续时间太短
有人觉得 50ms 就够了,但实际测试发现,快速甩手也能让加速度短暂归零。我建议至少 150ms 以上,200ms 是比较稳妥的值。

如何区分自由落体和抛掷?

你想想看,手机被抛出去和自由落体,传感器读数其实很像。怎么区分呢?

我个人的做法是:

  • 看初始状态:自由落体前手机通常是静止的,抛掷前手机有向上的加速度
  • 看落体后的撞击:自由落体后通常有撞击,抛掷可能被接住
  • 看旋转:抛掷时手机往往有旋转,陀螺仪数据会有明显变化
特征 自由落体 抛掷
落体前加速度 ≈ 1g(静止) > 1g(向上加速)
落体后撞击 大概率有 可能没有
旋转角速度 通常较小 通常较大
实用建议:如果你只是做防摔保护,不用区分得太细。只要检测到落体,就触发保护动作(比如关闭屏幕、保存数据)。宁可误报,也不要漏报。

好了,自由落体检测的核心内容就这些。说白了就是:看合成加速度是否接近零,并且持续一段时间。代码实现也不复杂,关键是把阈值和持续时间调好。如果你在实际项目中遇到问题,欢迎随时交流。


公众号:蓝海资料掘金营,微信deep3321