Android Coden
Android 4 min lesen

Bluetooth auf Android: BLE, Berechtigungen und Gerätekompatibilität

Bluetooth verbindet Android-Geräte drahtlos mit Peripheriegeräten und Sensoren. Du lernst BLE, Berechtigungen und Kompatibilitätsgrenzen kennen.

Bluetooth gehört zu den meistgenutzten Konnektivitätsoptionen auf Android-Geräten. Ob du einen Fitness-Tracker abfragst, einen Lautsprecher steuerst oder Sensordaten aus einem IoT-Gerät ausließt – hinter all diesen Szenarien steckt Bluetooth. Als Android-Entwickler musst du verstehen, welche Variante für deinen Anwendungsfall passt, welche Laufzeitberechtigungen du anforderst und wo die Grenzen der Gerätekompatibilität verlaufen.

Was ist das?

Bluetooth ist ein Kurzstrecken-Funkstandard, der auf nahezu jedem Android-Gerät verfügbar ist. Das Android-SDK unterscheidet zwei Hauptvarianten:

  • Bluetooth Classic (BR/EDR): Für kontinuierliche, bandbreitenintensive Verbindungen – Headsets, Lautsprecher, Dateiübertragungen via RFCOMM oder Serial Port Profile.
  • Bluetooth Low Energy (BLE): Optimiert für sporadische, energiesparende Übertragungen – Herzfrequenzsensoren, Smartwatches, Beacon-Systeme, Türschlösser.

Im Android-SDK repräsentiert die Klasse BluetoothAdapter den lokalen Bluetooth-Chip. Über ihn startest du Scans, nimmst Verbindungen entgegen und fragst den Bluetooth-Status ab. Das gesamte API liegt im Paket android.bluetooth. Neuere Ergänzungen wie BluetoothLeScanner, BluetoothLeAdvertiser und der CompanionDeviceManager decken moderne BLE-Szenarien ab, bei denen du einem Benutzer die Gerätekopplung komfortabel im System-Dialog anbieten kannst.

Im Roadmap-Kontext dieser Phase bildet Bluetooth die Brücke zwischen der App-eigenen Datenschicht und physischer Hardware. Die gesammelten Sensordaten landen typischerweise in einem Repository, das sie aufbereitet und an die UI-Schicht weiterreicht – genau wie jede andere Remote- oder Gerätedatenquelle.

Wie funktioniert es?

Ein typischer BLE-Workflow besteht aus drei Phasen:

  1. Scannen: Der BluetoothLeScanner sucht nach Geräten, die sich über Advertising-Pakete ankündigen. Du kannst nach spezifischen GATT-Service-UUIDs filtern, um nur relevante Geräte zu empfangen und Akkuverbrauch zu reduzieren.
  2. Verbinden: Mit BluetoothDevice.connectGatt() öffnest du eine GATT-Verbindung. Die BluetoothGattCallback-Implementierung informiert dich über Verbindungszustände, Service-Entdeckung und Characteristic-Reads/Writes.
  3. Kommunizieren: Du liest und schreibst Characteristics oder aktivierst Notifications via BluetoothGattDescriptor, um Push-Ereignisse vom Gerät zu empfangen, ohne aktiv pollen zu müssen.

Berechtigungen im Wandel

Das Berechtigungsmodell änderte sich mit Android 12 (API 31) grundlegend:

PlattformversionBenötigte Berechtigungen
bis Android 11 (API ≤ 30)BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION
ab Android 12 (API ≥ 31)BLUETOOTH_SCAN, BLUETOOTH_CONNECT, optional BLUETOOTH_ADVERTISE

ACCESS_FINE_LOCATION war bis API 30 beim Scannen Pflicht, weil BLE-Beacons zur Standortbestimmung missbraucht werden können. Ab API 31 kannst du im Manifest mit android:usesPermissionFlags="neverForLocation" erklären, dass du keine Standortdaten auswerten willst – dann entfällt die Location-Berechtigung vollständig. Vergisst du dieses Flag, fordert das System die Location-Berechtigung trotzdem an, was Nutzer oft verwirrt oder sie dazu veranlasst, die Berechtigung zu verweigern.

Gerätekompatibilität

Nicht jedes Gerät unterstützt jede Bluetooth-Fähigkeit. Prüfe zur Laufzeit, ob Bluetooth und BLE verfügbar sind, bevor du irgendeine Bluetooth-Logik ausführst:

val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter == null) {
    // Gerät unterstützt kein Bluetooth
    return
}
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    // Kein BLE-Support – Fallback oder Fehlermeldung zeigen
    return
}

Trage zusätzlich <uses-feature android:name="android.hardware.bluetooth_le" android:required="false" /> ins Manifest ein, wenn deine App auf Geräten ohne BLE installierbar bleiben soll. Ohne diesen Eintrag mit required="false" filtert der Play Store deine App auf BLE-losen Geräten automatisch heraus.

In der Praxis

Hier ein vereinfachtes Beispiel, das nach einem BLE-Herzfrequenzmesser scannt, sich verbindet und Notifications empfängt:

// Scan starten
val scanner = bluetoothAdapter.bluetoothLeScanner
val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        scanner.stopScan(this)
        result.device.connectGatt(context, false, gattCallback)
    }
}
scanner.startScan(scanCallback)

// Verbindungs- und Daten-Callbacks
val gattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(
        gatt: BluetoothGatt, status: Int, newState: Int
    ) {
        when (newState) {
            BluetoothProfile.STATE_CONNECTED -> gatt.discoverServices()
            BluetoothProfile.STATE_DISCONNECTED -> gatt.close() // Ressource freigeben!
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        val hrService = gatt.getService(
            UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")
        )
        val hrChar = hrService?.getCharacteristic(
            UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
        )
        hrChar?.let { gatt.setCharacteristicNotification(it, true) }
    }

    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic
    ) {
        val bpm = characteristic.getIntValue(
            BluetoothGattCharacteristic.FORMAT_UINT8, 1
        )
        // bpm an ViewModel/Repository weiterleiten
    }
}

Typische Stolperfalle: GATT-Verbindungen nicht geschlossen

BluetoothGatt.close() nach dem Trennen zu vergessen ist der häufigste BLE-Bug in Android-Apps. Jede offene GATT-Verbindung belegt eine Systemressource. Android erlaubt nur eine begrenzte Anzahl gleichzeitiger GATT-Clients – auf einigen Geräten kaum mehr als sieben. Rufst du close() nicht auf, schöpft deine App dieses Limit mit jedem Verbindungsversuch weiter aus, bis neue Verbindungen stillschweigend scheitern, ohne dass du eine Fehlermeldung erhältst.

Die Lösung ist einfach: Rufe gatt.close() immer in onConnectionStateChange auf, sobald newState == BluetoothProfile.STATE_DISCONNECTED gilt. Greife danach nicht mehr auf das BluetoothGatt-Objekt zu.

Fazit

Bluetooth – insbesondere BLE – ist eine leistungsfähige Plattform-API, die strikte Aufmerksamkeit bei Berechtigungen, Ressourcenverwaltung und Gerätekompatibilität erfordert. Den solidesten Code bekommst du, wenn du einen Instrumentierungstest schreibst, der eine echte BLE-Verbindung aufbaut und wieder trennt, die GATT-Ressourcen anschließend im Android Studio Memory Profiler beobachtest und deine Berechtigungslogik auf einem API-30- und einem API-31-Gerät parallel prüfst. Nur so erkennst du, ob deine App auf dem gesamten Gerätespektrum deiner Nutzer tatsächlich zuverlässig funktioniert.

Quellen (4)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.