Tips

Android センサー・フレームワーク 【Android TIPS】

Sensorオブジェクトの取得
次に、Sensorオブジェクトの取得を行います。このオブジェクトは、イベントリスナーの登録時にどのセンサーに対してなのか指定する際に必要となります。
Sensorオブジェクトを取得するには、SensorManagerオブジェクトのgetDefaultSensor()メソッドもしくはgetSensorList()メソッドを使用します。

getDefaultSensor()メソッドの引数にSensorクラスに定義されているTYPE_***定数を使用することで、指定した種類のSensorオブジェクトを取得することができます。以下の例では、MAGNETIC_FIELDすなわち地磁気センサーを指定しています。

Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

また、getSensorList()メソッドの場合も同じように引数にTYPE_***定数を使用します。

List<Sensor> deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

メソッドの戻り値のデータ型に注目してください。getSensorList()メソッドの戻り値はListクラスとなっています。引数にTYPE_ALLを渡すことでそのAndroid端末で使用可能な全てのセンサーを取得することが可能です。


Sensorクラスは各種センサー機器に共通する属性(名前、センサー種類、開発ベンダー、バージョン番号等)を管理するクラスでした。
このクラスには以下のようなメソッドが存在し、センサーに関する情報を取得することができます。

メソッドのシグネチャ 処理内容
public String getName() センサーの名前を取得する
public int getType() センサーのタイプを取得する
public String getVendor() センサーのベンダー名を取得する
public int getVersion() センサーのバージョンを取得する



次にSensorクラスに定義されている代表的な定数を紹介します。
各プラットフォーム毎に利用可能な(サポートされるようになった)センサーが違っているというところに注目してください。
非推奨となっているセンサーは下位互換のために存在しているため、基本的には使用しないことが推奨されています。

定数名 説明 Android 4.0 Android 2.3 Android 2.2 Android 1.5
TYPE_ACCELEROMETER 加速度
TYPE_AMBIENT_TEMPERATURE 周囲温度
TYPE_GRAVITY 重力
TYPE_GYROSCOPE ジャイロスコープ
TYPE_LIGHT 照度
TYPE_LINEAR_ACCELERATION 線形加速度
TYPE_MAGNETIC_FIELD 地磁気
TYPE_ORIENTATION 傾き 非推奨 非推奨 非推奨
TYPE_PRESSURE 気圧
TYPE_PROXIMITY 近接
TYPE_RELATIVE_HUMIDITY 相対温度
TYPE_ROTATION_VECTOR 回転ベクトル
TYPE_TEMPERATURE 温度 非推奨


SensorEventListenerオブジェクトをイベントリスナーとして登録
次に、SensorEventListenerインタフェースを実装したクラスを作成し、イベントリスナーとして登録を行います。この処理には、SensorManagerオブジェクトおよびSensorオブジェクトを使用する必要があります。

SensorEventListenerインタフェースにはコールバックメソッドとして、2つのメソッドonAccuracyChanged()メソッドとonSensorChanged()メソッドが定義されています。

onAccuracyChanged()メソッドのシグネチャは以下の通りです。このメソッドはセンサーの精度が変化したときに呼ばれるコールバックメソッドです。

public void onAccuracyChanged (Sensor sensor, int accuracy)

onSensorChanged()メソッドのシグネチャは以下の通りです。このメソッドはセンサーの値が変化したときに呼ばれるコールバックメソッドです。

public void onSensorChanged (SensorEvent event)

SensorEventListenerインタフェースを実装したクラスを作成し、これら2つのメソッドをオーバーライドします。

public class MySensorEventListener implements SensorEventListener {
    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // センサーの精度が変更されると呼ばれる
    }
 
    @Override
    public final void onSensorChanged(SensorEvent event) {
        // センサーの値が変化すると呼ばれる
    }
}

あとは、作成したクラス(MySensorEventListenerクラス)をインスタンス化し、SensorManagerクラスに定義されているregisterListener()メソッドを呼び出すことでイベントリスナーの登録を行います。

private Sensor light;
private SensorEventListener listener;
// …省略…
listener = new MySensorEventListener();
sensorManager.registerListener(listener, light, SensorManager.SENSOR_DELAY_NORMAL);

registerListener()メソッドは3つの引数をとるメソッドであり、第1引数にはSensorEventListenerインタフェースを実装したクラスのオブジェクト、第2引数にはSensorオブジェクト、第3引数にはint型の値を渡す必要があります。

上記例では、第1引数には先ほど作成したMySensorEventListenerクラスのオブジェクトを、第2引数にはあらかじめ取得しておいた照度センサーのオブジェクトを表すlight変数を、第3引数にはSensorManagerクラスに定義されているSENSOR_DELAY_NORMAL定数を渡しています。

第3引数は、センサーの反応速度を決めるための値です。
SensorManagerクラスに定義されている定数を用いることが一般的で、以下のような定数が用意されています。

定数名 説明 遅延
SENSOR_DELAY_FASTEST ゲームよりもさらに素早く反応させたい場合に指定する 0ms程度
SENSOR_DELAY_GAME ゲーム用途で使用する場合に指定する 20ms程度
SENSOR_DELAY_NORMAL デフォルト 60ms程度
SENSOR_DELAY_UI ユーザーインタフェース用途で使用する場合に指定する 200ms程度



反応速度を早くすると、その分だけバッテリーの消費が多くなってしまいます。不必要に早い反応速度にしないようにすることが大切です。

測定値の取得
センサーが測定した値を取得するには、onSensorChanged()メソッド内で引数のSensorEventオブジェクトから行います。
SensorEventクラスには以下の情報が属性として含まれています。

アクセス修飾子 データ型 属性名 概要
public int accuracy イベントの精度
public Sensor sensor イベントが発生したセンサー
public long timestamp イベントが発生した時間
public float[] values センサーが取得した値



これらの属性はすべてpublic宣言されているため、メソッドを介す必要がなく直接参照することができます。

values属性はセンサーで検出した値が格納されている属性です。
データ型が配列となっている点がポイントであり、「値がどのように格納されるか」はセンサーの種類によって異なります。

例えば、TYPE_ACCELEROMETER(加速度センサー)やTYPE_MAGNETIC_FIELD(地磁気センサー)は3次元ベクトルの値を計測するセンサーであるため、測定した値を以下のようにX、Y、Z軸の要素に分割し、values[0]からvalues[2]に保持します。
一方、TYPE_LIGHT(照度センサー)は計測する値は明るさという単一のデータのみなので、values[0]しか使用しません。

TYPE_ACCELEROMETER TYPE_LIGHT TYPE_MAGNETIC_FIELD
values[0] X軸の加速度(m/s^2) 明るさ(ルクス) X軸の地磁気(uT)
values[1] Y軸の加速度(m/s^2) 使用しない Y軸の地磁気(uT)
values[2] Z軸の加速度(m/s^2) 使用しない Z軸の地磁気(uT)

各センサーで計測した値の単位やどのようにvalues[]に格納されるかは公式HPを参照して下さい。
http://developer.android.com/reference/android/hardware/SensorEvent.html

なお、Android端末におけるX軸、Y軸、Z軸は以下のように定義されています。

リスナーの解除
最後に、不要となったリスナーは素早く適切に解除しておきましょう。
センサーが動き続けるとバッテリーを消費してしまうため、適宜(Activityの場合であれば、onPause()メソッド等)イベントリスナーの解除を行うことが重要です。

リスナーの解除にはSensorManagerクラスに定義されているunregisterListener()メソッドを使用します。

@Override
protected void onPause() {
    super.onPause();

    sensorManager.unregisterListener(listener);
}

センサー一覧と測定値のライブ表示アプリケーション
では、センサー・フレームワークを利用したアプリケーションを作成してみましょう。


ListViewのアイテムをクリックすると、そのセンサーで取得している値をライブ表示します。

まずは、MainActivityのレイアウトXMLファイルです。

● res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >
    </ListView>

</RelativeLayout>

ListViewを使用するため、RelaytiveLayout内に定義しました。


次に、MainActivityのソースプログラムです。
● src/com.example.sensorsample/MainActivity.java

package com.example.sensorsample;
// ...省略...
public class MainActivity extends Activity implements OnItemClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

        ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1,
                sensors){

            @Override
            public Object getItem(int position) {
                Sensor sensor = (Sensor)super.getItem(position);
                return sensor.getName();
            }
        };

        ListView listView = (ListView) findViewById(R.id.listView1);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Intent intent = new Intent(this, DetailActivity.class);
        intent.putExtra("ID", position);
        startActivity(intent);
    }
}

10行目でSensorManagerオブジェクトの取得、11行目で全SensorオブジェクトをListで取得しています。
13行目から21行目で取得したListを使い、ArrayAdapterを作成しています。
各アイテムのレイアウトにはsimple_list_item_1を使用しました。

ArrayAdapterを作成する際に、匿名クラスでgetItem()メソッドをオーバーライドしています。
このメソッドは、ListViewの各アイテムの内容を表示する際に使われるメソッドで、オーバーライドしていない(デフォルトでは)とSensorクラスのtoString()メソッドの実行結果が使われてしまいます。これでは、何のセンサーだかわかりませんので、ListViewに表示する文字をSensorオブジェクトの名前に変更しています。

あとは23行目から25行目でListViewオブジェクトの生成、Adapterの設定、ItemClickListenerインタフェースの設定を行いました。

29行目からはonItemClick()メソッドをオーバーライドしています。
ListViewの要素をクリックすると、何番目がクリックされたのか(position)を付加情報に設定したIntentを作成し、DetailActivityを起動します。


次に、DetailActivityのレイアウトXMLファイルです。このActivityはListViewをクリックした際に表示する2つ目のActivityです。
● res/layout/activity_detail.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView1"
        android:textStyle="bold"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

LinearLayoutの中に4つのTextViewを縦に並べました。
1つ目のTextViewにはセンサー名を、2~4つ目のTextViewはそれぞれX軸、Y軸、Z軸の値を表示するために用います。


次に、DetailActivityのソースプログラムです。
● src/com.example.sensorsample/DetailActivity.java

package com.example.sensorsample;
// ...省略...
public class DetailActivity extends Activity {
    private SensorManager sensorManager = null;
    private List<Sensor> sensors = null;
    private SensorEventListener listener = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

        final TextView x = (TextView) findViewById(R.id.textView2);
        final TextView y = (TextView) findViewById(R.id.textView3);
        final TextView z = (TextView) findViewById(R.id.textView4);

        listener = new SensorEventListener() {

            @Override
            public void onSensorChanged(SensorEvent event) {
                x.setText("x:" + String.valueOf(event.values[0]));
                y.setText("y:" + String.valueOf(event.values[1]));
                z.setText("z:" + String.valueOf(event.values[2]));
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) { }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();

        int id = getIntent().getIntExtra("ID", 0);
        Sensor sensor = sensors.get(id);

        TextView title = (TextView) findViewById(R.id.textView1);
        title.setText(sensor.getName());

        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(listener);
    }
}

20から31行目で、匿名クラスを使用しSensorEventListenerインタフェースをインスタンス化しています。
onAccuracyChanged()メソッドは空(何も実装していない)ですが、onSensorChanged()メソッドではセンサーから取得した値をTextViewに設定しています。

また、35行目からはonResume()メソッドをオーバーライドしています。
Intentの付加情報に設定されているIDを取り出し、全Sensorリストの中からリスニングするSensorオブジェクトを特定します。
あとは、44行目でSensorEventListenerの設定を行い、リスニングを開始しています。

48行目からはonPause()メソッドをオーバーライドしています。
この中では設定されているイベントリスナーの解除を行いっています。つまり、Activityがバックグラウンドに移ったときにリスニングの解除をしていることになります。


最後にマニフェストファイルです。
● AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sensorsample"
    ...省略...>

    <application
        ...省略...>
        <activity android:name="com.example.sensorsample.DetailActivity" />
    </application>
</manifest>

DetailActivityを新しく追加しているので、マニフェストファイルで追加しているのは8行目だけです。

Androidアプリ開発の必須知識!JAVAプログラミングを学べる連載リンク

はじめてのJAVA 連載

Android SQLiteデータベースとSQL 【Android TIPS】
Android Studioインストール時の問題と解決方法のまとめ
一覧へ戻る

Recent News

Recent Tips

Tag Search