Androidのセンサーで気をつけるべきこと

「気をつけるべきこと」とか書きましたが、
ありていに言えば「自分がはまったのでくやしい!ぐぬぬ!」ってことですね。

ということでAndroidのセンサーから値を取得する上で、気をつけなきゃいけないこと書いておきます。


  1. SensorManager取得時に指定したセンサーのタイプを信用するな。
  2. SensorのResolutionはよくわからない。


具体的にどういうことかコードを見ていきましょう。

Androidでセンサーの値を取るときはこんな感じのコードを書きます。

@Override
void onCreate(Bundle savedInstanceState) {
    ...
    final SensorManager mgr = (SensorManager)getSystemService(SENSOR_SERVICE);
    final Sensor acc = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    mgr.registerListener(listener, acc, SensorManager.SENSOR_DELAY_NORMAL);
}

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        ...
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

このとき、SensorManagerに対して指定したセンサータイプのセンサのイベントが取れるようになります。
きっと取れると思います。取れるはずなんです。本当なら。
でも実はうまくいかないことがよくあります。
で、よくよく調べてみると、Android端末のいくつかでは指定したセンサータイプと、
getDefaultSensorで実際に取得できるセンサーの物理的な種類が一致していないことがわかりました。
そりゃうまく動かないわけですよ。
だって加速度センサの値が欲しいのに実際には磁気センサを取得してたりするんですから。


これをどうやって回避するかというと、実際に取得したいセンサに関連する他の論理センサも動作させるということを行います。
具体的なコードで説明をすると、

final Sensor acc = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mgr.registerListener(listener, acc, SensorManager.SENSOR_DELAY_NORMAL);

final Sensor ori = mgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);
mgr.registerListener(listener, ori, SensorManager.SENSOR_DELAY_NORMAL);

final Sensor mag = mgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mgr.registerListener(listener, mag, SensorManager.SENSOR_DELAY_NORMAL);

こんなふうにして関連するセンサを全部使用します。
後はイベントを受ける側で、どのイベントが来たかを振り分けてあげればうまくいきます。
(どうして加速度センサと磁気センサと傾きセンサが関連するかの説明は省略します)

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            ...
            break;
        }
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

次にSensorのResolutionですが、名前からして測定値を物理量に変換するのに使用するものだと思うじゃないですか普通は(たぶん普通の感覚だよね?)。
でもこれ、実は実装によってはただの固定値であまり意味のある値が入っていません。
Resolutionを使わなくても、普通に値を使えば物理量に単位変換された値が入っています。
Resolutionの単位も実装依存のため、何のために存在するかいまいちわかりません。
なので間違っても↓みたいなコードは書いちゃだめですよ!ダメ、ゼッタイ!

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        final float res = event.sensor.getResolution();
        switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            final float accX = event.value[0] * res;
            // do something
            ...
            break;
        }
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

↑こういうことしちゃダメですよ!