BluetoothAdapter — systemowa klasa Androida reprezentująca lokalny adapter Bluetooth urządzenia. BluetoothAdapter to punkt wejścia dla wszystkich operacji Bluetooth na Androidzie: włączanie radia (enable), skanowanie urządzeń, zarządzanie widzialnością (setScanMode), pobieranie informacji o adapterze (getName, getAddress, getState). Klasa jest dostępna przez BluetoothManager.getAdapter() (API 18+) lub BluetoothAdapter.getDefaultAdapter(). Na urządzeniach bez modułu Bluetooth getDefaultAdapter() zwraca null. Według Android Developers, 2026, BluetoothAdapter to obowiązkowy komponent każdej aplikacji BLE na Androida, począwszy od API 5.
Najważniejsze
BluetoothAdapter reprezentuje fizyczny adapter Bluetooth urządzenia z Androidem. Każde urządzenie ma dokładnie jeden adapter (wyjątkiem jest Android Automotive z wieloma układami Bluetooth, gdzie używany jest BluetoothManager.getAdapterList()). BluetoothAdapter enkapsuluje stan radia: STATE_OFF (0), STATE_TURNING_ON (1), STATE_ON (2), STATE_TURNING_OFF (3). Stan jest śledzony przez BroadcastReceiver na ACTION_STATE_CHANGED.
Pobranie instancji BluetoothAdapter to pierwszy krok każdej aplikacji BLE na Androida. Zalecana metoda — przez BluetoothManager.getAdapter() od API 18+. Alternatywa — statyczna metoda BluetoothAdapter.getDefaultAdapter(), która działa od API 5, ale jest mniej elastyczna. Obie metody zwracają null, jeśli urządzenie nie ma modułu Bluetooth (tablety Wi-Fi-only, emulator). Sprawdzenie null jest obowiązkowe: aplikacja musi poprawnie zakończyć działanie lub wyłączyć funkcje BLE.
// Pobieranie BluetoothAdapter (zalecane)
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
class BluetoothHelper(context: Context) {
private val bluetoothAdapter: BluetoothAdapter?
init {
// Metoda 1: przez BluetoothManager (API 18+)
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
as BluetoothManager?
bluetoothAdapter = manager?.adapter
// Metoda 2: przez metodę statyczną (API 5+)
// val adapter = BluetoothAdapter.getDefaultAdapter()
// Sprawdzenie null
if (bluetoothAdapter == null) {
// Urządzenie nie obsługuje Bluetooth
}
}
// Sprawdź stan Bluetooth
fun isBluetoothEnabled(): Boolean {
return bluetoothAdapter?.isEnabled == true
}
// Pobierz informacje o adapterze
fun getAdapterInfo(): Map<String, String> {
return mapOf(
"name" to (bluetoothAdapter?.name ?: "N/A"),
"address" to (bluetoothAdapter?.address ?: "N/A"),
"state" to (bluetoothAdapter?.state?.toString() ?: "N/A"),
"scanMode" to (bluetoothAdapter?.scanMode?.toString() ?: "N/A")
)
}
}
Klasa BluetoothHelper demonstruje pobieranie BluetoothAdapter przez BluetoothManager z następną kontrolą null. isBluetoothEnabled sprawdza isEnabled — obowiązkowy warunek przed jakimikolwiek operacjami BLE. getAdapterInfo zwraca nazwę urządzenia, adres MAC, stan i tryb widzialności. Ważne: na Android 10+ (API 29+) system service zwraca fikcyjny adres MAC (02:00:00:00:00:00), jeśli aplikacja nie ma uprawnienia BLUETOOTH_ADMIN i ACCESS_FINE_LOCATION.
BluetoothAdapter udostępnia metody zarządzania radiem Bluetooth. enable() i disable() włączają i wyłączają Bluetooth. Obie metody wymagają uprawnienia BLUETOOTH_ADMIN i są wykonywane asynchronicznie: po wywołaniu enable() system uruchamia proces włączania radia, stan jest śledzony przez BroadcastReceiver z akcją BluetoothAdapter.ACTION_STATE_CHANGED. Od Androida 10+ enable() i disable() wymagają dodatkowego uprawnienia systemowego — zwykłe aplikacje nie mogą programowo zarządzać Bluetooth bez okna dialogowego użytkownika.
getState() zwraca bieżący stan adaptera: STATE_OFF (10), STATE_TURNING_ON (11), STATE_ON (12), STATE_TURNING_OFF (13). getAddress() zwraca adres MAC adaptera Bluetooth. Na Android 6+ do zapytania o adres MAC wymagane jest ACCESS_FINE_LOCATION (lub ACCESS_COARSE_LOCATION dla API 31+). Na Android 10+ getAddress() zwraca stały adres 02:00:00:00:00:00 — rzeczywisty adres jest niedostępny przez publiczne API.
getScanMode() określa tryb widzialności adaptera: SCAN_MODE_NONE (niewidoczny), SCAN_MODE_CONNECTABLE (widoczny dla podłączonych), SCAN_MODE_CONNECTABLE_DISCOVERABLE (widoczny dla wszystkich). Tryb widzialności jest ograniczony czasowo (zazwyczaj 60–300 sekund) dla bezpieczeństwa. Ustawienie trybu przez setScanMode() wymaga BLUETOOTH_ADMIN i uprawnienia systemowego na Android 10+.
| Metoda | Opis | Wymagane uprawnienie |
|---|---|---|
| enable() | Włączenie radia Bluetooth | BLUETOOTH_ADMIN |
| disable() | Wyłączenie radia Bluetooth | BLUETOOTH_ADMIN |
| getState() | Bieżący stan adaptera | BLUETOOTH |
| getAddress() | Adres MAC adaptera | BLUETOOTH + ACCESS_FINE_LOCATION (API 23+) |
| getScanMode() | Tryb widzialności urządzenia | BLUETOOTH |
| setScanMode() | Ustawienie trybu widzialności | BLUETOOTH_ADMIN |
BluetoothAdapter obsługuje dwa typy skanowania. Klasyczne skanowanie Bluetooth (BR/EDR) uruchamiane jest przez startDiscovery() — wykrywa urządzenia Bluetooth wszystkich typów, w tym telefony i słuchawki. Wyniki są zwracane przez BroadcastReceiver z akcją BluetoothDevice.ACTION_FOUND. startDiscovery() działa 12 sekund i może być anulowane przez wywołanie cancelDiscovery(). Ta metoda jest przestarzała dla BLE — używaj BluetoothLeScanner.
Skanowanie BLE przez BluetoothAdapter wykonuje się przestarzałą metodą startLeScan(LeScanCallback). Od API 21 Google zaleca używanie BluetoothLeScanner, uzyskiwanego przez BluetoothAdapter.getBluetoothLeScanner(). BluetoothLeScanner udostępnia bardziej elastyczne API: konfiguracja skanowania przez ScanSettings (tryb, callback type, match mode), filtrowanie przez ScanFilter (po UUID usługi, nazwie urządzenia, adresie MAC) i obsługa PendingIntent dla skanowania w tle.
// Stare (przestarzałe) vs nowe API skanowania BLE
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.*
class BLEScanner(private val bluetoothAdapter: BluetoothAdapter?) {
// PRZESTARZAŁE: startLeScan (API 18+, API 21)
@Suppress("DEPRECATION")
fun legacyScan() {
bluetoothAdapter?.startLeScan { device, rssi, scanRecord ->
print("Found (LE Scan): $device.name, RSSI: $rssi")
}
}
// NOWE: BluetoothLeScanner (API 21+)
fun modernScan() {
val scanner = bluetoothAdapter?.bluetoothLeScanner
?: return
// Ustawienia skanowania
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.build()
// Filtruj po usłudze (UUID tętna)
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
)
// Rozpocznij skanowanie
scanner.startScan(filters, settings, object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
val rssi = result.rssi
print("Found (BLE Scanner): ${device.name}, RSSI: $rssi, address: ${device.address}")
}
override fun onScanFailed(errorCode: Int) {
print("Scan error: $errorCode")
}
})
}
}
Klasa BLEScanner porównuje przestarzały startLeScan i nowoczesny BluetoothLeScanner. W legacyScan callback LeScanCallback otrzymuje BluetoothDevice, RSSI i surowy scanRecord. W modernScan używane jest ScanSettings z trybem LOW_LATENCY (maksymalna szybkość wykrywania) i ScanFilter do filtrowania po UUID usługi pulsu (Heart Rate Service 0x180D). ScanCallback udostępnia onScanResult z obiektem ScanResult zawierającym rozszerzone informacje: nazwę, RSSI, dane reklamowe, typ połączenia.
BluetoothManager — systemowa usługa Androida, wprowadzona w API 18 (Android 4.3), do zarządzania operacjami Bluetooth. Przed API 18 jedynym sposobem uzyskania BluetoothAdapter była statyczna metoda getDefaultAdapter(). BluetoothManager udostępnia: adapter — instancję BluetoothAdapter, getConnectedDevices() — listę podłączonych urządzeń, getDevicesMatchingConnectionStates() — filtrowanie po stanie. BluetoothManager jest także używany do uzyskania BluetoothLeScanner na starszych API.
Zalety BluetoothManager nad bezpośrednim wywołaniem BluetoothAdapter.getDefaultAdapter(): aplikacja nie zależy od statycznego singletona, menedżer uwzględnia kontekst (Activity/Application), co jest ważne w scenariuszach wielokontowych Android Enterprise. Na Android Automotive z wieloma układami Bluetooth, BluetoothManager.getAdapterList() zwraca wszystkie dostępne adaptery — BluetoothAdapter.getDefaultAdapter() zwraca tylko pierwszy.
// Używanie BluetoothManager dla BLE
class BLEConnection(context: Context) {
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val adapter: BluetoothAdapter? = bluetoothManager.adapter
// Pobierz listę podłączonych urządzeń BLE
fun getConnectedDevices(): List<BluetoothDevice> {
return bluetoothManager.getConnectedDevices(
BluetoothProfile.GATT
)
}
// Filtruj urządzenia po stanie
fun getDevicesByState(states: IntArray): List<BluetoothDevice> {
return bluetoothManager.getDevicesMatchingConnectionStates(
BluetoothProfile.GATT, states
)
}
// Sprawdź obsługę BLE na urządzeniu
fun isBLESupported(): Boolean {
return adapter != null && context.packageManager
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
// Poproś o włączenie Bluetooth przez okno systemowe
fun requestEnableBluetooth(activity: MainActivity) {
if (adapter?.isEnabled == false) {
val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
activity.startActivityForResult(intent, REQUEST_ENABLE_BT)
}
}
companion object {
const val REQUEST_ENABLE_BT = 1001
}
}
Klasa BLEConnection używa BluetoothManager do dostępu do BluetoothAdapter i pobrania listy podłączonych urządzeń GATT. isBLESupported sprawdza obecność radia BLE przez PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) — ważna kontrola dla urządzeń z Bluetooth Classic bez BLE. requestEnableBluetooth wyświetla systemowe okno dialogowe włączenia Bluetooth (ACTION_REQUEST_ENABLE), nie wymagając uprawnienia BLUETOOTH_ADMIN — to jedyny legalny sposób włączenia Bluetooth na Android 10+ bez aplikacji systemowej.
Uprawnienia dla BluetoothAdapter ewoluowały z każdą wersją Androida. Na Android 6–11 (API 23–30) do skanowania BLE wymagane są BLUETOOTH, BLUETOOTH_ADMIN i ACCESS_FINE_LOCATION. Na Android 12+ (API 31+) Google podzielił uprawnienia: ACCESS_FINE_LOCATION został zastąpiony przez BLUETOOTH_SCAN (skanowanie), BLUETOOTH_CONNECT (podłączenie), BLUETOOTH_ADVERTISE (reklama). Do wyszukiwania urządzeń BLE wystarczy BLUETOOTH_SCAN, lokalizacja nie jest wymagana.
Tabela uprawnień według wersji Androida:
| Operacja | API 23–30 | API 31+ |
|---|---|---|
| Skanowanie BLE | ACCESS_FINE_LOCATION | BLUETOOTH_SCAN (bez lokalizacji) |
| Podłączenie do BLE | ACCESS_FINE_LOCATION | BLUETOOTH_CONNECT |
| Reklama BLE | ACCESS_FINE_LOCATION | BLUETOOTH_ADVERTISE |
| Włączanie/wyłączanie | BLUETOOTH_ADMIN | BLUETOOTH_ADMIN (systemowe) |
| Pobieranie adresu MAC | ACCESS_FINE_LOCATION | BLUETOOTH_CONNECT (fikcyjny adres) |
Na Android 12+ wszystkie uprawnienia Bluetooth są uprawnieniami czasu wykonania — należy je wymagać w czasie wykonania przez ActivityResultContracts.RequestMultiplePermissions. BLUETOOTH_SCAN i BLUETOOTH_ADVERTISE należą do grupy NEARBY_DEVICES, BLUETOOTH_CONNECT — do tej samej grupy. Uprawnienia BLUETOOTH i BLUETOOTH_ADMIN pozostają w manifeście dla zgodności z API < 31, ale dla API 31+ są ignorowane — Google wymaga wyraźnego określenia nowych uprawnień.
// Prośba o uprawnienia Bluetooth na Android 12+
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
class PermissionHelper(context: Context) {
fun getRequiredPermissions(): Array<String> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+: skanowanie BLE
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADVERTISE
)
} else {
// Android 6-11: skanowanie BLE
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
)
}
}
// Sprawdź wszystkie uprawnienia
fun hasPermissions(context: Context): Boolean {
return getRequiredPermissions().all { permission ->
ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED
}
}
}
Klasa PermissionHelper zwraca poprawny zestaw uprawnień w zależności od poziomu API. Na Android 12+ używane są BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE bez lokalizacji. Na Android 6–11 do skanowania BLE nadal wymagane jest ACCESS_FINE_LOCATION. Deweloper musi uwzględnić oba scenariusze przy wymaganiu uprawnień przez ActivityResultContracts lub RxPermissions.
Pełny przykład aplikacji BLE w Kotlinie używającej BluetoothAdapter do skanowania, łączenia i odczytu danych z urządzenia BLE. Przykład obejmuje sprawdzanie uprawnień, pobieranie adaptera, skanowanie przez BluetoothLeScanner i łączenie przez BluetoothDevice.connectGatt.
// Pełny menedżer BLE w Kotlinie
class BLEManager(private val context: Context) {
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val adapter: BluetoothAdapter? = bluetoothManager.adapter
private var scanner: BluetoothLeScanner? = adapter?.bluetoothLeScanner
private var gatt: BluetoothGatt? = null
// 1. Odkrywanie usług
fun canScan(): Boolean {
return adapter?.isEnabled == true
&& scanner != null
&& PermissionHelper(context).hasPermissions(context)
}
// 2. Skanowanie z filtrem
fun startScanning(callback: (BluetoothDevice, Int) -> Unit) {
if (!canScan()) return
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0)
.build()
scanner?.startScan(null, settings, object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
callback(result.device, result.rssi)
}
})
}
// 3. Zatrzymaj skanowanie
fun stopScanning() {
scanner?.stopScan(object : ScanCallback() {})
}
// 4. Połącz z urządzeniem BLE
fun connectToDevice(device: BluetoothDevice) {
if (adapter?.isEnabled != true) return
gatt = device.connectGatt(
context,
false,
object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
// Znaleziono usługi, można odczytać charakterystyki
}
},
BluetoothDevice.TRANSPORT_LE
)
}
// 5. Zwolnij zasoby
fun disconnect() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
Menedżer BLEManager łączy pełny cykl BLE na Androidzie: sprawdzenie adaptera i uprawnień (canScan), skanowanie przez BluetoothLeScanner z ScanSettings (startScanning), łączenie przez BluetoothDevice.connectGatt z TRANSPORT_LE (connectToDevice), zwalnianie zasobów (disconnect). Wszystkie operacje BLE są wykonywane na wątku UI — Android wywołuje callbacki BluetoothGattCallback na głównym wątku. Do wydajnych zadań BLE zaleca się przeniesienie operacji GATT do tła HandlerThread.
Często zadawane pytania
BluetoothAdapter — klasa reprezentująca lokalny adapter Bluetooth urządzenia z Androidem. Pobierana przez BluetoothManager.getAdapter() (API 18+) lub BluetoothAdapter.getDefaultAdapter(). Udostępnia metody włączania/wyłączania Bluetooth, skanowania urządzeń, zarządzania widzialnością i pobierania informacji o adapterze. Zwraca null na urządzeniach bez modułu Bluetooth.
Przyczyną jest brak radia Bluetooth na urządzeniu. Typowe dla tabletów Wi-Fi-only, emulatora Androida i Android TV bez Bluetooth. Sprawdzaj getDefaultAdapter() na null przy uruchomieniu aplikacji i wyłączaj funkcje BLE, jeśli adapter jest nieobecny. Alternatywa — sprawdzenie przez PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) w celu dokładniejszego określenia.
BluetoothLeScanner (API 21+) — nowoczesne API do skanowania BLE z obsługą ScanFilter, ScanSettings i PendingIntent. startLeScan (API 18+) — przestarzała metoda BluetoothAdapter przyjmująca LeScanCallback z ograniczonym zestawem danych. BluetoothLeScanner jest zalecany przez Google dla wszystkich nowych projektów, umożliwia filtrowanie po UUID, konfigurację trybu zużycia energii i pracę w tle przez PendingIntent.
Na Android 12+ (API 31) do skanowania BLE wymagane jest BLUETOOTH_SCAN, do łączenia — BLUETOOTH_CONNECT, do reklamy — BLUETOOTH_ADVERTISE. Uprawnienie do lokalizacji ACCESS_FINE_LOCATION nie jest już wymagane dla BLE. Na Android 6–11 konieczne jest ACCESS_FINE_LOCATION. Wszystkie uprawnienia są wymagane w czasie wykonania przez ActivityResultContracts.
Od Androida 10+ programowe włączenie Bluetooth bez systemowego okna dialogowego jest dostępne tylko dla aplikacji systemowych z uprawnieniem BLUETOOTH_PRIVILEGED. Zwykłe aplikacje muszą użyć Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) i startActivityForResult — użytkownik potwierdza włączenie w systemowym oknie dialogowym. BLUETOOTH_ADMIN w manifeście nie daje prawa do enable() na Android 10+.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również