#はじめに
Androidには様々なセンサーが内蔵されています。そのセンサーを全て取得してみます。
とは言ってもセンサーの取得方法としてわかりやすく記載されているところがほとんど無いようです。
ここではMainActivityはそのままで、クラスを追加して簡単に取得する方法で進めていきます。
#準備
センサーからの情報はSensorEventListenerをインターフェイスとしたActivityクラスで受け取るのですがそれだと汎用性がなくなります。
そこでActivityではなくServiceとして実装します。
Serviceとして実装するにはmanifestファイルにserviceタグを追加してサービスを有効にします。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
中略
<service
android:name=".SensorServiceListener"
android:enabled="true"
android:exported="false"></service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
さっそくセンサーの値を受け取るサービスクラスを作ります。
class SensorServiceListener : Service(),SensorEventListener{
override fun onCreate() {
super.onCreate()
}
override fun onDestroy() {
super.onDestroy()
}
override fun onSensorChanged(event: SensorEvent?) {
}
override fun onBind(intent: Intent?): IBinder {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
}
override の実装が必要なのでメソッド数が多いです。
メソッド名 | 役割 |
---|---|
onSensorChanged | センサーの値を受け取ったとき |
onAccuracyChanged | センサーのレンジが変わったとき |
onStartCommand | サービスを裏で動かし続けるときのもので今回は使わない |
onBind | サービスとサービス開始クラスの架け橋 |
#onCreate
サービスクラス生成時のメソッドは下記の通りです。
var sensors: List<Sensor> = listOf()
override fun onCreate() {
super.onCreate()
val sensorManager: SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensors = sensorManager.getSensorList(Sensor.TYPE_ALL)
if (sensors.size > 0) {
for (i in 0..sensors.size-1) {
val s: Sensor = sensors.get(i);
sensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_NORMAL)
}
}
}
センサーを管理するSENSOR_SERVICEを作成し、TYPE_ALLですべてのセンサーをsensorsに代入します。
registerListenerでそのセンサーを有効にしています。
#onDestroy
クラス破棄時の処理は下記の通り
override fun onDestroy() {
super.onDestroy()
val sensorManager : SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensorManager.unregisterListener(this)
}
#onSensorChanged
正常にセンサーから情報が来たときのためにログを出しておきます。
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) return
Log.d("","${event.values[0]}")
}
#MainActivity側
とりあえず情報が取得出来るのか動かします。
##開始
val intent = Intent(this,SensorServiceListener::class.java)
context.startService(intent)
##終了
val intent = Intent(this,SensorServiceListener::class.java)
context.stopService(intent)
onSensorChangedにブレイクポイントを設定してここに飛んできたら成功です。
#問題点
SensorServiceListenerクラスにはセンサーの情報が来ますが、ではそれをメインクラスに伝えるには?ということで調べてみると参考にするものが無い。
なにせIntentとして定義したものをサービスとして動かしているので実体がありません。
それ意外にもメインでは複雑な管理をさせたくないのでサービスを管理するSensorServiceクラスを作ります。
class SensorService(private val context: Context) : Handler(){
private lateinit var mService: SensorServiceListener
var sensors: List<Sensor> = listOf()
fun start(){
val intent = Intent(context,SensorServiceListener::class.java)
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
context.startService(intent)
}
fun stop(){
val intent = Intent(context,SensorServiceListener::class.java)
context.unbindService(mConnection)
context.stopService(intent)
}
private val mConnection = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val localBinder = binder as SensorServiceListener.LocalBinder
mService = localBinder.getService()
sensors = mService.sensors
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
}
mServiceには最初なにも入っていません。
サービスが正常に開始されるとonServiceConnectedが呼ばれてその中で**getService()**でサービスの実体が返されます。
実体が得られたのでsensors がメインクラスで使えるようにコピーしておきます。
SensorServiceListenerもバインドに対応させます。
override fun onBind(intent: Intent?): IBinder {
return binder
}
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
fun getService(): SensorServiceListener = this@SensorServiceListener
}
getServiceで自分自身を返します。
これでメインクラスからサービスへアクセスが出来るようになりました。とは言ってもセンサーに何かを要求することは無いのでsensersにセンサーの情報をコピー出来ただけです。
欲しいのはセンサーを取得したときにメインクラスへその値を通知する方法です。
実はそのためにメインクラスのSensorServiceを**Handler()**クラスで作っていました。
サービス側でこのHandler()が参照できればそこにメッセージを送信し、メインクラスで受信すれば良いのです。
class SensorServiceListener : Service(),SensorEventListener{
private var handler : Handler? = null // 追加
fun setHandler(handler : Handler){ // 追加
this.handler = handler
}
private fun setHandler(){//追加
mService.setHandler(this)
}
private val mConnection = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val localBinder = binder as SensorServiceListener.LocalBinder
mService = localBinder.getService()
sensors = mService.sensors
setHandler() // 追加
}
バインドしたときにサービス側のhandler に自分自身を参照させます。
あとはメッセージのやりとりをすれば良いのです。
#サンプル
interface SensorServiceInterface : SensorService.Listener { // 独自のイベントリスナー
fun onSensorChanged(sensorType : Int,values : FloatArray) // センサー値取得イベント
fun onAccuracyChanged(sensorType : Int,accuracy : Int) // センサーレンジ変更イベント
}
class SensorService(private val context: Context) : Handler(){ // 様々なセンサーの情報を管理するクラス
private lateinit var mService: SensorServiceListener // センサー取得サービスの参照用
private var listener: SensorServiceInterface? = null // 外部へのリスナー仲介
interface Listener {}
var sensors: List<Sensor> = listOf() // センサー情報
fun start(){
val intent = Intent(context,SensorServiceListener::class.java)
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
context.startService(intent)
}
fun stop(){
val intent = Intent(context,SensorServiceListener::class.java)
context.unbindService(mConnection)
context.stopService(intent)
}
override fun handleMessage(msg: Message) { // スレッド間通信用メッセージクラス
if (msg.arg1 == 1){ // センサーの値取得時
val values = msg.obj as FloatArray //センサーの値を参照
listener?.onSensorChanged(msg.arg2,values) // センサー値取得イベント
}
if (msg.arg1 == 2){ // センサーのレンジ変更時
listener?.onAccuracyChanged(msg.arg2,msg.obj.toString().toInt()) // レンジ変更イベント
}
}
fun setListener(listener: Listener?) { // イベント受け取り先を設定
if (listener is SensorServiceInterface) {
this.listener = listener
}
}
private fun setHandler(){
mService.setHandler(this)
}
private val mConnection = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val localBinder = binder as SensorServiceListener.LocalBinder
mService = localBinder.getService()
sensors = mService.sensors
setHandler()
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
}
class SensorServiceListener : Service(),SensorEventListener{
private var handler : Handler? = null
var sensors: List<Sensor> = listOf()
override fun onCreate() {
super.onCreate()
val sensorManager: SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensors = sensorManager.getSensorList(Sensor.TYPE_ALL)
if (sensors.size > 0) {
for (i in 0..sensors.size-1) {
val s: Sensor = sensors.get(i);
sensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_NORMAL)
}
}
}
override fun onDestroy() {
super.onDestroy()
val sensorManager : SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensorManager.unregisterListener(this)
}
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) return
val msg = Message.obtain() // センサー値取得イベントを発生させる
msg.arg1 = 1 // センサー値取得イベントを示す値
msg.arg2 = event.sensor.type // センサーの種類を渡す
msg.obj = event.values.clone() // センサーの値をコピーして渡す
if (handler != null) handler?.sendMessage(msg) // メッセージ送信
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
val msg = Message.obtain() // レンジ変更イベントを発生させる
msg.arg1 = 2 // レンジ変更イベントを示す値
msg.arg2 = sensor!!.type.toInt() // センサーの種別
msg.obj = accuracy
if (handler != null) handler?.sendMessage(msg) // メッセージ送信
}
fun setHandler(handler : Handler){
this.handler = handler
}
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
fun getService(): SensorServiceListener = this@SensorServiceListener
}
}
class MainActivity : AppCompatActivity() {
val sendorService = SensorService(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendorService.setListener(sensorListener)
sendorService.start()
}
override fun onDestroy() {
super.onDestroy()
sendorService.stop()
}
private val sensorListener = object : SensorServiceInterface{
override fun onSensorChanged(sensorType: Int, values: FloatArray) {
var idx = -1
for (i in 0..sendorService.sensors.size-1){
if(sendorService.sensors[i].type == sensorType){
idx = i
}
}
if (idx != -1) {
Log.d("Sample","$sensorType $sendorService.sensors[idx].name ${values[0]}")
}
}
override fun onAccuracyChanged(sensorType: Int, accuracy: Int) {}
}
ログにセンサー番号、センサー名、センサーの値(最初の1つだけ)を残すとこんな感じです。
#おわりに
ただでさえ情報が少ないService関係ですがさらにメインに伝えるためにはbindを使わねばならず、その情報もなかなかありません。他の様々なサービスもこれをベースにして頂ければ苦労が少なくなるかと思います。
肝心のセンサーですが端末の機種によって様々です。最低でも照度とスマホの傾き?は得られるようです。
他にもmanifestに使用を許可しないと使えないものもあるようです。
スマホのカタログスペックにセンサーは載っているようですので、普段はあまりみないセンサーの欄にも注目してみましょう。
#参考
下記のサイトを参考にしています。
Androidデベロッパーガイド