Платформа 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. | мкТл |
SensorEvent.values[1] |
Напряженность геомагнитного поля по оси Y. | ||
SensorEvent.values[2] |
Напряженность геомагнитного поля по оси Z. | ||
TYPE_MAGNETIC_FIELD_UNCALIBRATED |
SensorEvent.values[0] |
Напряженность геомагнитного поля (без калибровки по твердому железу) по оси X. | мкТл |
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) или более поздней версии, частота запросов к этому датчику ограничена.
Этот датчик предоставляет необработанные данные о напряженности поля (в мкТл) для каждой из трех осей координат.
Обычно использовать этот датчик напрямую не требуется. Вместо этого вы можете использовать датчик вектора вращения, чтобы определить необработанное вращательное движение, или вы можете использовать акселерометр и датчик геомагнитного поля в сочетании с методом getRotationMatrix(), чтобы получить матрицу вращения и матрицу наклона. Затем эти матрицы можно использовать с методами getOrientation() и getInclination(), чтобы получить данные об азимуте и геомагнитном наклонении.
Примечание. При тестировании приложения вы можете повысить точность датчика, помахав устройством в форме восьмерки.
Использовать некалиброванный магнитометр
Неоткалиброванный магнитометр похож на датчик геомагнитного поля, но не калибруется по жесткому железу. Заводская калибровка и температурная компенсация по-прежнему применяются к магнитному полю. Некалиброванный магнитометр полезен для обработки неверных оценок жесткого железа. В целом 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().