はじめに
KotlinでBeacon受信機能を持ったアプリの開発を行なったため、備忘録として残す。
今回は、AltBeaconを使って実装した。
開発環境
AndroidStudio 3.1
実装
AltBeaconをbuild.gradleにて追加
implementation 'org.altbeacon:android-beacon-library:2+'
AndroidManifestにパーミッションの追加
<!-- 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が大きい(端末から近い)ビーコンの種類によって、画面が切り替わる仕様になっている。
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の制約がある。)
そこで、以下のようにコードを変更する。
// この中身はメインスレッドで実行される
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が以下の画像のように更新される。
さいごに
AndroidアプリにBeacon受信機能を実装することができた。
今回は、RSSIが最も大きいビーコン情報を表示するアプリにしたが、リスト表示することもできる。
また、Handlerクラスなど、スレッドに関する勉強もしよ...