LoginSignup
4
4

More than 3 years have passed since last update.

【Kotlin】Android内蔵センサーの計測値を全て取得

Posted at

はじめに

Androidには様々なセンサーが内蔵されています。そのセンサーを全て取得してみます。
とは言ってもセンサーの取得方法としてわかりやすく記載されているところがほとんど無いようです。
ここではMainActivityはそのままで、クラスを追加して簡単に取得する方法で進めていきます。

準備

センサーからの情報はSensorEventListenerをインターフェイスとしたActivityクラスで受け取るのですがそれだと汎用性がなくなります。
そこでActivityではなくServiceとして実装します。
Serviceとして実装するにはmanifestファイルにserviceタグを追加してサービスを有効にします。

manifest.xml
<?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>

さっそくセンサーの値を受け取るサービスクラスを作ります。

service.kt
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

サービスクラス生成時のメソッドは下記の通りです。

onCreate.kt
    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

クラス破棄時の処理は下記の通り

onDestroy.kt
    override fun onDestroy() {
        super.onDestroy()
        val sensorManager : SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        sensorManager.unregisterListener(this)
    }

onSensorChanged

正常にセンサーから情報が来たときのためにログを出しておきます。

onSensorChanged.kt
    override fun onSensorChanged(event: SensorEvent?) {
        if (event == null) return
        Log.d("","${event.values[0]}")
    }

MainActivity側

とりあえず情報が取得出来るのか動かします。

開始

start.kt
        val intent = Intent(this,SensorServiceListener::class.java)
        context.startService(intent)

終了

stop.kt
        val intent = Intent(this,SensorServiceListener::class.java)
        context.stopService(intent)

onSensorChangedにブレイクポイントを設定してここに飛んできたら成功です。

問題点

SensorServiceListenerクラスにはセンサーの情報が来ますが、ではそれをメインクラスに伝えるには?ということで調べてみると参考にするものが無い。
なにせIntentとして定義したものをサービスとして動かしているので実体がありません。

それ意外にもメインでは複雑な管理をさせたくないのでサービスを管理するSensorServiceクラスを作ります。

SensorService.kt
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もバインドに対応させます。

SensorServiceListener.kt
    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
    private val binder = LocalBinder()

    inner class LocalBinder : Binder() {
        fun getService(): SensorServiceListener = this@SensorServiceListener
    }

getServiceで自分自身を返します。
これでメインクラスからサービスへアクセスが出来るようになりました。とは言ってもセンサーに何かを要求することは無いのでsensersにセンサーの情報をコピー出来ただけです。

欲しいのはセンサーを取得したときにメインクラスへその値を通知する方法です。

実はそのためにメインクラスのSensorServiceHandler()クラスで作っていました。
サービス側でこのHandler()が参照できればそこにメッセージを送信し、メインクラスで受信すれば良いのです。

SensorServiceListener.kt
class SensorServiceListener : Service(),SensorEventListener{
    private var handler : Handler? = null // 追加

    fun setHandler(handler  : Handler){  // 追加
        this.handler = handler
    }
SensorService.kt
    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 に自分自身を参照させます。
あとはメッセージのやりとりをすれば良いのです。

サンプル

SensorService.kt
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
    }
}
MainActivity.kt
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デベロッパーガイド

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4