AltBeaconでビーコン検出

# はじめに
KotlinでBeacon受信機能を持ったアプリの開発を行なったため、備忘録として残す。
今回は、AltBeaconを使って実装した。

# 開発環境
AndroidStudio 3.1

#実装
AltBeaconをbuild.gradleにて追加

```build.gradle(app/)
implementation 'org.altbeacon:android-beacon-library:2+'
```

AndroidManifestにパーミッションの追加

```AndroidManifest.xml
<!-- BLE使用のためのパーミッション -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- 対応しない端末へのインストールを禁止(この記述の追加は各自で判断) -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
```

以下、Activityの編集
`onCreate`で、BLE対応端末の確認・APIレベルが23以上の確認を行なっている。
APIレベ23以上の端末は、アプリ起動時にダイアログで権限の申請をする必要がある。
そのため、23以上の場合`checkPermission`メソッドにて権限のリクエストを行う。
また、`BeaconManager`のインスタンスの生成と、フォーマットの設定を行う。

今回は、複数のビーコンを検知した状態で、最もRSSIが大きい(端末から近い)ビーコンの種類によって、画面が切り替わる仕様になっている。

```MainActivity.kt
class MainActivity : AppCompatActivity(), BeaconConsumer {

    // BeaconManager型変数の宣言
    private var beaconManager: BeaconManager? = null

    // uuidの指定
    private val uuidString: String = "00000000-0000-0000-0000-000000000000"
    private val uuid = Identifier.parse(uuidString)

    // ビーコンのフォーマット設定
    private val IBEACON_FORMAT: String = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"


    /**************************************************
     * AppCompatActivity内のメソッドをoverride
     **************************************************/
    // onCreate
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // デバイスのBLE対応チェック
        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {

            // 未対応の場合、Toast表示
            showToast("このデバイスはBLE未対応です", Toast.LENGTH_LONG)
        }

        // API 23以上かのチェック
        if (Build.VERSION.SDK_INT >= 23) {

            // パーミッションの要求
            checkPermission()
        }

        // ビーコンマネージャのインスタンスを生成
        beaconManager = BeaconManager.getInstanceForApplication(this)

        // BeaconManagerの設定
        beaconManager!!.beaconParsers.add(BeaconParser().setBeaconLayout(IBEACON_FORMAT))
    }

    // onResume
    override fun onResume() {
        super.onResume()

        // ビーコンサービスの開始
        beaconManager!!.bind(this)
    }

    // onPause
    override fun onPause() {
        super.onPause()

        // ビーコンサービスの停止
        beaconManager!!.unbind(this)
    }


    /**************************************************
     * BeaconConsumer内のメソッドをoverride
     **************************************************/
    // onBeaconServiceConnect
    override fun onBeaconServiceConnect() {

        try {
            // ビーコン情報の監視を開始、第3,4引数はmajor・minor値を指定する時に使用
            beaconManager!!.startMonitoringBeaconsInRegion(Region("ここは適用な文字列", uuid, null, null))
        }
        catch (e: RemoteException) {
            e.printStackTrace()
        }

        // モニタリングの通知受取り処理
        beaconManager!!.addMonitorNotifier(object: MonitorNotifier{

            // 領域内に侵入した時に呼ばれる
            override fun didEnterRegion(region: Region) {

                // レンジングの開始
                beaconManager!!.startRangingBeaconsInRegion(region)
            }

            // 領域外に退出した時に呼ばれる
            override fun didExitRegion(region: Region) {

                // レンジングの停止
                beaconManager!!.stopRangingBeaconsInRegion(region)
            }

            // 領域への侵入/退出のステータスが変化した時に呼ばれる
            override fun didDetermineStateForRegion(i: Int, region: Region) {
                //
            }
        })

        // レンジングの通知受け取り処理
        beaconManager!!.addRangeNotifier(object: RangeNotifier{

            // 範囲内のビーコン情報を受け取る
            override fun didRangeBeaconsInRegion(beacons: Collection<Beacon>, region: Region){

                var maxMajor: Int?
                var maxMinor: Int?

                // 範囲内の複数のビーコン情報を保持させる変数
                var getMajorList: ArrayList<Int> = ArrayList()
                var getMinorList: ArrayList<Int> = ArrayList()
                var getRssiList: ArrayList<Int> = ArrayList()

                // 範囲内にビーコンがある時の処理
                if (beacons.size > 0) {

                    // 範囲内のビーコンの数だけ繰り返す
                    for (beacon in beacons) {
                        // 複数のビーコン情報をArrayListに分割
                        getMajorList.add(beacon.id2.toInt())
                        getMinorList.add(beacon.id3.toInt())
                        getRssiList.add(beacon.rssi)
                    }

                    // RSSIが最も大きいインデックスを取得
                    var indexRssi: Int = getRssiList.indexOf(getRssiList.max())

                    // 取得したインデックスのmajor値・minor値を取得
                    maxMajor = getMajorList[indexRssi]
                    maxMinor = getMinorList[indexRssi]

                    // Viewの更新
                    viewUpdate(maxMajor, maxMinor)
                }
            }
        })
    }


    /**************************************************
     * メソッド
     **************************************************/
    // トースト表示のメソッド
    fun showToast(text: String, length: Int) {

        // トーストの生成と表示
        var toast: Toast = Toast.makeText(this, text, length)
        toast.show()
    }

    // パーミッションの許可チェック
    @RequiresApi(Build.VERSION_CODES.M)
    fun checkPermission() {

        // パーミッション未許可の時
        if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

            // パーミッションの許可ダイアログの表示
            requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 0)
        }
    }

    fun viewUpdate(major: Int?, minor: Int?) {

        // Viewの取得
        var majorTextView: TextView = findViewById(R.id.major) as TextView
        var minorTextView: TextView = findViewById(R.id.minor) as TextView

        majorTextView.text = "major:" + major
        minorTextView.text = "minor:" + minor
    }

}

```

なお、このコードでビーコンを検知するとアプリがクラッシュする。
エラーを見ると、`Only the original thread that created a view hierarchy can touch its views.`と表示される。
これは、ビーコンを検出する別スレッド上にて、`viewUpdate`メソッドを呼んだためである。(メインスレッド以外でUIの操作を行なってはいけないというAndroidの制約がある。)
そこで、以下のようにコードを変更する。

```MainActivity.kt
// この中身はメインスレッドで実行される
class MainActivity : AppCompatActivity(), BeaconConsumer {

    ~省略~

    // ビーコンのフォーマット設定
    private val IBEACON_FORMAT: String = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"

    // Handlerクラスの変数の宣言(追加)
    private val handler: Handler = Handler()

    ~省略~

        // レンジングの通知受け取り処理
        beaconManager!!.addRangeNotifier(object: RangeNotifier{

            // 範囲内のビーコン情報を受け取る
            override fun didRangeBeaconsInRegion(beacons: Collection<Beacon>, region: Region){

                var maxMajor: Int?
                var maxMinor: Int?

                // 範囲内の複数のビーコン情報を保持させる変数
                var getMajorList: ArrayList<Int> = ArrayList()
                var getMinorList: ArrayList<Int> = ArrayList()
                var getRssiList: ArrayList<Int> = ArrayList()

                // 範囲内にビーコンがある時の処理
                if (beacons.size > 0) {

                    // 範囲内のビーコンの数だけ繰り返す
                    for (beacon in beacons) {
                        // 複数のビーコン情報をArrayListに分割
                        getMajorList.add(beacon.id2.toInt())
                        getMinorList.add(beacon.id3.toInt())
                        getRssiList.add(beacon.rssi)
                    }

                    // RSSIが最も大きいインデックスを取得
                    var indexRssi: Int = getRssiList.indexOf(getRssiList.max())

                    // 取得したインデックスのmajor値・minor値を取得
                    maxMajor = getMajorList[indexRssi]
                    maxMinor = getMinorList[indexRssi]

                    Log.d("Test_Major:", maxMajor.toString())
                    Log.d("Test_Minor", maxMinor.toString())

                    // メインスレッドで実装(追加)
                    handler.post {
                        // 空の引数を渡して、Viewの更新
                        viewUpdate(maxMajor, maxMinor)
                    }
                }
            }
        })
```

`Handler`クラスを使うと、別スレッド中でもメインスレッドで処理を実装することができる。詳しい説明については参考サイトを参照してください...
これからここら辺の勉強しよ...

レイアウトは、TextView2つ用意して、idを"major","minor"に設定。
これで複数(一個でもいい)のBeaconをキャッチした時、Viewが以下の画像のように更新される。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/108396/8b2a422d-8584-825d-dbff-fb620de12bdb.png)

# さいごに
AndroidアプリにBeacon受信機能を実装することができた。
今回は、RSSIが最も大きいビーコン情報を表示するアプリにしたが、リスト表示することもできる。
また、Handlerクラスなど、スレッドに関する勉強もしよ...