位置传感器

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_ORIENTATION1 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)或更高版本,则此传感器将受到速率限制

此传感器提供每个三个坐标轴的原始场强数据(以μT为单位)。通常,您不需要直接使用此传感器。相反,您可以使用旋转矢量传感器来确定原始旋转运动,或者您可以将加速度计和地磁场传感器与getRotationMatrix()方法结合使用以获取旋转矩阵和倾斜矩阵。然后,您可以将这些矩阵与getOrientation()getInclination()方法结合使用以获取方位角和地磁倾角数据。

注意:在测试您的应用时,您可以通过以8字形挥动设备来提高传感器的精度。

使用未校准的磁力计

未校准的磁力计类似于地磁场传感器,只是没有对磁场应用硬铁校准。工厂校准和温度补偿仍应用于磁场。未校准的磁力计有助于处理不良的硬铁估计。通常,geomagneticsensor_event.values[0]将接近uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3]。也就是说,

校准后的x ≈ 未校准后的x - 偏差估计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()方法确定传感器的最大范围。

您还应该阅读