Androidのセンサ情報を低消費電力に取得する(Sensor Batching)

この記事は[Fujitsu extended Advent Calendar 2016](http://qiita.com/advent-calendar/2016/fujitsu_ext) 23日目の記事です。

※この記事に描かれていることは個人の見解であり、所属する組織の公式見解ではありません。
個人的な活動の経験から得た知見の共有を目的としています。

Sensor BatchingはAndroid4.4より搭載されており搭載から結構時間が経っています。
しかしながらあまり日本語の記事がなく、困っていたので実際に動かした個人の知見をベースに紹介してみます。

#ネットに繋がる最強のセンサデバイス
センサ使ってなんか試してみよーぜ！ってことで世の中にはインターネットに繋げられるいろんなセンサデバイスが出現し始めました。わかりやすいところだとAndroid WearやApple Watch、pebbleなどのウェアラブルデバイスなんかそうです。あとはArduinoやRaspberry Piを使って秋月電子や千石電商等で買ってきたセンサを繋いだりして自作することも可能です。Bluetooth Low Energyのような低消費電力で接続できる無線通信規格も出てきました。

ただ、「まず試す」なら皆さん大体持っている最強のセンサーデバイスがあります。そう「スマートフォン」です。AndroidもiPhoneにもかなりの数のセンサが搭載されており誰でも自由にアプリを作ることができます。ここではAndroid周りの話をします。（iPhoneも同等だと思いますが不確実）

#最近のAndroidスマホに大体入ってるセンサ

最近のここ数年のAndroidスマホにはかなり色々な種類のセンサが入っています。

>加速度センサ　・・・物体の動きを感知するセンサ。画面が縦横に回転できるのはこのセンサが60ミリ秒周期で動作しているおかげです。LINEのふるふるという機能もこのセンサで動いています。重力加速度を見つけることで端末の傾きなんかも分かったりします。
>地磁気センサ　・・・　いわゆるコンパスです。Google Mapなんかで北がどっちか教えてくれるのはこのセンサです。
>ジャイロセンサ　・・・端末がどれぐらい回転したかがわかるセンサ。運転ゲームなんかでは必須です。ポケモンGOでAR使うときはこれがないと実行できません。

「９軸モーションセンサ」などとまとめられることがありますが、大体上の３つが入ってます。

他にもこんなセンサがあったりします。
>照度センサ　・・・　画面の明るさを自動調整するために入っているセンサ。大体ディスプレイの上にあります。
>近接センサ　・・・　通話中画面を消して誤動作させないためのセンサ。大体照度センサのすぐ横にあります。

Xperia,arrows,AQUOSなど大体のスマホにはこれらのセンサが入っています。
他にも温湿度センサとか気圧センサなど変わったセンサが乗っているデバイスも中にはあります。(Galaxyなど）
これらのセンサは全てAPIが公開されており、APIを使って自由にセンサを使ったアプリを作ることができます。全てのセンサの情報は取得間隔を指定することができ、これを応用することによってジャンプしたり、明るいところに来たり、暗いところに来たり、といった情報も取得できます。なんか色々遊べそう＆使えそうですよね。

#Android4.4〜　Sensor Batching
ただし先程までの機能を使うためにはCPUが動いている必要がありました。つまり画面がついていたり、消えていてもアプリがずっと動くような処理のアプリを作らなければセンサの情報は取得できません。画面がついているときは今までのやり方で十分ですが、消えているときまでCPUを動かすとあっという間に電池が減ってしまいます。（そのためメーカーによっては歩数計用に専用のマイコンを作ったりしています）

そこで、Android4.4よりSensor Batchという機能が追加されました。この機能を使うことにより、画面が消えてCPUが省電力モードに入っている時であってもセンサを稼働し、ハードウェアの中にセンサの情報を蓄積しておくことができます。バッチ処理にて定期的にアプリを動かして取りに行く必要はありますが、ずっと動かす必要がないため消費電力を抑えたまま、先程あげたセンサの情報を収集することができます。

#実装方法

ここからAndroid Studioでのアプリの実装方法の話になります。従来、センサを登録する際、SensorManagerを使って以下のようにリスナー登録をする必要がありました。

```java:MainActivity.java
//変数の宣言
Sensor mAccel;
SensorManager mSensorManager;

//加速度センサの指定
mAccel = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

//加速度センサのリスナー登録
mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI);
```
１つ目の引数がリスナー、２つ目がセンサー（今回は加速度センサ）、３つ目がセンサ値の取得間隔です。これによりセンサが取得できる度にonSensorChanged()へ値がコールバックされます。SENSOR_DELAY_UIは60msec間隔なので60msec毎に変化したセンサの値がコールバックされます。一方で、SensorBatchを使う場合は以下のように登録します。

```java:MainActivity.java
//変数の宣言
Sensor mAccel;
SensorManager mSensorManager;

//加速度センサの指定
mAccel = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

//加速度センサのリスナー登録
int SECOND = 1000 * 1000;
mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI ,SECOND*30);
```

変更すべき箇所はregisterListenerの４つ目の引数です。ここを変えるだけで基本的にはSensorBatchは有効になります。１つ目の引数がリスナー、２つ目がセンサー、３つ目がセンサ自体の取得間隔（マイクロ秒指定）、４つ目がバッチ間隔（マイクロ秒指定）です。上記の場合、60msec毎にセンサの値を取得しハードウェアの中に蓄積、最大３０秒おきにバッチ処理でまとめて取り出すことができます。

バッチは起動されると溜まった数だけセンサの値をonSensorChangedにコールバックします。
そのためログを吐き出す際は工夫しないと一気に流れます…。実際に実行してみましたがバッチの実行間隔は結構ムラがあるようです。３０秒指定して３０秒後に来ることもあれば、５秒ぐらいで返ってきてしまうこともありました。また他にセンサを動かしている機能（自動回転など）があると都度コールバックするようです・・・。あまり実行間隔はあてにしないほうが良いかもしれません（この辺りおそらく機種によって動作が異なる気がします）

バッチ間隔を待たずにセンサの値を取り出したくなったら任意のタイミングで以下を呼び出してください。ごそっとセンサの情報がonSensorChangedに返ってきます。

```java:MainActivity.java
mSensorManager.flush(this);
```

今度は、これらをクラウドに集めて分析するところまで書いてみようと思います。
（本当はそこまで書きたかった...）