如何获取加速度计、陀螺仪和磁力计的数据?它们的单位是什么?

解读

国内面试中,这道题考察的是“传感器框架”这一基础但高频的模块。面试官想确认三件事:

  1. 你是否真的在真机上用过 SensorManager,而不是只看过文档;
  2. 你是否理解三种物理量的工程含义,避免把“加速度 9.8”当成异常值;
  3. 你是否具备“采样率+功耗”意识,因为国内厂商对后台功耗极度敏感,直接决定你的代码能否过 CTS 和厂商功耗测试。

因此,回答时要给出“注册—采样—换算—释放”完整闭环,并主动把单位、量程、坐标系、采样率、功耗、后台限制这些点一次讲清,体现“工程落地”经验。

知识点

  1. SensorManager 获取与注册:

    • getSystemService(Context.SENSOR_SERVICE)registerListener(listener, sensor, rateUs, handler)
    • 采样率等级:SENSOR_DELAY_FASTEST / GAME / UI / NORMAL,对应 0 μs、20 000 μs、60 000 μs、200 000 μs,实际值由 HAL 层和厂商配置决定。
  2. 三种传感器类型:

    • TYPE_ACCELEROMETER:硬件加速度计,单位 m/s²,含重力分量。
    • TYPE_GYROSCOPE:硬件陀螺仪,单位 rad/s,逆时针为正,遵循右手定则。
    • TYPE_MAGNETIC_FIELD:硬件磁力计,单位 μT(微特斯拉),1 μT = 0.01 G(高斯)。
  3. 事件回调:

    • onSensorChanged(SensorEvent event):values[0~2] 分别对应 X/Y/Z 轴,坐标系与手机自然方向绑定,与屏幕旋转无关。
    • onAccuracyChanged(Sensor sensor, int accuracy):ACCURACY_HIGH / MEDIUM / LOW / UNRELIABLE,国内部分机型在金属环境会频繁回调 UNRELIABLE,需做降级处理。
  4. 后台限制(国内重点):

    • Android 9 以后后台应用无法接收 ≥200 Hz 连续采样;
    • 部分厂商(华为、OPPO)在息屏 5 min 后强制降频到 5 Hz,甚至直接 unregister;
    • 若需高频率实时数据,必须前台 Service + 可见通知,否则会被系统或管家杀掉。
  5. 资源释放:

    • onPause() / onDestroy() 中必须 unregisterListener,否则会被标记为“传感器泄露”,导致应用耗电异常告警,无法上架国内商店。
  6. 单位换算与校准:

    • 加速度:event.values[i] 直接就是 m/s²,重力分量 9.8 无需再乘;
    • 陀螺仪:rad/s 转 deg/s 乘 180/π;
    • 磁力计:μT 转 mT 乘 0.001,国内地图 SDK 一般要求 μT 级输入。

答案

第一步,获取系统服务并检查硬件是否存在:

val sm = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val acc = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
val gyr = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
val mag = sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
if (acc == null || gyr == null || mag == null) {
    // 国内低端机可能缺陀螺仪,需降级或提示用户
}

第二步,定义监听并注册,采样率根据业务选择:

val listener = object : SensorEventListener {
    override fun onSensorChanged(e: SensorEvent) {
        when (e.sensor.type) {
            Sensor.TYPE_ACCELEROMETER -> {
                val x = e.values[0]   // m/s²
                val y = e.values[1]
                val z = e.values[2]
            }
            Sensor.TYPE_GYROSCOPE -> {
                val wx = e.values[0]  // rad/s
                val wy = e.values[1]
                val wz = e.values[2]
            }
            Sensor.TYPE_MAGNETIC_FIELD -> {
                val bx = e.values[0]  // μT
                val by = e.values[1]
                val bz = e.values[2]
            }
        }
    }
    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        if (accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
            // 国内金属支架场景极易触发,需提示用户校准
        }
    }
}
// 注册:前台 UI 线程用 GAME 级别,后台高频率任务用 FASTEST + 前台 Service
sm.registerListener(listener, acc, SensorManager.SENSOR_DELAY_GAME)
sm.registerListener(listener, gyr, SensorManager.SENSOR_DELAY_GAME)
sm.registerListener(listener, mag, SensorManager.SENSOR_DELAY_GAME)

第三步,在生命周期结束时必须释放:

override fun onPause() {
    super.onPause()
    sm.unregisterListener(listener)
}

单位总结:
加速度计:m/s²(含重力 9.8);
陀螺仪:rad/s;
磁力计:μT。

拓展思考

  1. 虚拟传感器与融合:
    国内很多入门机砍掉陀螺仪,但系统仍提供 TYPE_GRAVITYTYPE_LINEAR_ACCELERATIONTYPE_ROTATION_VECTOR,底层通过加速度+磁力计+算法融合生成。面试时可主动说明“若硬件缺失,可降级到 ROTATION_VECTOR,但动态精度下降 30%,需业务侧容忍漂移”。

  2. 功耗实战:
    连续 200 Hz 采样在 4000 mAh 手机上每小时耗电约 6%,国内商店审核要求“后台传感器任务必须≤5 Hz”。可采取“退至后台自动降级 + 显著性检测(方差阈值)停采”策略,既保活又省电。

  3. 坐标系对齐:
    国内折叠屏和 Pad 越来越多,默认传感器坐标系基于自然方向,与屏幕旋转无关。若业务需要“屏幕坐标系”,必须结合 Display.getRotation() 做 4 组映射矩阵切换,否则会出现“横屏下左右颠倒”的客诉。

  4. 安全与隐私:
    2023 年起,国内主流厂商在隐私合规检测中把“传感器列表读取”列为高风险权限,需在隐私政策中声明用途。若用于摇一摇广告,还必须提供开关,否则无法通过华为、小米、OPPO 的上架扫描。