センサを使ってAndroid端末の傾きを知る


Androidではたくさんのセンサ・デバイスが搭載されています。
機種によって、センサの種類は異なりますが、今回は最もよく使われるセンサの一つ、傾き(方向)センサの使い方を解説します。

Androidのセンサは以下の通り、SensorクラスのConstantとして定めています。

センサ一覧(センサクラスの定数)

定数 説明
TYPE_ACCELEROMETER 加速度センサ
TYPE_ALL 全部のセンサを指定
TYPE_GYROSCOPE ジャイロスコープ
TYPE_LIGHT 照度センサ
TYPE_MAGNETIC_FIELD 地磁気センサ
TYPE_ORIENTATION 傾きセンサ(非推奨定数)
TYPE_PRESSURE 加圧センサ
TYPE_PROXIMITY 接近センサ
TYPE_TEMPERATURE 温度センサ


TYPE_ORIENTATIONが非推奨となっており、今後のバージョンアップを考えると、いままでと同じ使い勝手で傾きを求めることは遠慮した方がよさそうです。

傾き導出方法(従来)

もちろん非推奨ではありますが、今まで通りの方法でも、傾き(方向)を求めることが出来ます。
以下は、従来方式のサンプルです。

List<Sensor> sensors = SensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
Sensor sensor = sensors.get(0);
SensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_NORMAL);
…
public void onSensorChanged(SensorEvent event) {
  if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
     (処理など)... event.values[0];
  }
}

Android 2.2推奨の傾き取得方法

続きではAndroid2.2で推奨されているgetOrientationメソッドをつかった傾きセンサ値の取得方法を説明します。

Froyoでは、傾き(方向)は地磁気センサと加速度センサの合成として扱います。
開発者側でのコード記述量は増えますが、より柔軟な設計が可能になります。

※後述する内容でセンサ値を求める際に回転行列を使いますが、TYPE_ORIENTATIONの傾き検出と
違って、OpenGL/ESなど、より複雑なシステムでそのまま使えるようになり、応用性が確保されました。

手順は以下の通りです

  1. Manifestの変更
  2. センサ・マネージャの取得
  3. イベントリスナーの登録・解除処理
  4. SensorEventListenerによるセンサ値の取得

準備:Manifestの変更

このあとのサンプルコードはHT-03AやXperiaなど実機で動きを確認しながらのほうが、分り易いでしょう。

予防的にAndroidManifest.xmlに縦画面固定の属性を追加します。
手元のデバイスの角度を色々変えていると意図せずActivity画面が回転してしまう場合があるためです。

        <activity android:name=".orientationSensorActivity"
                  android:label="@string/app_name"
                  android:screenOrientation="portrait">

3行目 portraitを指定してサンプルコードでは縦画面固定にしてください。

センサ・マネージャの取得、イベントリスナーの登録

	private SensorManager mSensorManager;

	@Override
	public void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.main);

	    /* センサ・マネージャを取得する */
	    mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
	}

getSystemServiceメソッドでセンサ・マネージャを取得します。
センサマネージャは、センサ制御のためのクラスです。

イベントリスナーの登録はonResumeメソッドで実施します。また、リスナーの登録と解除は対で行う必要があります。

	private boolean mIsMagSensor;
	private boolean mIsAccSensor;

	@Override
	protected void onResume() {
		// TODO 自動生成されたメソッド・スタブ
		super.onResume();

		// センサの取得
        List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);

	    // センサマネージャへリスナーを登録(implements SensorEventListenerにより、thisで登録する)
        for (Sensor sensor : sensors) {

        	if( sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
        		mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
        		mIsMagSensor = true;
        	}

        	if( sensor.getType() == Sensor.TYPE_ACCELEROMETER){
        		mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
        		mIsAccSensor = true;
        	}
        }
}

13、18行目で、センサ・マネージャへセンサ値を受け取るイベントリスナー(コールバック)を登録しています。
registerListenerの第3引数はセンサ感度を示しています(以下の4種類が用意されています)

  • SENSOR_DELAY_FASTEST 最高速でのセンサ読み出し
  • SENSOR_DELAY_GAME 高速ゲーム向け
  • SENSOR_DELAY_NORMAL 通常モード
  • SENSOR_DELAY_UI 低速。ユーザインターフェイス向け

# 通常の用途ではDELAY_UIで問題ない感度が得られると思います。

イベントリスナの解除タイミング

イベントリスナは、onPauseのタイミングで解除します。

	@Override
	protected void onPause() {
		// TODO 自動生成されたメソッド・スタブ
		super.onPause();

		//センサーマネージャのリスナ登録破棄
	    if (mIsMagSensor || mIsAccSensor) {
	        mSensorManager.unregisterListener(this);
	        mIsMagSensor = false;
	        mIsAccSensor = false;
	    }
	}

onPauseメソッドをイベントリスナの解除に利用する理由は、Activityのライフサイクルにあります。アプリ終了時に必ずonPauseを通るためです。万一アプリ終了時にイベントリスナの解除が漏れてしまうと、センサは常時つけっぱなしになり、急速にバッテリを消費することになります。
無駄な消費を防ぐためにも、センサ・デバイスの利用(コールバック登録~解除区間)は最小限に抑える必要があります。

センサ取得(SensorEventListener)の内部処理

public class orientationSensorActivity extends Activity  implements SensorEventListener{
    (省略)
    private static final int MATRIX_SIZE = 16;
	/* 回転行列 */
    float[]  inR = new float[MATRIX_SIZE];
    float[] outR = new float[MATRIX_SIZE];
    float[]    I = new float[MATRIX_SIZE];

    /* センサーの値 */
    float[] orientationValues   = new float[3];
    float[] magneticValues      = new float[3];
    float[] accelerometerValues = new float[3];
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO 自動生成されたメソッド・スタブ
	}

SensorEventListenerでは、必須なのでonAccuracyChangedメソッドを追加します。
センサーの精度が変更されたときに呼び出されるイベントリスナですが、今回は使わないのでStubです。

	public void onSensorChanged(SensorEvent event) {
           if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) return;

		switch (event.sensor.getType()) {
			case Sensor.TYPE_MAGNETIC_FIELD:
				magneticValues = event.values.clone();
				break;
			case Sensor.TYPE_ACCELEROMETER:
				accelerometerValues = event.values.clone();
                                break;
		}

		if (magneticValues != null && accelerometerValues != null) {

			SensorManager.getRotationMatrix(inR, I, accelerometerValues, magneticValues);

			//Activityの表示が縦固定の場合。横向きになる場合、修正が必要です
			SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);
			SensorManager.getOrientation(outR, orientationValues);

			Log.v("Orientation",
	            String.valueOf( radianToDegree(orientationValues[0]) ) + ", " + //Z軸方向,azimuth
	            String.valueOf( radianToDegree(orientationValues[1]) ) + ", " + //X軸方向,pitch
   	            String.valueOf( radianToDegree(orientationValues[2]) ) );       //Y軸方向,roll
		}
	}

	int radianToDegree(float rad){
		return (int) Math.floor( Math.toDegrees(rad) ) ;
	}

onSensorChangedは、センサーの値が変更されたときに呼ばれるメソッドです。
傾きの値は、地磁気センサと加速度センサの合成です(19行目) 。両方のセンサ値がとれるまでは処理に入りません(14行目)

もっとも大事なポイントはgetOrientationメソッドの前後です。

			SensorManager.getRotationMatrix(inR, I, accelerometerValues, magneticValues);

			//Activityの表示が縦固定の場合。横向きになる場合、修正が必要です
			SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);
			SensorManager.getOrientation(outR, orientationValues);

getRotationMatrixメソッドで、地磁気センサと加速度センサの値から、回転行列inR, Iを作成します。
inR、Iは現在の内部状態を示します。
remapCoordinateSystemメソッドでは内部状態inRを元に、システムに合った座標軸系へ行列変換(outR)しています。

最後のgetOrientationメソッドでは傾き情報として、
Z軸方向の方位、X軸方向のpitch、Y軸方向のrollを得ることになります。

6 Comments