Android 平台提供了几种传感器,可让您监控设备的运动。
传感器的可能架构因传感器类型而异
- 重力、线性加速度、旋转矢量、显著运动、步数计数器和步数检测器传感器是基于硬件的或基于软件的。
- 加速度计和陀螺仪传感器始终是基于硬件的。
大多数支持 Android 的设备都配有加速度计,许多设备现在还包括陀螺仪。基于软件的传感器的可用性变化更大,因为它们通常依赖一个或多个硬件传感器来获取数据。根据设备的不同,这些基于软件的传感器可以从加速度计和磁力计或陀螺仪获取数据。
运动传感器对于监控设备运动非常有用,例如倾斜、摇动、旋转或摆动。运动通常是直接用户输入的反映(例如,用户在游戏中驾驶汽车或用户在游戏中控制球),但它也可以反映设备所在的物理环境(例如,在您驾驶汽车时随您移动)。在第一种情况下,您正在监控相对于设备参考系或应用程序参考系的运动;在第二种情况下,您正在监控相对于世界参考系的运动。运动传感器本身通常不用于监控设备位置,但可以与其他传感器(如地磁场传感器)一起使用来确定设备相对于世界参考系的位置(有关更多信息,请参阅 位置传感器)。
所有运动传感器都针对每个 SensorEvent
返回传感器值的多分量数组。例如,在单个传感器事件期间,加速度计将针对三个坐标轴返回加速度力数据,而陀螺仪将针对三个坐标轴返回旋转速率数据。这些数据值以 float
数组 (values
) 的形式返回,以及其他 SensorEvent
参数。表 1 总结了 Android 平台上可用的运动传感器。
传感器 | 传感器事件数据 | 描述 | 度量单位 |
---|---|---|---|
TYPE_ACCELEROMETER |
SensorEvent.values[0] |
沿 x 轴的加速度力(包括重力)。 | m/s2 |
SensorEvent.values[1] |
沿 y 轴的加速度力(包括重力)。 | ||
SensorEvent.values[2] |
沿 z 轴的加速度力(包括重力)。 | ||
TYPE_ACCELEROMETER_UNCALIBRATED |
SensorEvent.values[0] |
沿 X 轴的测量加速度,没有任何偏差补偿。 | m/s2 |
SensorEvent.values[1] |
沿 Y 轴的测量加速度,没有任何偏差补偿。 | ||
SensorEvent.values[2] |
沿 Z 轴的测量加速度,没有任何偏差补偿。 | ||
SensorEvent.values[3] |
沿 X 轴的测量加速度,估计有偏差补偿。 | ||
SensorEvent.values[4] |
沿 Y 轴的测量加速度,估计有偏差补偿。 | ||
SensorEvent.values[5] |
沿 Z 轴的测量加速度,估计有偏差补偿。 | ||
TYPE_GRAVITY |
SensorEvent.values[0] |
沿 x 轴的重力。 | m/s2 |
SensorEvent.values[1] |
沿 y 轴的重力。 | ||
SensorEvent.values[2] |
沿 z 轴的重力。 | ||
TYPE_GYROSCOPE |
SensorEvent.values[0] |
绕 x 轴的旋转速率。 | rad/s |
SensorEvent.values[1] |
绕 y 轴的旋转速率。 | ||
SensorEvent.values[2] |
绕 z 轴的旋转速率。 | ||
TYPE_GYROSCOPE_UNCALIBRATED |
SensorEvent.values[0] |
绕 x 轴的旋转速率(没有漂移补偿)。 | rad/s |
SensorEvent.values[1] |
绕 y 轴的旋转速率(没有漂移补偿)。 | ||
SensorEvent.values[2] |
绕 z 轴的旋转速率(没有漂移补偿)。 | ||
SensorEvent.values[3] |
绕 x 轴的估计漂移。 | ||
SensorEvent.values[4] |
绕 y 轴的估计漂移。 | ||
SensorEvent.values[5] |
绕 z 轴的估计漂移。 | ||
TYPE_LINEAR_ACCELERATION |
SensorEvent.values[0] |
沿 x 轴的加速度力(不包括重力)。 | m/s2 |
SensorEvent.values[1] |
沿 y 轴的加速度力(不包括重力)。 | ||
SensorEvent.values[2] |
沿 z 轴的加速度力(不包括重力)。 | ||
TYPE_ROTATION_VECTOR |
SensorEvent.values[0] |
沿 x 轴的旋转矢量分量 (x * sin(θ/2))。 | 无量纲 |
SensorEvent.values[1] |
沿 y 轴的旋转矢量分量 (y * sin(θ/2))。 | ||
SensorEvent.values[2] |
沿 z 轴的旋转矢量分量 (z * sin(θ/2))。 | ||
SensorEvent.values[3] |
旋转矢量的标量分量 ((cos(θ/2)).1 | ||
TYPE_SIGNIFICANT_MOTION |
N/A | N/A | N/A |
TYPE_STEP_COUNTER |
SensorEvent.values[0] |
自上次重启后,传感器激活期间用户采取的步数。 | 步数 |
TYPE_STEP_DETECTOR |
N/A | N/A | N/A |
1 标量分量是可选值。
旋转矢量传感器和重力传感器是用于运动检测和监控的最常用的传感器。旋转矢量传感器特别通用,可用于各种与运动相关的任务,例如检测手势、监控角度变化以及监控相对方向变化。例如,如果您正在开发游戏、增强现实应用程序、二维或三维指南针或相机稳定应用程序,则旋转矢量传感器是理想选择。在大多数情况下,使用这些传感器比使用加速度计和地磁场传感器或方向传感器更好。
Android 开源项目传感器
Android 开源项目 (AOSP) 提供三种基于软件的运动传感器:重力传感器、线性加速度传感器和旋转矢量传感器。这些传感器已在 Android 4.0 中更新,现在使用设备的陀螺仪(除了其他传感器)来提高稳定性和性能。如果您想尝试这些传感器,可以使用 getVendor()
方法和 getVersion()
方法来识别它们(供应商为 Google LLC;版本号为 3)。通过供应商和版本号识别这些传感器是必要的,因为 Android 系统将这三种传感器视为辅助传感器。例如,如果设备制造商提供他们自己的重力传感器,则 AOSP 重力传感器将显示为辅助重力传感器。这三种传感器都依赖于陀螺仪:如果设备没有陀螺仪,则这些传感器不会显示,也不能使用。
使用重力传感器
重力传感器提供一个三维向量,指示重力的方向和大小。通常,此传感器用于确定设备在空间中的相对方向。以下代码展示了如何获取默认重力传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
单位与加速度传感器使用的单位相同(m/s2),坐标系与加速度传感器使用的坐标系相同。
注意:当设备处于静止状态时,重力传感器的输出应与加速度计的输出相同。
使用线性加速度计
线性加速度传感器为您提供一个三维向量,表示沿每个设备轴的加速度,不包括重力。您可以使用此值执行手势检测。该值还可以用作惯性导航系统的输入,该系统使用航位推算。以下代码展示了如何获取默认线性加速度传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
从概念上讲,此传感器根据以下关系为您提供加速度数据
linear acceleration = acceleration - acceleration due to gravity
当您想要获取不受重力影响的加速度数据时,通常会使用此传感器。例如,您可以使用此传感器查看您的汽车行驶速度。线性加速度传感器始终存在偏移量,您需要将其删除。最简单的方法是在您的应用程序中构建一个校准步骤。在校准期间,您可以让用户将设备放在桌子上,然后读取所有三个轴的偏移量。然后,您可以从加速度传感器的直接读数中减去该偏移量,以获得实际的线性加速度。
传感器 坐标系 与加速度传感器使用的坐标系相同,度量单位(m/s2)也是如此。
使用旋转矢量传感器
旋转矢量表示设备的方向,作为角度和轴的组合,其中设备绕轴(x、y 或 z)旋转了 θ 角。以下代码展示了如何获取默认旋转矢量传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
旋转矢量的三个元素表示如下
其中旋转矢量的大小等于 sin(θ/2),旋转矢量的方向等于旋转轴的方向。
旋转矢量的三个元素等于单位四元数(cos(θ/2),x*sin(θ/2),y*sin(θ/2),z*sin(θ/2))的最后三个分量。旋转矢量的元素是无量纲的。x、y 和 z 轴的定义方式与加速度传感器相同。参考坐标系定义为直接正交基(见图 1)。该坐标系具有以下特征
- X 定义为向量积 Y x Z。它在设备当前位置的接地处切线,并指向大约东部。
- Y 在设备当前位置的接地处切线,并指向地磁北极。
- Z 指向天空,垂直于地面平面。
有关展示如何使用旋转矢量传感器的示例应用程序,请参阅 RotationVectorDemo.java。
使用重要运动传感器
每次检测到重要运动时,重要运动传感器都会触发一个事件,然后它会禁用自身。重要运动是指可能导致用户位置发生变化的运动;例如步行、骑自行车或坐在行驶的汽车中。以下代码展示了如何获取默认重要运动传感器的实例以及如何注册事件侦听器
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val mSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION) val triggerEventListener = object : TriggerEventListener() { override fun onTrigger(event: TriggerEvent?) { // Do work } } mSensor?.also { sensor -> sensorManager.requestTriggerSensor(triggerEventListener, sensor) }
Java
private SensorManager sensorManager; private Sensor sensor; private TriggerEventListener triggerEventListener; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); triggerEventListener = new TriggerEventListener() { @Override public void onTrigger(TriggerEvent event) { // Do work } }; sensorManager.requestTriggerSensor(triggerEventListener, mSensor);
有关更多信息,请参阅 TriggerEventListener
。
使用步数计数器传感器
步数计数器传感器提供自上次重启后传感器激活期间用户采取的步数。步数计数器比步数检测器传感器具有更高的延迟(高达 10 秒),但精度更高。
注意:为了让您的应用程序在运行 Android 10(API 级别 29)或更高版本的设备上使用此传感器,您必须声明 ACTIVITY_RECOGNITION
权限。
以下代码展示了如何获取默认步数计数器传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
为了在运行您的应用程序的设备上节省电池电量,您应该使用 JobScheduler
类以特定间隔从步数计数器传感器检索当前值。尽管不同类型的应用程序需要不同的传感器读取间隔,但您应该尽可能地延长此间隔,除非您的应用程序需要来自传感器的实时数据。
使用步数检测器传感器
每次用户迈出一步时,步数检测器传感器都会触发一个事件。延迟预计低于 2 秒。
注意:为了让您的应用程序在运行 Android 10(API 级别 29)或更高版本的设备上使用此传感器,您必须声明 ACTIVITY_RECOGNITION
权限。
以下代码展示了如何获取默认步数检测器传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
使用原始数据
以下传感器为您的应用程序提供有关施加到设备的线性和旋转力的原始数据。为了有效地使用来自这些传感器的值,您需要从环境中过滤掉因素,例如重力。您可能还需要对值的趋势应用平滑算法以减少噪声。
使用加速度计
加速度传感器测量施加到设备的加速度,包括重力。以下代码展示了如何获取默认加速度传感器的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
注意:如果您的应用程序的目标是 Android 12(API 级别 31)或更高版本,则此传感器 速率受限。
从概念上讲,加速度传感器通过测量施加到传感器本身的力 (Fs) 来确定施加到设备的加速度 (Ad),使用以下关系
但是,重力始终会根据以下关系影响测量的加速度
因此,当设备放在桌子上(并且没有加速)时,加速度计读取的大小为 g = 9.81 m/s2。类似地,当设备处于自由落体并且因此以 9.81 m/s2 的速度快速加速朝向地面时,其加速度计读取的大小为 g = 0 m/s2。因此,为了测量设备的实际加速度,必须从加速度计数据中去除重力的贡献。这可以通过应用高通滤波器来实现。相反,低通滤波器可用于隔离重力。以下示例展示了如何执行此操作
Kotlin
override fun onSensorChanged(event: SensorEvent) { // In this example, alpha is calculated as t / (t + dT), // where t is the low-pass filter's time-constant and // dT is the event delivery rate. val alpha: Float = 0.8f // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0] gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1] gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2] // Remove the gravity contribution with the high-pass filter. linear_acceleration[0] = event.values[0] - gravity[0] linear_acceleration[1] = event.values[1] - gravity[1] linear_acceleration[2] = event.values[2] - gravity[2] }
Java
public void onSensorChanged(SensorEvent event){ // In this example, alpha is calculated as t / (t + dT), // where t is the low-pass filter's time-constant and // dT is the event delivery rate. final float alpha = 0.8; // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; // Remove the gravity contribution with the high-pass filter. linear_acceleration[0] = event.values[0] - gravity[0]; linear_acceleration[1] = event.values[1] - gravity[1]; linear_acceleration[2] = event.values[2] - gravity[2]; }
注意:您可以使用许多不同的技术来过滤传感器数据。上面的代码示例使用简单的滤波器常数(alpha)来创建低通滤波器。该滤波器常数来自时间常数 (t),它是滤波器添加到传感器事件的延迟的粗略表示,以及传感器的事件传递速率 (dt)。代码示例使用 0.8 的 alpha 值用于演示目的。如果您使用此过滤方法,您可能需要选择不同的 alpha 值。
加速度计使用标准传感器 坐标系。实际上,这意味着当设备以其自然方向平放在桌子上时,以下条件适用
- 如果您将设备推向左侧(使其向右移动),则 x 加速度值将为正。
- 如果您将设备推向底部(使其远离您),则 y 加速度值将为正。
- 如果您将设备以 A m/s2 的加速度推向天空,则 z 加速度值将等于 A + 9.81,这对应于设备的加速度 (+A m/s2) 减去重力 (-9.81 m/s2)。
- 静止设备将具有 +9.81 的加速度值,这对应于设备的加速度(0 m/s2 减去重力,即 -9.81 m/s2)。
一般而言,如果您要监控设备运动,加速度计是一个很好的传感器。几乎每部运行 Android 的手机和平板电脑都配备了加速度计,并且它比其他运动传感器消耗的功率少 10 倍。一个缺点是您可能需要实现低通和高通滤波器以消除重力并减少噪声。
使用陀螺仪
陀螺仪测量设备绕其 x、y 和 z 轴的旋转速率(以 rad/s 为单位)。以下代码展示了如何获取默认陀螺仪的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
注意:如果您的应用程序的目标是 Android 12(API 级别 31)或更高版本,则此传感器 速率受限。
传感器的 坐标系 与加速度传感器使用的坐标系相同。旋转在逆时针方向为正;也就是说,从 x、y 或 z 轴上某个正位置观察位于原点的设备的观察者,如果设备看起来在逆时针旋转,则会报告正旋转。这是正旋转的标准数学定义,与方向传感器使用的滚动定义不同。
通常,陀螺仪的输出会随时间积分,以计算旋转,描述角度在时间步长内的变化。例如
Kotlin
// Create a constant to convert nanoseconds to seconds. private val NS2S = 1.0f / 1000000000.0f private val deltaRotationVector = FloatArray(4) { 0f } private var timestamp: Float = 0f override fun onSensorChanged(event: SensorEvent?) { // This timestep's delta rotation to be multiplied by the current rotation // after computing it from the gyro sample data. if (timestamp != 0f && event != null) { val dT = (event.timestamp - timestamp) * NS2S // Axis of the rotation sample, not normalized yet. var axisX: Float = event.values[0] var axisY: Float = event.values[1] var axisZ: Float = event.values[2] // Calculate the angular speed of the sample val omegaMagnitude: Float = sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ) // Normalize the rotation vector if it's big enough to get the axis // (that is, EPSILON should represent your maximum allowable margin of error) if (omegaMagnitude > EPSILON) { axisX /= omegaMagnitude axisY /= omegaMagnitude axisZ /= omegaMagnitude } // Integrate around this axis with the angular speed by the timestep // in order to get a delta rotation from this sample over the timestep // We will convert this axis-angle representation of the delta rotation // into a quaternion before turning it into the rotation matrix. val thetaOverTwo: Float = omegaMagnitude * dT / 2.0f val sinThetaOverTwo: Float = sin(thetaOverTwo) val cosThetaOverTwo: Float = cos(thetaOverTwo) deltaRotationVector[0] = sinThetaOverTwo * axisX deltaRotationVector[1] = sinThetaOverTwo * axisY deltaRotationVector[2] = sinThetaOverTwo * axisZ deltaRotationVector[3] = cosThetaOverTwo } timestamp = event?.timestamp?.toFloat() ?: 0f val deltaRotationMatrix = FloatArray(9) { 0f } SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); // User code should concatenate the delta rotation we computed with the current rotation // in order to get the updated rotation. // rotationCurrent = rotationCurrent * deltaRotationMatrix; }
Java
// Create a constant to convert nanoseconds to seconds. private static final float NS2S = 1.0f / 1000000000.0f; private final float[] deltaRotationVector = new float[4](); private float timestamp; public void onSensorChanged(SensorEvent event) { // This timestep's delta rotation to be multiplied by the current rotation // after computing it from the gyro sample data. if (timestamp != 0) { final float dT = (event.timestamp - timestamp) * NS2S; // Axis of the rotation sample, not normalized yet. float axisX = event.values[0]; float axisY = event.values[1]; float axisZ = event.values[2]; // Calculate the angular speed of the sample float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ); // Normalize the rotation vector if it's big enough to get the axis // (that is, EPSILON should represent your maximum allowable margin of error) if (omegaMagnitude > EPSILON) { axisX /= omegaMagnitude; axisY /= omegaMagnitude; axisZ /= omegaMagnitude; } // Integrate around this axis with the angular speed by the timestep // in order to get a delta rotation from this sample over the timestep // We will convert this axis-angle representation of the delta rotation // into a quaternion before turning it into the rotation matrix. float thetaOverTwo = omegaMagnitude * dT / 2.0f; float sinThetaOverTwo = sin(thetaOverTwo); float cosThetaOverTwo = cos(thetaOverTwo); deltaRotationVector[0] = sinThetaOverTwo * axisX; deltaRotationVector[1] = sinThetaOverTwo * axisY; deltaRotationVector[2] = sinThetaOverTwo * axisZ; deltaRotationVector[3] = cosThetaOverTwo; } timestamp = event.timestamp; float[] deltaRotationMatrix = new float[9]; SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); // User code should concatenate the delta rotation we computed with the current rotation // in order to get the updated rotation. // rotationCurrent = rotationCurrent * deltaRotationMatrix; }
标准陀螺仪提供原始旋转数据,没有任何滤波或噪声和漂移(偏差)校正。在实践中,陀螺仪噪声和漂移会引入需要补偿的误差。您通常通过监控其他传感器(如重力传感器或加速度计)来确定漂移(偏差)和噪声。
使用未校准的陀螺仪
未校准的陀螺仪类似于 陀螺仪,只是没有对旋转速率应用陀螺仪漂移补偿。工厂校准和温度补偿仍然应用于旋转速率。未校准的陀螺仪对于后期处理和融合方向数据很有用。通常,gyroscope_event.values[0]
将接近于 uncalibrated_gyroscope_event.values[0] - uncalibrated_gyroscope_event.values[3]
。也就是说,
calibrated_x ~= uncalibrated_x - bias_estimate_x
注意: 未校准的传感器提供更原始的结果,可能包含一些偏差,但其测量值包含较少的校准应用校正造成的跳跃。某些应用程序可能更喜欢这些未校准的结果,因为它们更平滑、更可靠。例如,如果应用程序试图进行自己的传感器融合,引入校准实际上会扭曲结果。
除了旋转速率外,未校准的陀螺仪还提供每个轴周围的估计漂移。以下代码向您展示如何获取默认未校准陀螺仪的实例
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
其他代码示例
该 BatchStepSensor 示例进一步演示了此页面上介绍的 API 的使用。