Android BLE Development: From Scanning to Data Transfer

<\/script>\n
'; }, get iframeSnippet() { const domain = '{ SITE_DOMAIN }'; const type = '{ embed_type }'; const slug = '{ embed_slug }'; return ''; }, get activeSnippet() { return this.method === 'script' ? this.scriptSnippet : this.iframeSnippet; }, copySnippet() { navigator.clipboard.writeText(this.activeSnippet).then(() => { this.copied = true; setTimeout(() => { this.copied = false; }, 2000); }); } }" @keydown.escape.window="open = false" @click.outside="open = false">

Embed This Widget

Theme


      
    

Widget powered by . Free, no account required.

Building BLE apps with Android's BLE API

| 2 분 읽기

Android BLE Development

Android's BLE API is stable from API 21+ but has well-documented pitfalls around connection management, threading, and MTU negotiation. This guide covers production-grade patterns.

BluetoothLeScanner

Always filter by service ATT">UUID to avoid processing irrelevant advertising packets:

val settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()
val filter = ScanFilter.Builder()
    .setServiceUuid(ParcelUuid.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"))
    .build()
bluetoothAdapter.bluetoothLeScanner.startScan(listOf(filter), settings, callback)

Use SCAN_MODE_LOW_POWER for background scanning. Android 12+ requires BLUETOOTH_SCAN with neverForLocation if scan results are not used for location.

GATT Callback and Threading

Android's GATT callbacks arrive on a Binder thread. Request MTU before service discovery, then chain operations through callbacks:

device.connectGatt(context, false, object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) gatt.requestMtu(512)
    }
    override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
        gatt.discoverServices()
    }
    override fun onCharacteristicChanged(
        gatt: BluetoothGatt, char: BluetoothGattCharacteristic, value: ByteArray
    ) { /* API 33+ — value passed directly, no race */ }
})

Common Issues

Issue Cause Fix
STATUS_133 on connect Android stack bug Retry with exponential backoff
Characteristic reads stale API < 33 value handling Use API 33 onCharacteristicChanged(gatt, char, ByteArray)
Scan stops after 30 min Background restriction Use foreground service
Bonding loop Incorrect CCCD write order Write CCCD after discovery completes

Permissions (Android 12+)

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

Best Practices

  • Single GATT queue: Serialize all GATT operations — parallel calls cause STATUS_133
  • Bonding: Store peripheral MAC; use autoConnect=true for bonded devices
  • Reconnection: Exponential backoff on disconnect (100 ms → 200 ms → ... → 30 s cap)
  • Coroutines: Wrap callbacks with suspendCancellableCoroutine for clean async code

Use the GATT Profile Browser to verify service UUIDs during development. For iOS patterns, see iOS Core Bluetooth Guide.

자주 묻는 질문

Yes, our guides range from beginner introductions to advanced topics. Each guide indicates its difficulty level and prerequisites so you can find the right starting point.