Android 平台提供了两种传感器,可用于确定设备的位置:地磁场传感器和加速度计。Android 平台还提供了一种传感器,可用于确定设备正面与物体之间的距离(称为近程传感器)。地磁场传感器和近程传感器是基于硬件的传感器。大多数手机和平板电脑制造商都会添加地磁场传感器。同样,手机制造商通常会添加近程传感器,以确定手机何时被用户拿在靠近脸部的位置(例如,在通话期间)。如需确定设备的方向,您可以使用设备加速度计和地磁场传感器的读数。
注意:方向传感器已在 Android 2.2(API 级别 8)中弃用,方向传感器类型已在 Android 4.4W(API 级别 20)中弃用。
位置传感器可用于确定设备在世界参照系中的物理位置。例如,您可以将地磁场传感器与加速度计结合使用,以确定设备相对于磁北极的位置。您还可以使用这些传感器来确定设备在应用参考框架中的方向。 位置传感器通常不用于监控设备移动或运动,例如摇晃、倾斜或推力(如需了解详情,请参阅移动传感器)。
地磁场传感器和加速度计会为每个 SensorEvent
返回传感器值的多维数组。例如,地磁场传感器会在单个传感器事件期间为每个坐标轴提供地磁场强度值。同样,加速度计传感器会测量传感器事件期间施加到设备的加速度。如需详细了解传感器使用的坐标系,请参阅
传感器坐标系。近程传感器会为每个传感器事件提供一个值。表 1 总结了 Android 平台支持的位置传感器。
表 1. Android 平台支持的位置传感器。
传感器 | 传感器事件数据 | 说明 | 度量单位 |
---|---|---|---|
TYPE_GAME_ROTATION_VECTOR |
SensorEvent.values[0] |
沿 x 轴的旋转矢量分量 (x * sin(θ/2))。 | 无单位 |
SensorEvent.values[1] |
沿 y 轴的旋转矢量分量 (y * sin(θ/2))。 | ||
SensorEvent.values[2] |
沿 z 轴的旋转矢量分量 (z * sin(θ/2))。 | ||
TYPE_GEOMAGNETIC_ROTATION_VECTOR |
SensorEvent.values[0] |
沿 x 轴的旋转矢量分量 (x * sin(θ/2))。 | 无单位 |
SensorEvent.values[1] |
沿 y 轴的旋转矢量分量 (y * sin(θ/2))。 | ||
SensorEvent.values[2] |
沿 z 轴的旋转矢量分量 (z * sin(θ/2))。 | ||
TYPE_MAGNETIC_FIELD |
SensorEvent.values[0] |
沿 x 轴的地磁场强度。 | μT |
SensorEvent.values[1] |
沿 y 轴的地磁场强度。 | ||
SensorEvent.values[2] |
沿 z 轴的地磁场强度。 | ||
TYPE_MAGNETIC_FIELD_UNCALIBRATED |
SensorEvent.values[0] |
沿 x 轴的地磁场强度(无硬铁校准功能)。 | μT |
SensorEvent.values[1] |
沿 y 轴的地磁场强度(无硬铁校准功能)。 | ||
SensorEvent.values[2] |
沿 z 轴的地磁场强度(无硬铁校准功能)。 | ||
SensorEvent.values[3] |
沿 x 轴的铁偏差估算。 | ||
SensorEvent.values[4] |
沿 y 轴的铁偏差估算。 | ||
SensorEvent.values[5] |
沿 z 轴的铁偏差估算。 | ||
TYPE_ORIENTATION 1 |
SensorEvent.values[0] |
方位角(绕 z 轴的角度)。 | 度数 |
SensorEvent.values[1] |
俯仰角(绕 x 轴的角度)。 | ||
SensorEvent.values[2] |
倾侧角(绕 y 轴的角度)。 | ||
TYPE_PROXIMITY |
SensorEvent.values[0] |
与对象的距离。2 | 厘米 |
1此传感器已在 Android 2.2(API 级别 8)中弃用,此传感器类型已在 Android 4.4W(API 级别 20)中弃用。 传感器框架提供了用于获取设备方向的替代方法,这些方法将在计算设备的屏幕方向中进行讨论。
2 有些近程传感器仅提供表示“近”和“远”的二元值。
使用游戏旋转矢量传感器
游戏旋转矢量传感器与旋转矢量传感器相同,只是它不使用地磁场。因此,Y 轴不指向北,而是指向其他参照坐标。该参考坐标可以偏移与陀螺仪沿 Z 轴偏移时的幅度相同数量级的幅度。
由于游戏旋转矢量传感器不使用磁场,因此相对旋转更准确,且不受磁场变化的影响。如果您不关心北方在哪里,并且由于普通旋转矢量依赖于磁场而无法满足您的需求,请在游戏中使用此传感器。
以下代码展示了如何获取默认游戏旋转矢量传感器的实例:
Kotlin
private lateinit var sensorManager: SensorManager private var sensor: Sensor? = null ... sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
使用地磁旋转矢量传感器
地磁旋转矢量传感器与旋转矢量传感器类似,但它不使用陀螺仪。此传感器的精确度低于正常的旋转矢量传感器,但功耗有所降低。如果您想在后台收集旋转信息,但又不想消耗太多电池电量,请使用此传感器。此传感器与批处理结合使用时效果最佳。
以下代码展示了如何获取默认地磁旋转矢量传感器的实例:
Kotlin
private lateinit var sensorManager: SensorManager private var sensor: Sensor? = null ... sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);
计算设备的屏幕方向
通过计算设备的朝向,您可以监控设备相对于地球参考系(具体而言,是磁北极)的位置。以下代码展示了如何计算设备的屏幕方向:
Kotlin
private lateinit var sensorManager: SensorManager ... // Rotation matrix based on current readings from accelerometer and magnetometer. val rotationMatrix = FloatArray(9) SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading) // Express the updated rotation matrix as three orientation angles. val orientationAngles = FloatArray(3) SensorManager.getOrientation(rotationMatrix, orientationAngles)
Java
private SensorManager sensorManager; ... // Rotation matrix based on current readings from accelerometer and magnetometer. final float[] rotationMatrix = new float[9]; SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading); // Express the updated rotation matrix as three orientation angles. final float[] orientationAngles = new float[3]; SensorManager.getOrientation(rotationMatrix, orientationAngles);
系统通过将设备的地磁场传感器与设备的加速度计结合使用来计算方向角。系统使用这两个硬件传感器来提供以下三个方向角度的数据:
- 方位角(绕 -z 轴的旋转角度)。这是设备当前罗盘方向与磁北之间的角度。 如果设备的顶部边缘朝向磁北,方位角为 0 度;如果顶部边缘朝向正南,方位角为 180 度。同样,如果顶部边缘朝向东,方位角为 90 度;如果顶部边缘朝向西,方位角为 270 度。
- 俯仰角(绕 x 轴旋转的角度,以度为单位)。这是与设备屏幕平行的平面和与地面平行的平面之间的角度。如果您将设备与地面平行放置,使底部边缘最靠近您,然后将设备的顶部边缘朝向地面倾斜,则俯仰角变为正值。向相反方向倾斜(即让设备的顶部边缘远离地面)会导致俯仰角变为负值。值的范围为 -90 度到 90 度。
- 横滚(绕 y 轴旋转的角度,以度为单位)。这是垂直于设备屏幕的平面与垂直于地面的平面之间的角度。如果您将设备与地面平行放置,使底部边缘最靠近您,然后将设备的左侧边缘向地面倾斜,则横滚角变为正值。向相反方向倾斜(即向地面移动设备的右边缘)会导致横滚角变为负值。值的范围为 -180 度到 180 度。
注意:传感器的横滚定义已更改,以反映地理位置传感器生态系统中的绝大多数实现。
请注意,这些角度所基于的坐标系与航空中使用的坐标系(用于偏航、俯仰和滚动)不同。在航空系统中,x 轴沿飞机长边延伸,从机尾到机头。
方向传感器通过处理来自加速度计和地磁场传感器的原始传感器数据来获取数据。由于涉及大量处理,方向传感器的准确度和精确度会降低。具体而言,仅当横滚角为 0 时,此传感器才可靠。因此,方向传感器已在 Android 2.2(API 级别 8)中弃用,而方向传感器类型已在 Android 4.4W(API 级别 20)中弃用。
我们建议您结合使用 getRotationMatrix()
方法和 getOrientation()
方法来计算方向值,而不是使用方向传感器的原始数据,如以下代码示例所示。在此过程中,您可以使用 remapCoordinateSystem()
方法将方向值转换为应用的参考系。
Kotlin
class SensorActivity : Activity(), SensorEventListener { private lateinit var sensorManager: SensorManager private val accelerometerReading = FloatArray(3) private val magnetometerReading = FloatArray(3) private val rotationMatrix = FloatArray(9) private val orientationAngles = FloatArray(3) public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { // Do something here if sensor accuracy changes. // You must implement this callback in your code. } override fun onResume() { super.onResume() // Get updates from the accelerometer and magnetometer at a constant rate. // To make batch operations more efficient and reduce power consumption, // provide support for delaying updates to the application. // // In this example, the sensor reporting delay is small enough such that // the application receives an update before the system checks the sensor // readings again. sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer -> sensorManager.registerListener( this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI ) } sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField -> sensorManager.registerListener( this, magneticField, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI ) } } override fun onPause() { super.onPause() // Don't receive any more updates from either sensor. sensorManager.unregisterListener(this) } // Get readings from accelerometer and magnetometer. To simplify calculations, // consider storing these readings as unit vectors. override fun onSensorChanged(event: SensorEvent) { if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size) } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size) } } // Compute the three orientation angles based on the most recent readings from // the device's accelerometer and magnetometer. fun updateOrientationAngles() { // Update rotation matrix, which is needed to update orientation angles. SensorManager.getRotationMatrix( rotationMatrix, null, accelerometerReading, magnetometerReading ) // "rotationMatrix" now has up-to-date information. SensorManager.getOrientation(rotationMatrix, orientationAngles) // "orientationAngles" now has up-to-date information. } }
Java
public class SensorActivity extends Activity implements SensorEventListener { private SensorManager sensorManager; private final float[] accelerometerReading = new float[3]; private final float[] magnetometerReading = new float[3]; private final float[] rotationMatrix = new float[9]; private final float[] orientationAngles = new float[3]; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // Do something here if sensor accuracy changes. // You must implement this callback in your code. } @Override protected void onResume() { super.onResume(); // Get updates from the accelerometer and magnetometer at a constant rate. // To make batch operations more efficient and reduce power consumption, // provide support for delaying updates to the application. // // In this example, the sensor reporting delay is small enough such that // the application receives an update before the system checks the sensor // readings again. Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (accelerometer != null) { sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI); } Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); if (magneticField != null) { sensorManager.registerListener(this, magneticField, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI); } } @Override protected void onPause() { super.onPause(); // Don't receive any more updates from either sensor. sensorManager.unregisterListener(this); } // Get readings from accelerometer and magnetometer. To simplify calculations, // consider storing these readings as unit vectors. @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.length); } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.length); } } // Compute the three orientation angles based on the most recent readings from // the device's accelerometer and magnetometer. public void updateOrientationAngles() { // Update rotation matrix, which is needed to update orientation angles. SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading); // "rotationMatrix" now has up-to-date information. SensorManager.getOrientation(rotationMatrix, orientationAngles); // "orientationAngles" now has up-to-date information. } }
除了将传感器的坐标系转换为应用的参考框架之外,您通常不需要对设备的原始方向角度执行任何数据处理或过滤操作。
使用地磁场传感器
借助地磁场传感器,您可以监控地球磁场的变化。以下代码展示了如何获取默认地磁场传感器的实例:
Kotlin
private lateinit var sensorManager: SensorManager private var sensor: Sensor? = null ... sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
注意: 如果您的应用以 Android 12(API 级别 31)或更高版本为目标平台,则此传感器会受到速率限制。
此传感器可提供三个坐标轴中每个轴的原始磁场强度数据(以微特斯拉为单位)。
通常,您不需要直接使用此传感器。您可以改用旋转矢量传感器来确定原始旋转运动,也可以结合使用加速度计和地磁场传感器与 getRotationMatrix()
方法来获取旋转矩阵和倾角矩阵。然后,您可以将这些矩阵与 getOrientation()
和 getInclination()
方法搭配使用,以获取方位角和地磁倾角数据。
注意: 在测试应用时,您可以挥动设备,使其呈“8”字形,从而提高传感器的精确度。
使用未经校准的磁力计
未校准磁力计与地磁场传感器类似,只是未对磁场应用硬铁校准。出厂校准和温度补偿仍会应用于磁场。未校准的磁力计有助于处理不良的硬铁估算。一般来说,geomagneticsensor_event.values[0]
将接近 uncalibrated_magnetometer_event.values[0] -
uncalibrated_magnetometer_event.values[3]
。即,
calibrated_x ~= uncalibrated_x - bias_estimate_x
注意:未校准传感器可提供更多的原始结果,可能包括一些偏差,但其测量结果包含校准后更正结果中的少数“激增”值。一些应用可能更倾向于使用这些未校准结果,因为此类结果更流畅、可靠。例如,如果应用试图自己进行传感器融合,则引入校准可能会使结果失真。
除了磁场之外,未校准的磁力计还提供每个轴上的估计硬铁偏差。以下代码展示了如何获取默认的未校准磁力计的实例:
Kotlin
private lateinit var sensorManager: SensorManager private var sensor: Sensor? = null ... sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
使用近程传感器
借助近程传感器,您可以确定物体与设备的距离。以下代码展示了如何获取默认的近程传感器实例:
Kotlin
private lateinit var sensorManager: SensorManager private var sensor: Sensor? = null ... sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
近程传感器通常用于确定人的头部与手机设备正面之间的距离(例如,当用户拨打或接听电话时)。大多数近程传感器会返回以厘米为单位的绝对距离,但有些传感器仅返回“近”和“远”值。
注意:在某些设备型号上,近程传感器位于屏幕下方,如果启用该传感器时屏幕处于开启状态,则可能会导致屏幕上出现闪烁的点。
以下代码展示如何使用近程传感器:
Kotlin
class SensorActivity : Activity(), SensorEventListener { private lateinit var sensorManager: SensorManager private var proximity: Sensor? = null public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) // Get an instance of the sensor service, and use that to get an instance of // a particular sensor. sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { // Do something here if sensor accuracy changes. } override fun onSensorChanged(event: SensorEvent) { val distance = event.values[0] // Do something with this sensor data. } override fun onResume() { // Register a listener for the sensor. super.onResume() proximity?.also { proximity -> sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL) } } override fun onPause() { // Be sure to unregister the sensor when the activity pauses. super.onPause() sensorManager.unregisterListener(this) } }
Java
public class SensorActivity extends Activity implements SensorEventListener { private SensorManager sensorManager; private Sensor proximity; @Override public final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Get an instance of the sensor service, and use that to get an instance of // a particular sensor. sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); } @Override public final void onAccuracyChanged(Sensor sensor, int accuracy) { // Do something here if sensor accuracy changes. } @Override public final void onSensorChanged(SensorEvent event) { float distance = event.values[0]; // Do something with this sensor data. } @Override protected void onResume() { // Register a listener for the sensor. super.onResume(); sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL); } @Override protected void onPause() { // Be sure to unregister the sensor when the activity pauses. super.onPause(); sensorManager.unregisterListener(this); } }
注意:有些近程传感器会返回表示“近”或“远”的二元值。在这种情况下,传感器通常在“远”状态下报告最大范围值,而在“近”状态下报告较小的值。通常,远距离值大于 5 厘米,但具体值因传感器而异。您可以使用 getMaximumRange()
方法确定传感器的最大量程。