位置センサー

Android プラットフォームには、デバイスの位置を特定できる 2 つのセンサー(地磁気センサーと加速度計)が用意されています。Android プラットフォームには、デバイスの前面が物体にどれだけ近づいているかを判断できるセンサー(近接センサー)も用意されています。地磁気センサーと近接センサーはハードウェア ベースです。ほとんどのスマートフォンやタブレットのメーカーは、地磁気センサーを搭載しています。同様に、スマートフォン メーカーは通常、スマートフォンがユーザーの顔に近づいたとき(通話中など)を判断するために近接センサーを搭載しています。デバイスの向きを特定するには、デバイスの加速度計と地磁気センサーの測定値を使用します。

注: 向きセンサーは Android 2.2(API レベル 8)で非推奨になり、向きセンサータイプは Android 4.4W(API レベル 20)で非推奨になりました。

位置センサーは、世界座標系におけるデバイスの物理的な位置を特定するのに役立ちます。たとえば、地磁気センサーを加速度計と組み合わせて使用すると、磁北に対するデバイスの位置を特定できます。これらのセンサーを使用して、アプリの基準座標系におけるデバイスの向きを特定することもできます。位置センサーは通常、デバイスの動きやモーション(揺れ、傾き、推力など)をモニタリングするために使用されることはありません(詳しくは、モーション センサーをご覧ください)。

地磁気センサーと加速度計は、SensorEvent ごとにセンサー値の多次元配列を返します。たとえば、地磁気センサーは、1 回のセンサー イベント中に 3 つの座標軸のそれぞれについて地磁気強度値を提供します。同様に、加速度センサーは、センサー イベント中にデバイスに適用された加速度を測定します。センサーで使用される座標系の詳細については、 センサー座標系をご覧ください。近接センサーは、センサー イベントごとに 1 つの値を返します。表 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 cm

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

このシステムは、デバイスの地磁気センサーと加速度計を組み合わせて使用​​することで、方位角を計算します。システムは、これら 2 つのハードウェア センサーを使用して、次の 3 つの向きの角度のデータを提供します。

  • 方位角(-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)以降をターゲットとしている場合、このセンサーはレート制限されます。

このセンサーは、3 つの座標軸のそれぞれについて、未加工の電界強度データ(μT 単位)を提供します。通常、このセンサーを直接使用する必要はありません。代わりに、回転ベクトル センサーを使用して未加工の回転運動を特定するか、加速度計と地磁気センサーを 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);

近接センサーは通常、ユーザーの頭部がハンドセット デバイスの前面からどの程度離れているかを判断するために使用されます(ユーザーが電話をかけたり受けたりしているときなど)。ほとんどの近接センサーは絶対距離を cm 単位で返しますが、一部のセンサーは近接値と遠隔値のみを返します。

注: 一部のデバイスモデルでは、近接センサーが画面の下に配置されているため、画面がオンのときに近接センサーが有効になると、画面に点滅するドットが表示されることがあります。

次のコードは、近接センサーの使用方法を示しています。

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

注: 一部の近接センサーは、「近」または「遠」を表すバイナリ値を返します。この場合、センサーは通常、遠い状態では最大範囲値を報告し、近い状態ではより小さい値を報告します。通常、far 値は 5 cm を超える値ですが、センサーによって異なる場合があります。センサーの最大範囲は、getMaximumRange() メソッドを使用して特定できます。

関連ドキュメント