안드로이드 롤리팝 업데이트와 BLE
급속히 보급되는 BLE 장치들과 트렌드에 비해 안드로이드 기기의 BLE 성능은 실망스럽다는 반응이 꽤 많았습니다. 개발 측면에 있어서도 애플의 안정적인 BLE 성능과 stack 구조에 비해서 불안정하고 기능도 빈약했습니다. 그래서 왜 그런지 해외 자료를 좀 뒤져보니 이런저런 이유가 있다고하네요. 그리고 BLE 에 관해서 만큼은 가급적 롤리팝 이후 버전을 사용할 것을 권고하고 있습니다. 왜 그런지를 정리해 보겠습니다.
원래 안드로이드에서는 리눅스에서 일반적으로 사용하던 BlueZ 라는 오픈소스 블루투스 스택을 사용해 왔습니다. 하지만 구글에서는 BlueZ 스택을 제거하기로 결정을 내리는데, 문제는 BlueZ 가 GPL 라이센스를 따르기 때문이었습니다. BlueZ 의 GPL 라이센스는 블루투스 스택과 관련된 다른 소스들이 GPL 라이센스를 따를 것을 강제하기 때문에 구글이 자체적으로 유지하던 라이센스와 충돌을 일으켰습니다. 이걸 해결하기 위해서는 다른 프로세스와 BlueZ 스택을 분리해야 하는 등의 어려움이 따르는 상황이었습니다.
그래서 결국 구글은 BlueZ를 제거하고 Brooadcom과 함께 Bluedroid 를 개발하게 됩니다. 안드로이드 킷캣(KitKat) 버전까지 말이 많았던 BLE 성능이나 기능의 부실함은 Bluedroid 가 아직 안정화되지 않은 상태임을 말해줍니다. 실제 안드로이드에서 BLE를 사용하기 위해서는 안드로이드 젤리빈(v4.3) 이상이 되어야 할 정도였으니 애플에 비해서 BLE의 행보가 엄청 더뎠던건 사실입니다. 주변 환경은 빠르게 BLE 위주로 흘러가고 있는데두요…
이게 어느정도 해결된 버전이 안드로이드 롤리팝(v5.x, Lollipop) 입니다. 안드로이드 롤리팝에서는 BLE 주변장치(Peripheral device) 기능 문제, 스캐닝 성능, 저전력 동작 기능을 지원합니다. 이 기능들 중 일부는 하드웨어 칩셋에 의존적이기 때문에 기기에 따라서 롤리팝이라 하더라도 이런 기능들이 지원되지 않는 경우도 있긴 합니다. 하지만 적어도 이전보다는 향상된 BLE 성능을 롤리팝에서는 기대할 수 있습니다.
좀 더 구체적으로 롤리팝 이후 개선된 안드로이드 BLE 기능들을 살펴보면 다음과 같습니다.
Support for BLE Peripheral and Broadcaster Modes
안드로이드 킷캣(~v4.4)까지 안드로이드 기기는 BLE Central mode 만 지원했습니다. 이 모드에서는 스마트 폰은 장치들을 스캔(scan)하고 주변장치(peripheral device)와 연결을 맺을 수 있지만 안드로이드가 주변장치의 역할은 할 수가 없었죠. 안드로이드 롤리팝에서는 이게 가능합니다.
BLE 기기는 서로를 인식하기 위해 주변장치가 Advertising packet을 주변에 쏘고 중앙(Central) 장치가 이를 수집하도록 역할을 정의하고 있습니다. 따라서 주변장치 역할을 한다는 얘기는 안드로이드 기기가 Advertising packet 을 주변에 Broadcast 한다는 의미이기도 합니다. 우리가 흔히 얘기하는 Beacon, iBeacon 장치가 사실 별게 아니라 Advertising packet을 주변에 계속 broadcasting 하는 장치입니다. 종합하자면 이제 안드로이드 휴대폰도 스스로 비컨 장치가 되어서 Beacon, iBeacon packet을 쏴댈 수 있습니다.
물론 Advertising 과정 이후 기기간 연결도 가능합니다. BLE 에서 Central/Peripheral 장치는 연결 이후의 동작 시나리오가 틀리기 때문에 안드로이드 기기가 Peripheral 장치로 연결된다면 이후의 진행 과정도 당연히 Central 일 때와 달라집니다.
Concurrent Advertisements
안드로이드가 advertising packet을 송출할 수 있게 됐으니, 이걸 사용하려는 앱이 다수가 생길겁니다. 따라서 주변에 Advertising packet을 송출할 때 여러 앱이 원하는 데이터를 어떻게 실어 보낼 것인지가 문제가 됩니다. 롤리팝에서는 여러 앱이 요청한 데이터를 한데 몰아넣는 대신 각각의 분리된 패킷으로 송출될 수 있도록 concurrent advertisement를 지원합니다. 그래서 각각의 앱 데이터는 고유의 advertising packet 구조와 데이터를 갖습니다. 이 점은 개발자가 패킷이 어떻게 전송되는지, 다른 앱의 데이터와 어떻게 상호작용할지에 대해 고민하지 않도록 해줍니다.
Peripheral Advertising Latency Balancing
또 다른 중요한 변화는 advertising packet이 얼마나 자주 송출될 것인지를 결정하는 부분입니다. 패킷 전송 간격이 짧을수록 스마트폰 배터리는 광탈할 것이지만 다른 장치가 내 장치를 발견하는 시간이 줄어듭니다. 반대로 전송 간격이 길어지면 발견 시간은 조금 늦어져도 배터리를 아낍니다. 이 때문에 롤리팝에서는 advertising의 속도와 저전력, 응답 속도의 적절한 밸런싱을 합니다.
Improved Scanning
보통 백그라운드 프로세스로 실행되는 BLE 스캐닝(scanning) 과정은 두 가지 이유로 굉장히 중요한 이슈입니다.
하나는 스캐닝 과정이 배터리를 굉장히 빠르게 소모하는 과정이라는 점입니다. BLE 스캐닝을 시작하면 안드로이드 기기는 대기 모드가 해제되고 배터리를 빠르게 소모합니다. 때문에 가급적 사용자는 BLE를 꺼두려고 합니다. BLE를 끄면 당연히 주변장치와의 연결도 해제되겠죠. (실제 바로 해제되는건 아니지만 뭐 비슷하니까…) 때문에 다시 주변장치와 BLE 통신을 하려면 시간도 꽤 걸리고 사용자 조작이 필요해지기도 합니다. 불편한 시나리오가 되버리죠.
그래서 롤리팝은 스캐닝 과정을 하위 레이어로 내려서 실행하고 스마트 폰이 대기 모드로 들어갈 수 있도록 변경했습니다. 따라서 사용자가 굳이 블루투스 장치를 사용하지 않더라도 꺼둘 필요가 없도록 했습니다.
또 하나 스캐닝에서 중요한 점은 안드로이드가 슬립 상태에 들어가더라도 주변장치는 계속 advertising 패킷을 쏴댄다는 점입니다. 그래서 전혀 필요없는 데이터라도 앱은 불필요하게 깨어나서 이 패킷들을 봐야 합니다. 이 문제를 해결하기위해 롤리팝에서는 필터를 사용할 수 있습니다. 필터를 이용하면 앱은 특정한 포맷이나 특정 데이터와 매칭되는 패킷이 왔을 때만 깨어날 수 있습니다. 최근의 안드로이드 릴리즈 버전에서는 더 나은 필터링을 제공하며 불필요한 백그라운드 프로세스 수행을 줄이도록 도와줍니다. 이는 배터리를 더욱 아낄 수 있는 방법이기도 합니다.
안드로이드 v5.0 이상에서의 블루투스 앱 개발
안드로이드 폰들은 서로 다른 기능들을 가진 다양한 칩셋들을 사용하기 때문에 롤리팝 기기라 하더라도 앞서 설명한 기능들이 통합되어 적용되지는 않습니다. 하지만 적어도 안드로이드 플랫폼 수준에서는 롤리팝 버전이 이전 버전과는 크게 차이가 남을 알 수 있습니다.
안드로이드 파편화로 인한 기능지원 문제는 Nexus 5에서 찾아볼 수 있습니다. Nexus 5 는 BLE 칩셋을 가지고 있고 중앙장치/주변장치(central/peripheral) 역할을 수행할 수 있지만 이 둘을 동시에 할 수는 없습니다. 따라서 사용자가 주변장치로서 advertising을 실행한다면 다른 장치와의 연결이 끊어져 버립니다. 이런 이슈때문에 구글에서는 Nexus 5 에서 주변장치 모드를 빼버렸습니다.(링크 참고) 안드로이드 롤리팝 initial release 에서는 포함되었던 기능이 Nexus 5 업데이트에서 빠져 혼란이 일기도 했습니다.
블루투스 v4.1 을 지원하는 Nexus 6 모델에서는 두 가지 역할을 동시에 수행할 수 있어 이런 제한이 없습니다.
안드로이드 롤리팝 버전의 BLE 스택은 하위 버전 호환성 (backwards compatibility)을 유지하고 있습니다. 따라서 젤리빈(v4.3), 킷캣(v4.4) 버전에서 작성된 코드도 롤리팝에서 동작합니다. 롤리팝 기기 보급률을 감안했을 때 좀 더 다양한 기기에서 BLE 기능을 수행하기 위해서는 새로운 기능에 대한 동경을 잠시 접어야 할 수도 있겠지만, 이런 제약에서 자유로운 상태라면 새 기능들을 충분히 활용하는 것이 여러모로 이점이 많습니다.
롤리팝의 새로운 BLE 기능을 사용할 때 소스상의 가장 큰 차이점은 BluetoothLeScanner 클래스 부분입니다. (위 이미지 참고)
새롭게 제공되는 BluetoothLeScanner 클래스를 실제 사용하는 소스코드를 보면 아래와 같습니다. (전체 소스는 링크를 참고하세요)
private void scanLeDevice(final boolean enable) { if (enable) { mHandler.postDelayed(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mLEScanner.stopScan(mScanCallback); } } }, SCAN_PERIOD); if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mLEScanner.startScan(filters, settings, mScanCallback); } } else { if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mLEScanner.stopScan(mScanCallback); } } }
다른 부분에서는 큰 차이가 없지만 실제 스캐닝을 수행하는 mLEScanner.startScan() 메서드 호출 부분이 차이가 납니다.
if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mLEScanner.startScan(filters, settings, mScanCallback); }
주변 장치가 스캔 될 때마다 호출될 scan callback 인스턴스를 넘겨주는 부분은 그대로지만 2개의 파라미터 – ScanFilter, ScanSettings를 더 줄 수 있습니다.
이 중 가장 파워풀 한 ScanFilter 는 다음 항목에 기반해서 스캔된 장치들을 거르도록 해줍니다.
- Service UUIDs which identify the Bluetooth GATT services running on the device.
- Name of remote Bluetooth LE device.
- Mac address of the remote device.
- Service data which is the data associated with a service.
- Manufacturer specific data which is the data associated with a particular manufacturer.
ScanFilter를 통해 앱은 불필요한 스캐닝 결과 처리 과정을 줄이고 하위 레이어의 전력 소비 부담을 줄여줄 수 있습니다.
ScanFilter를 거쳐 장치가 검색되면 connection 과정을 거쳐 페어링을 수행하고 BluetoothGattCallback 을 통해 데이터를 주고 받을 수 있습니다.
참고자료: