【Android】altBeacon ライブラリを使って領域監視を行う（v2.19以上対応）



## 概要
Beacon とは Bluetooth Low Energy (BLE) を発信する端末やそれを利用した位置特定技術のことです。
今回は Android 端末を使い、Beacon の領域監視を行う方法についてご紹介します。

ライブラリは altbeacon という Bluetooth ライブラリを使用します。

なお、このライブラリはバージョン3.0で従来使えていた多くのメソッドが使用不可になることが予定されており、2.19の時点でこれらのメソッドが非推奨となっています。
そのため当記事ではバージョン2.19以上に対応した書き方を紹介するとともに、旧バージョンからの移行方法についても合わせて述べたいと思います。

## 環境
Android Studio：2021.1.1 Patch 3
kotlin：1.6.20
targetSdkVersion：32
minSdkVersion：27


## 領域監視
領域監視とは Beacon 受信端末によって、Beaconの電波領域への自身の出入りを検知する機能です。
今回は altbeacon ライブラリを使って領域監視を行うとともに、捉えた Beacon の内容を識別するような実装を行います。

## 実装
### 準備
まずは実装の準備として以下のライブラリを gradle(appレベル) に追加します。（バージョンは2022/5/9時点のもの）

```build.gradle
implementation 'org.altbeacon:android-beacon-library:2.19.4'
```

次に Manifest ファイルに以下の権限を追加します。
なお、```uses-feature``` タグを追加することで Beacon に未対応の端末ではアプリのインストール自体をできなくしています。

``` AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<uses-feature
  android:name="android.hardware.bluetooth_le"
  android:required="true" />
```

ここまでできたら Activity への実装を進めていきます。
まずは以下のように、Beacon 検知を開始する Activity に```RangeNotifier```, ```MonitorNotifier```インターフェースを継承します。

``` MainActivity.kt
class MainActivity : AppCompatActivity(), RangeNotifier, MonitorNotifier {

```
なお、ライブラリバージョン2.19以前は ```BeaconConsumer``` インターフェースを継承していましたが、2.19以降はこのインターフェース自体が非推奨となっています。
旧バージョンから移行する場合は ```BeaconConsumer``` を削除して上記二つのインターフェースへ置き換えるようにしてください。

次に、領域監視に必要な権限をコールします。
呼び出しには ResultAPI を使用しています。
Android12 以降と以前では必要な権限が異なりますのでご注意ください。

``` MainActivity.kt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
  permissionResult.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
} else {
  permissionResult.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT))
}

```

次に ```Region``` オブジェクトを作成します。
これは Beacon の電波が複数存在する場合に検知対象の Beacon を識別するためのものです。

``` MainActivity.kt
private lateinit var mRegion: Region
--- 省略 ---
mRegion = Region("iBeacon", Identifier.parse("監視対象のUUID"), null, null)
```
第一引数では全ての Region オブジェクトで一意となるような文字列を指定します。
第二引数以降は uuid, Major, Minor の順で指定します。
今回は uuid のみを指定し、それ以外は null に指定しています。
こうすることで指定した uuid を持つ電波（アドバタイズ）を受信した時だけ領域への出入りを検知することができます。
なお、第二引数以降を全て ```null``` にした場合は全てのアドバタイズに対して同様の監視を行います。

次に Beacon検知を行うために必要な BeaconManager オブジェクトを作成します。

``` MainActivity.kt
private lateinit var beaconManager: BeaconManager
private val IBEACON_FORMAT = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24" 
--- 省略 ---
beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.beaconParsers.add(BeaconParser().setBeaconLayout(IBEACON_FORMAT)) // iBeaconのフォーマット指定
```

なお、iBeacon を検知したい場合は最後の行のように iBeacon のフォーマットを指定する必要があります。

### 検知開始

ここまでできたら領域監視を開始します。
開始したい任意の場所に以下のコードを記述します。
``` MainActivity.kt
beaconManager.addMonitorNotifier(this)
beaconManager.addRangeNotifier(this)
beaconManager.startMonitoring(mRegion)
beaconManager.startRangingBeacons(mRegion)
```

領域監視を開始するためには ```startMonitoring()``` と ```startRangingBeacons()``` 両方を実行する必要があり、これらのメソッドの引数に上で作成した Region オブジェクトを渡します。
なお、```startMonitoring()``` と ```startRangingBeacons()``` メソッドにはオートバインド機能が備わっているため、```beaconManager.bind()``` を明示的に呼び出す必要はありません。
旧バージョンから移行する場合は ```bind()``` メソッドを削除する必要があります。

### 検知処理実装
次にはじめに記述した二つのインターフェース（RangeNotifier, MonitorNotifier）に定義されているメソッドをオーバーライドします。

``` MainActivity.kt
override fun didEnterRegion(region: Region?) {
    //領域への入場を検知
    Log.d("iBeacon", "Enter Region ${region?.uniqueId}")
}

override fun didExitRegion(region: Region?) {
    //領域からの退場を検知
    Log.d("iBeacon", "Exit Region ${region?.uniqueId}")
}

override fun didRangeBeaconsInRegion(beacons: MutableCollection<Beacon>?, region: Region?) {
    // 検知したBeaconの情報
    Log.d("MainActivity", "beacons.size ${beacons?.size}")
    beacons?.let {
        for (beacon in beacons) {
            Log.d("MainActivity", "UUID: ${beacon.id1}, major: ${beacon.id2}, minor: ${beacon.id3}, RSSI: ${beacon.rssi}, TxPower: ${beacon.txPower}, Distance: ${beacon.distance}")
        }
    }
}

override fun didDetermineStateForRegion(state: Int, region: Region?) {
    //領域への入退場のステータス変化を検知（INSIDE: 1, OUTSIDE: 0）
    Log.d("MainActivity", "Determine State: $state")
}

```

```didEnterRegion()``` と ```didExitRegion()``` は Regionオブジェクトで指定した Beacon 領域への入退場を検知した時に実行されるメソッドです。
```didDetermineStateForRegion()``` も同じタイミングで実行されますが、こちらは入場と退場どちらが行われたかを数値で返却します。(入場が1, 出場が0)

```didRangeBeaconsInRegion()``` は指定した Region に関わらず検知した全ての Beacon の情報を取得します。
取得した情報は引数の ```beacons``` 変数に格納されますので、上記コードのような形で個々の uuid や distance （発信端末との距離）などの情報を確認することができます。

なお、旧バージョンのライブラリを使用していた時は ```onBeaconServiceConnect()``` メソッドの中でこれらのメソッドと同様の処理を書いていたため、新しいバージョンに移行する場合は ```onBeaconServiceConnect()``` メソッドごと削除した上で上記コードに置き換える必要があります。

### 検知停止
最後に領域監視を停止する方法を紹介します。

停止させたい場所に以下のコードを記述します。

``` MainActivity.kt
beaconManager.stopMonitoring(mRegion)
beaconManager.stopRangingBeacons(mRegion)
```

なお上記の二つのメソッドは自動でアンバインド処理を行ってくれるため、自分でアンバインド処理（```beaconManager.unbind()```）を明示的に呼び出す必要はありません。
旧バージョンから移行する場合は ```unbind()``` メソッドを削除するようにしてください。

## まとめ
今回は Android 端末で altbeacon ライブラリを用いて Beacon の領域監視を行う方法について説明しました。
なお、領域監視はフォアグラウンドで行うことでアプリが停止している状態の時も検知できるようになってより便利になるのですが、記事が長くなりそうなので別の機会に紹介させていただきたいと思います。

最後までお読みいただきましてありがとうございました。

## 参考
https://houwa-js.co.jp/2018/09/20180906/
[Android Beacon Library](https://altbeacon.github.io/android-beacon-library/autobind.html)