우찬쓰 개발블로그

편리한 안드로이드 블루투스 BLE 라이브러리, RxAndroidBle 사용해보기 본문

안드로이드/편리한 라이브러리

편리한 안드로이드 블루투스 BLE 라이브러리, RxAndroidBle 사용해보기

이우찬 2018. 11. 15. 13:12
반응형

안드로이드에 블루투스 기능을 붙여야하는 개발자들의 고통을 줄여주기위한 라이브러리를 소개하고자 한다. (RxJava를 모르면 더 고통스러워 질수 있으니 RxJava에 대한 지식이 있는 사람만 보기를 권장한다.)


구글에서 자체적으로 안드로이드에 지원하는 BLE라이브러리가 있지만, BLE 기능을 개발해본 개발자들은 알다시피 BluetoothGattCallback에서 전부 분기처리를 해주어야하니,, 여간 고통스러운일이 아닐 수 없다.




잠시 이 글과는 관계없는 다른 이야기를 하자면 자바에도 함수형 프로그래밍 바람이 불면서 OnClickListener등도 Activity class에 상속하거나 따로 변수로 생성하여 분기처리 하는 것이 아닌 람다로 간단히 해당 뷰에 set하는 형식으로 트랜드가 바뀌어 가고있다. 필자와 같이 코틀린을 쓰고 있는 개발자라면 더욱이 잘 느낄 것이다.


예 - 기존 방식) 다음과 같은 보일러 플레이트 코드가 많은 코드들 때문에 기존에는 이렇게 쓰지않고 따로 하나의 OnClickListener를 생성하여 분기처리하였다.

findViewById(R.id.btn_disconnect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
disconnectDivice();
}
});


예 - 트랜드 방식) 람다코드로는 한줄이면 끝나기 때문에 (무려 6줄이 1줄이 되었다!) 복잡한 분기처리가 필요없게 되었다.

findViewById(R.id.btn_disconnect).setOnClickListener(v -> disconnectDivice());



또한 RxJava 열풍이 불면서 Observable을 통해 BLE 분기처리 지옥을 개선해줄 방법이 생겼으니, 이것이 RxAndroidBle라고 할수 있겠다.


https://github.com/Polidea/RxAndroidBle


RxAndroidBle 는 Apache-2.0 라이센스 이므로 걱정없이 사용하도록 하자,



RxAndroidBLE를 사용하기위한 사전작업을 해보자.


1. App수준의 Gradle에 RxAndroidBle를 implement한다.


implementation "com.polidea.rxandroidble2:rxandroidble:1.6.0"
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.1.16'


2. RxBleClient 에 context를 넘겨준다. 개인적으로는 따로 BLEManager를 만들어서 Application class단에서 appContext를 넘겨주길 추천한다.


RxBleClient rxBleClient = RxBleClient.create(context);


3. 이 라이브러리에는 RxJava와 관련하여 한가지 버그가 있다. 


(링크) https://github.com/Polidea/RxAndroidBle/issues/383


특수한경우에 기기와 connect시, 혹은 connect 실패시 UndeliverableException이 생기는데, 무시해도 되는 Exception이지만 앱 크래시가 나는 경우가 있으니 별도의 무시하는 처리를 하여 주자. 필자는 BLEManager쪽에 추가하였다.


init {
RxJavaPlugins.setErrorHandler {
if (it is UndeliverableException) {
//무시
} else {
throw it
}
}
}



이제 RxAndroidBle 사용 준비가 되었다. 한단계씩 살펴보자.


각 개발자가 사용하는 블루투스 기기는 전부 다를 것임으로, 개념만 익혀보길 바란다.


1. 스캔


/**
* 블루투스 디바이스 검색.
*/
fun scanDevice(deviceScanCallback: DeviceScanCallback) {
mScanSubscription = mRxBleClient.scanBleDevices(

ScanSettings.Builder().build()
)?.subscribe({ scanResult ->
deviceScanCallback.responseServices(scanResult.bleDevice)
}, { throwable ->
if (throwable is BleScanException) {
when (throwable.reason) {
BleScanException.BLUETOOTH_DISABLED -> deviceScanCallback.bluetoothDisabled()
BleScanException.LOCATION_SERVICES_DISABLED -> deviceScanCallback.locationDisabled()
else -> {
deviceScanCallback.onError()
}
}
} else {
deviceScanCallback.onError()
}
})
}


블루투스 스캔시도시 현재 블루투스기능과 위치추적 기능이 켜져 있는지 따로 throwable을 뱉어준다. 필자는 따로 Callback을 정의하여 처리하였다. 이 외에도 추가처리를 해주고 싶다면 RxAndroidBle 라이브러리 깃허브를 찾아보기 바란다.


스캔을 그만하고 싶다면 구독했던 subscription을 dispose시켜주기만 하면 된다.

mScanSubscription?.dispose()




2. 연결


/**
* 디바이스에 연결 시도.
*/
fun connectDevice(macAddress: String, deviceConnectCallback: DeviceConnectCallback) {
mDevice = mRxBleClient.getBleDevice(macAddress)

//연결 시도
mConnectSubscription = mDevice?.establishConnection(false)?.flatMapSingle { rxBleConnection ->
this.mRxBleConnection = rxBleConnection
rxBleConnection.discoverServices()
}?.observeOn(AndroidSchedulers.mainThread())?.subscribe({ rxBleDeviceServices ->
mBluetoothGattServices = rxBleDeviceServices.bluetoothGattServices
deviceConnectCallback.responseServices(macAddress, rxBleDeviceServices.bluetoothGattServices)
}, { throwable ->

if (throwable is BleAlreadyConnectedException) {
deviceConnectCallback.onBluetoothAlreadyConnected()
return@subscribe
}

deviceConnectCallback.onBluetoothConnectCancel()
})
}


macAdress로 부터 connect를 시도하는 메소드이다. RxJava만 알고있다면 어렵지 않은 예제이다. 블루투스를 다루다보면 이미 연결된 기기에 다시 연결을 시도하는 경우가 생겨 그 경우만 추가 처리를 해주었다. 연결실패시, 연결이 끊어질시에 대한 추가 메소드도 구현하였다.


연결도 스캔과 마찬가지로 연결을 끊고싶다면 구독 해제를 해주자.

mConnectSubscription?.dispose()



3. readCharacteristic


RxJava를 이용한 RxAndroidBle 의 가장 큰 장점이라고 볼 수 있는 부분이다. 기존에 구글 BLE 라이브러리는 분기처리를 해야했다면, 이 라이브러리에서는 RxJava를 통해 간단하게 처리된다. 여기서 에러는 어차피 기기와의 연동이 끊어진 경우이므로 위에 연결 부분에서 onBluetoothConnectCancel()이 호출되기 때문에 별도의 처리를 하지 않았다.


/**
* 배터리 상태 요청.
*/
fun getBetteryLevel(batteryLevelCallback: BatteryLevelCallback) {
val diaposable = mRxBleConnection?.readCharacteristic(UUID_BATTERY_VALUE)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe({
batteryLevelCallback.onResponseBatteryLevel(
ValueInterpreter.getIntValue(it, ValueInterpreter.FORMAT_UINT8, 0)
)
}, {
// 에러 처리.
})
}


4. notification (& Indication)


이것또한 readCharacteristic과 마찬가지로 구글 BLE 라이브러리의 복잡성과 차별된 부분이다.  구글 BLE에서 BluetoothGattCallback에서 분기처리해서 구현할 필요 없이 Observable로 모든 처리가 완료된다.


/**
* 노티 등록(indication)
*/
fun startNotification(IndicationCallback: IndicationCallback) {

// 주의. 구글 BLE 라이브러리는 notification 메소드에서 indication까지 처리하지만,

// 여기선 setupNotification과 setupIndication으로 나뉜다.
mIndicationSubscription = mRxBleConnection?.setupIndication(UUID_INDICATION_SERVICE)
?.observeOn(AndroidSchedulers.mainThread())
?.doOnNext { indicationCallback.onStartIndication() }
?.flatMap { indicationObservable -> indicationObservable }
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe({ bytes ->
indicationCallback.onResponse(
// 값 전달
)
}, { throwable ->
mConnectSubscription?.dispose()
})

mBluetoothGattServices?.forEach { service ->
if (service.uuid == UUID_INDICATION_SERVICE) {
service.characteristics.forEach { characteristic ->
if (characteristic.uuid == UUID_INDICATION_CHRACTERISTIC) {
characteristic.descriptors.forEach { descriptor ->
mRxBleConnection?.writeDescriptor(
descriptor,
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
)
}
}
}
}
}
}



여기까지 이해했다면, 굳이 다른 설명이 필요 없어도 RxAndroidBle 개념을 충분히 이해할 수 있다고 생각한다. 다들 블루투스와 함께 분투하기 바란다.

반응형
Comments