Sensores de posição

A plataforma Android oferece dois sensores que permitem determinar a posição de um dispositivo: o sensor de campo geomagnético e o acelerômetro. A plataforma Android também oferece um sensor que permite determinar a proximidade da face de um dispositivo em relação a um objeto (conhecido como sensor de proximidade). O sensor de campo geomagnético e o sensor de proximidade são baseados em hardware. A maioria dos fabricantes de smartphones e tablets inclui um sensor de campo geomagnético. Da mesma forma, os fabricantes de smartphones geralmente incluem um sensor de proximidade para determinar quando um smartphone está sendo mantido perto do rosto de um usuário (por exemplo, durante uma chamada). Para determinar a orientação de um dispositivo, você pode usar as leituras do acelerômetro e do sensor de campo geomagnético.

Observação:o sensor de orientação foi descontinuado no Android 2.2 (nível 8 da API), e o tipo de sensor de orientação foi descontinuado no Android 4.4W (nível 20 da API).

Os sensores de posição são úteis para determinar a posição física de um dispositivo no referencial mundial. Por exemplo, é possível usar o sensor de campo geomagnético em combinação com o acelerômetro para determinar a posição de um dispositivo em relação ao polo norte magnético. Você também pode usar esses sensores para determinar a orientação de um dispositivo no escopo de referência do aplicativo. Os sensores de posição geralmente não são usados para monitorar o movimento ou a movimentação do dispositivo, como agitação, inclinação ou impulso. Para mais informações, consulte Sensores de movimento.

O sensor de campo geomagnético e o acelerômetro retornam matrizes multidimensionais de valores de sensores para cada SensorEvent. Por exemplo, o sensor de campo geomagnético fornece valores de intensidade de campo geomagnético para cada um dos três eixos de coordenadas durante um único evento do sensor. Da mesma forma, o sensor de acelerômetro mede a aceleração aplicada ao dispositivo durante um evento do sensor. Para mais informações sobre os sistemas de coordenadas usados por sensores, consulte Sistemas de coordenadas do sensor. O sensor de proximidade fornece um único valor para cada evento do sensor. A Tabela 1 resume os sensores de posição compatíveis com a plataforma Android.

Tabela 1. Sensores de posição compatíveis com a plataforma Android.

Sensor Dados de eventos do sensor Descrição Unidades de medida
TYPE_GAME_ROTATION_VECTOR SensorEvent.values[0] Componente do vetor de rotação ao longo do eixo X (X * sen (θ / 2)). Sem unidade
SensorEvent.values[1] Componente do vetor de rotação ao longo do eixo Y (Y * sen (θ / 2)).
SensorEvent.values[2] Componente do vetor de rotação ao longo do eixo Z (Z * sen (θ / 2)).
TYPE_GEOMAGNETIC_ROTATION_VECTOR SensorEvent.values[0] Componente do vetor de rotação ao longo do eixo X (X * sen (θ / 2)). Sem unidade
SensorEvent.values[1] Componente do vetor de rotação ao longo do eixo Y (Y * sen (θ / 2)).
SensorEvent.values[2] Componente do vetor de rotação ao longo do eixo Z (Z * sen (θ / 2)).
TYPE_MAGNETIC_FIELD SensorEvent.values[0] Intensidade do campo geomagnético ao longo do eixo X. μT
SensorEvent.values[1] Intensidade do campo geomagnético ao longo do eixo Y.
SensorEvent.values[2] Intensidade do campo geomagnético ao longo do eixo Z.
TYPE_MAGNETIC_FIELD_UNCALIBRATED SensorEvent.values[0] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo X. μT
SensorEvent.values[1] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo Y.
SensorEvent.values[2] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo Z.
SensorEvent.values[3] Estimativa da polarização do ferro ao longo do eixo X.
SensorEvent.values[4] Estimativa da polarização do ferro ao longo do eixo Y.
SensorEvent.values[5] Estimativa da polarização do ferro ao longo do eixo Z.
TYPE_ORIENTATION1 SensorEvent.values[0] Azimute (ângulo ao redor do eixo Z). Graus
SensorEvent.values[1] Inclinação (ângulo ao redor do eixo X).
SensorEvent.values[2] Rolagem (ângulo ao redor do eixo Y).
TYPE_PROXIMITY SensorEvent.values[0] Distância do objeto.2 cm

1Esse sensor foi descontinuado no Android 2.2 (API nível 8) e no Android 4.4W (API nível 20). O framework de sensor oferece métodos alternativos para adquirir a orientação do dispositivo, que são discutidos em Determinar a orientação do dispositivo.

2 Alguns sensores de proximidade fornecem apenas valores binários que representam "perto" e "longe".

Usar o sensor vetorial de rotação para jogos

O sensor de vetor de rotação do jogo é idêntico ao sensor de vetor de rotação, exceto que não usa o campo geomagnético. Portanto, o eixo Y não aponta para o norte, mas para alguma outra referência. Essa referência pode variar pela mesma ordem de magnitude que o giroscópio varia ao redor do eixo Z.

Como o sensor de vetor de rotação do jogo não usa o campo magnético, as rotações relativas são mais precisas e não são afetadas por mudanças no campo magnético. Use esse sensor em um jogo se você não se importar com a direção do norte, e o vetor de rotação normal não se encaixar nas suas necessidades por causa da dependência do campo magnético.

O código a seguir mostra como receber uma instância do sensor de vetor de rotação padrão do jogo:

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);

Usar o sensor vetorial de rotação geomagnética

O sensor de vetor de rotação geomagnética é semelhante ao sensor de vetor de rotação, mas não usa o giroscópio. A precisão desse sensor é menor que a do sensor de vetor de rotação normal, mas o consumo de energia é reduzido. Use esse sensor apenas se você quiser coletar informações de rotação em segundo plano sem usar muita bateria. Esse sensor é mais útil quando usado em conjunto com o lote.

O código a seguir mostra como conseguir uma instância do sensor de vetor de rotação geomagnética padrão:

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);

Calcular a orientação do dispositivo

Ao calcular a orientação de um dispositivo, é possível monitorar a posição dele em relação ao referencial da Terra (especificamente, o polo norte magnético). O código a seguir mostra como calcular a orientação de um dispositivo:

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);

O sistema calcula os ângulos de orientação usando um sensor de campo geomagnético do dispositivo em combinação com o acelerômetro do dispositivo. Usando esses dois sensores de hardware, o sistema fornece dados para os três ângulos de orientação a seguir:

  • Azimute (graus de rotação em torno do eixo -z). Esse é o ângulo entre a direção atual da bússola do dispositivo e o norte magnético. Se a borda superior do dispositivo estiver voltada para o norte magnético, o azimute será de 0 graus. Se a borda superior estiver voltada para o sul, o azimute será de 180 graus. Da mesma forma, se a borda superior estiver voltada para o leste, o azimute será de 90 graus. Se a borda superior estiver voltada para o oeste, o azimute será de 270 graus.
  • Inclinação (graus de rotação em torno do eixo x). Esse é o ângulo entre um plano paralelo à tela do dispositivo e um plano paralelo ao solo. Se você segurar o dispositivo paralelo ao chão com a borda inferior mais próxima de você e inclinar a borda superior do dispositivo em direção ao chão, o ângulo de inclinação vai se tornar positivo. A inclinação na direção oposta, afastando a borda superior do dispositivo do chão, faz com que o ângulo de inclinação se torne negativo. O intervalo de valores é de -90 a 90 graus.
  • Inclinação (graus de rotação em torno do eixo y). Esse é o ângulo entre um plano perpendicular à tela do dispositivo e um plano perpendicular ao solo. Se você segurar o dispositivo paralelo ao chão com a borda inferior mais próxima de você e inclinar a borda esquerda do dispositivo em direção ao chão, o ângulo de inclinação vai se tornar positivo. Inclinar na direção oposta (mover a borda direita do dispositivo em direção ao chão) faz com que o ângulo de inclinação se torne negativo. O intervalo de valores é de -180 graus a 180 graus.

Observação:a definição do rolo do sensor foi alterada para refletir a grande maioria das implementações no ecossistema de geosensores.

Esses ângulos funcionam com um sistema de coordenadas diferente do usado na aviação (para guinada, inclinação e guinada). No sistema de aviação, o eixo X está ao longo do lado longo do avião, da cauda à proa.

O sensor de orientação extrai os dados processando os dados brutos do sensor do acelerômetro e do sensor de campo geomagnético. Devido ao processamento intenso envolvido, a precisão do sensor de orientação é diminuída. Especificamente, esse sensor é confiável apenas quando o ângulo de inclinação é 0. Como resultado, o sensor de orientação foi descontinuado no Android 2.2 (nível 8 da API) e o tipo de sensor de orientação foi descontinuado no Android 4.4W (nível 20 da API). Em vez de usar dados brutos do sensor de orientação, recomendamos que você use o método getRotationMatrix() com o método getOrientation() para calcular valores de orientação, conforme mostrado no exemplo de código abaixo. Como parte desse processo, é possível usar o método remapCoordinateSystem() para traduzir os valores de orientação para o frame de referência do app.

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.
    }
}

Geralmente, não é necessário realizar nenhum processamento ou filtragem de dados dos ângulos de orientação brutos do dispositivo, exceto a conversão do sistema de coordenadas do sensor para o sistema de referência do aplicativo.

Usar o sensor de campo geomagnético

O sensor de campo geomagnético permite monitorar mudanças no campo magnético da Terra. O código abaixo mostra como conseguir uma instância do sensor de campo geomagnético padrão:

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);

Observação : se o app for direcionado ao Android 12 (nível 31 da API) ou mais recente, esse sensor será limitado a uma taxa.

Esse sensor fornece dados brutos de intensidade de campo (em μT) para cada um dos três eixos de coordenadas. Normalmente, não é preciso usar esse sensor diretamente. Em vez disso, use o sensor de vetor de rotação para determinar o movimento de rotação bruto ou use o acelerômetro e o sensor de campo geomagnético em conjunto com o método getRotationMatrix() para receber a matriz de rotação e a matriz de inclinação. Em seguida, use essas matrizes com os métodos getOrientation() e getInclination() para receber dados de azimute e inclinação geomagnética.

Observação : ao testar o app, é possível melhorar a precisão do sensor balançando o dispositivo em um padrão em forma de 8.

Usar o magnetômetro sem calibração

O magnetômetro não calibrado é semelhante ao sensor de campo geomagnético, exceto pelo fato de que nenhuma calibração de ferro duro é aplicada ao campo magnético. A calibração de fábrica e a compensação de temperatura ainda são aplicadas ao campo magnético. O magnetômetro não calibrado é útil para lidar com estimativas de hardware ruins. Em geral, geomagneticsensor_event.values[0] vai estar próximo de uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3]. Ou seja,

calibrated_x ~= uncalibrated_x - bias_estimate_x

Observação:sensores não calibrados fornecem resultados mais brutos e podem incluir alguma tendência, mas as medições deles contêm menos saltos de correções aplicadas pela calibração. Alguns aplicativos podem preferir esses resultados não calibrados como mais suaves e confiáveis. Por exemplo, se um aplicativo tentar realizar a própria fusão de sensores, a introdução de calibrações pode distorcer os resultados.

Além do campo magnético, o magnetômetro não calibrado também fornece a inclinação de ferro duro estimada em cada eixo. O código a seguir mostra como conseguir uma instância do magnetômetro não calibrado padrão:

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);

Usar o sensor de proximidade

O sensor de proximidade permite determinar a que distância um objeto está de um dispositivo. O código a seguir mostra como conseguir uma instância do sensor de proximidade padrão:

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);

O sensor de proximidade geralmente é usado para determinar a distância da cabeça de uma pessoa em relação ao rosto de um dispositivo móvel (por exemplo, quando um usuário está fazendo ou recebendo uma ligação). A maioria dos sensores de proximidade retorna a distância absoluta, em cm, mas alguns retornam apenas valores próximos e longe.

Observação:em alguns modelos de dispositivo, o sensor de proximidade está localizado abaixo da tela, o que pode fazer com que um ponto piscante apareça na tela se ele estiver ativado enquanto a tela estiver ligada.

O código a seguir mostra como usar o sensor de proximidade:

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);
    }
}

Observação:alguns sensores de proximidade retornam valores binários que representam "perto" ou "longe". Nesse caso, o sensor geralmente informa o valor máximo do alcance no estado distante e um valor menor no estado próximo. Normalmente, o valor de longe é um valor > 5 cm, mas isso pode variar de acordo com o sensor. É possível determinar o alcance máximo de um sensor usando o método getMaximumRange().

Leia também