Android BLE を下記、ドキュメントを参考に実装します。
今回は、Scanした結果をログ出力するところまでやります。
曖昧な理解なところがあるのであくまで参考程度で。
①概要
https://developer.android.com/guide/topics/connectivity/bluetooth?hl=ja
②Bluetooth Low Energyの概要
https://developer.android.com/guide/topics/connectivity/bluetooth-le?hl=ja#find
③Bluetooth Low Energyを使用する
https://developer.android.com/guide/topics/connectivity/use-ble?hl=ja#connect
#BLEパーミッション
②Bluetooth Low Energyの概要に記載があるように、BluetoothとLocationのパーミションが必要となります。
ビーコンは位置情報に関連付けられていることが多いため、ACCESS_FINE_LOCATION パーミッションも宣言する必要があります。このパーミッションがないと、スキャン結果を取得できません。
uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- If your app targets Android 9 or lower, you can declare
ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
実際はRuntime permissionを使って許可を求めると思いますが、今回は割愛します。
設定から、手動で許可をして下さい。
#Bluetooth有効か確認
Bluetoothが有効かどうか確認するメソッドです。
③Bluetooth Low Energyを使用するを参考にします。
BluetoothAdapterを取得する。
下記、ドキュメント通りBluetoothAdapterを取得しようとするとエラーになる。
// Initializes Bluetooth adapter.
val bluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager?.adapter
bluetoothManagerがNullとなりエラーとなる。
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getSystemServiceName(java.lang.Class)' on a null object reference
なので下記のように、変更する。(①概要を参考にした。)
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
Bluetoothを有効にするメソッド。有効でない場合は、ダイヤログを表示する。
REQUEST_ENABLE_BTは何でもよいので、定数を宣言する。
private val REQUEST_ENABLE_BT = 1
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
#SCAN
③Bluetooth Low Energyを使用するを参考にします。
bluetoothLeScannerクラスを使用します。
SCAN_PERIODはSCAN時間になります。
private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner
private var scanning = false
private val handler = Handler()
// Stops scanning after 10 seconds.
private val SCAN_PERIOD: Long = 10000
SCANメソッドは下記になります。
コールバックのleScanCallbackを実装する必要があります。
private fun scanLeDevice() {
bluetoothLeScanner?.let { scanner ->
if (!scanning) { // Stops scanning after a pre-defined scan period.
handler.postDelayed({
scanning = false
scanner.stopScan(leScanCallback)
}, SCAN_PERIOD)
scanning = true
scanner.startScan(leScanCallback)
} else {
scanning = false
scanner.stopScan(leScanCallback)
}
}
コールバック部
private val leScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
//コメントアウト部、今回は不要(別途実装が必要になる)
//leDeviceListAdapter.addDevice(result.device)
//leDeviceListAdapter.notifyDataSetChanged()
//nameをログで出力する。nullだった場合No Name
Log.d("result.device.name",result.device.name ?: "No Name")
}
}
ドキュメントでは下記を実装している。推測するに、SCANして見つかった情報をListに追加したいのかな。
別途実装が必要で、今回はログ出力したいのでコメントアウト。
leDeviceListAdapter.addDevice(result.device)
leDeviceListAdapter.notifyDataSetChanged()
#全容
Bluetooth確認ボタンとSCANボタンを設置して、動作確認出来るようにしました。
SCANするには、位置情報の権限を許可にする必要があります。
SCANして、ログ出力されればOKです。
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.widget.Button
class MainActivity : AppCompatActivity() {
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner
private var scanning = false
private val handler = Handler()
private val REQUEST_ENABLE_BT = 1
private val SCAN_PERIOD: Long = 10000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bluetooth_check_button).setOnClickListener {
if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
}
findViewById<Button>(R.id.scan_button).setOnClickListener {
scanLeDevice()
}
}
private fun scanLeDevice() {
bluetoothLeScanner?.let { scanner ->
if (!scanning) { // Stops scanning after a pre-defined scan period.
handler.postDelayed({
scanning = false
scanner.stopScan(leScanCallback)
println("stopScan")
}, SCAN_PERIOD)
scanning = true
scanner.startScan(leScanCallback)
println("startScan")
} else {
scanning = false
scanner.stopScan(leScanCallback)
println("stopScan")
}
}
}
private val leScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
//nameをログで出力する。nullだった場合No Name
Log.d("result.device.name", result.device.name ?: "No Name")
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bluetooth_check_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Bluetooth確認"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/scan_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="SCAN"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
#おわりに
BLEを触ってみたく、ドキュメントを見ながら実装をしました。
次は、GATTサーバーに接続からやってみようかなと。
あと、セントラルとペリフェラルが分からなく調べたところ、下記サイトが参考になりました。
https://houwa-js.co.jp/exe/2018/06/20180629/