[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-09 21:08:02 +09:00
parent f0befc887a
commit 93ec7e9056
363 changed files with 68333 additions and 64 deletions
@@ -0,0 +1,133 @@
---
id: android-bluetooth-le-scanning
title: Android BLE — Scan / Connect / GATT
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [android, bluetooth, ble, gatt, vibe-coding]
tech_stack: { language: "Kotlin / Bluetooth LE", applicable_to: ["Android"] }
applied_in: []
aliases: [BLE, GATT, characteristic, scan filter, ScanCallback]
---
# Android Bluetooth LE
> **Scan → Connect → Discover Services → Read/Write/Notify** 흐름. 권한 / lifecycle / state machine 까다로움. Nordic / Polidea 같은 라이브러리 권장.
## 📖 핵심 개념
- BLE: low energy. peripherals (sensor, watch).
- GATT: 서비스 / characteristic / descriptor.
- Scan filter: UUID / name / MAC.
- Permission (Android 12+): BLUETOOTH_SCAN, BLUETOOTH_CONNECT.
## 💻 코드 패턴
### Permission
```xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11- -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
```
### Scan
```kotlin
@SuppressLint("MissingPermission") // Permission check 별도
class BleScanner(private val ctx: Context) {
private val adapter = (ctx.getSystemService(BluetoothManager::class.java)).adapter
private val scanner: BluetoothLeScanner? get() = adapter?.bluetoothLeScanner
fun scan(serviceUuid: UUID): Flow<ScanResult> = callbackFlow {
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(serviceUuid)).build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val cb = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
trySend(result)
}
override fun onScanFailed(errorCode: Int) {
close(IllegalStateException("scan failed: $errorCode"))
}
}
scanner?.startScan(listOf(filter), settings, cb)
awaitClose { scanner?.stopScan(cb) }
}
}
```
### Connect + GATT
```kotlin
class BleClient(private val ctx: Context, private val device: BluetoothDevice) {
private var gatt: BluetoothGatt? = null
@SuppressLint("MissingPermission")
suspend fun connect(): BluetoothGatt = suspendCancellableCoroutine { cont ->
gatt = device.connectGatt(ctx, false, object : BluetoothGattCallback() {
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
g.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
g.close()
cont.resumeWithException(Exception("disconnected"))
}
}
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) cont.resume(g)
else cont.resumeWithException(Exception("services discovery failed"))
}
})
}
suspend fun read(serviceUuid: UUID, charUuid: UUID): ByteArray { ... }
suspend fun write(serviceUuid: UUID, charUuid: UUID, data: ByteArray) { ... }
fun notifications(serviceUuid: UUID, charUuid: UUID): Flow<ByteArray> { ... }
fun disconnect() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
```
### MTU / connection priority
```kotlin
gatt.requestMtu(247) // 큰 패킷
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH) // 빠른 throughput
```
## 🤔 의사결정 기준
| 상황 | 도구 |
|---|---|
| Direct BLE | Android API + wrapper |
| 복잡 GATT 시나리오 | Nordic/Polidea/Kable 라이브러리 |
| Companion Device Pairing (Android 8+) | CompanionDeviceManager (자동 pair UI) |
| BLE Beacon scanning | iBeacon / Eddystone 라이브러리 |
| BLE peripheral (앱이 server) | BluetoothGattServer |
## ❌ 안티패턴
- **Scan 무한**: 배터리 폭발 + Android 가 throttle (5회/30s). 짧게 + filter.
- **discoverServices 없이 read/write**: characteristic null.
- **gatt.close() 누락**: 메모리 + 다음 connect 실패.
- **메인 스레드 callback 안에서 무거운 작업**: 다른 callback 못 받음.
- **권한 체크 안 함 Android 12+**: SecurityException.
- **여러 callback 같은 gatt**: 마지막 것만 호출.
- **MTU 협상 안 함**: 작은 패킷 만. 247 바이트 설정.
- **disconnect 후 즉시 connect**: race. delay 또는 state machine.
## 🤖 LLM 활용 힌트
- 권한 + Scan filter + state machine + close 4종 강조.
- 라이브러리 권장 — Kable (Kotlin Multiplatform) 또는 Nordic.
## 🔗 관련 문서
- [[Android_Lifecycle_Aware_Components]]
- [[Native_Battery_Network_Profiling]]