BluetoothAdapter es una clase de sistema Android que representa el adaptador Bluetooth local de un dispositivo. BluetoothAdapter es el punto de entrada para todas las operaciones Bluetooth en Android: activar la radio (enable), escanear dispositivos, gestionar la visibilidad (setScanMode) y obtener información del adaptador (getName, getAddress, getState). La clase está disponible mediante BluetoothManager.getAdapter() (API 18+) o BluetoothAdapter.getDefaultAdapter(). En dispositivos sin módulo Bluetooth, getDefaultAdapter() devuelve null. Según Android Developers, 2026, BluetoothAdapter es un componente obligatorio para cualquier aplicación BLE en Android, desde API 5.
Puntos clave
BluetoothAdapter representa el adaptador Bluetooth físico de un dispositivo Android. Cada dispositivo tiene exactamente un adaptador (excepto Android Automotive con múltiples chips Bluetooth, que usa BluetoothManager.getAdapterList()). BluetoothAdapter encapsula el estado de la radio: STATE_OFF (0), STATE_TURNING_ON (1), STATE_ON (2), STATE_TURNING_OFF (3). El estado se rastrea mediante BroadcastReceiver en ACTION_STATE_CHANGED.
Obtener una instancia de BluetoothAdapter es el primer paso de cualquier aplicación BLE en Android. El método recomendado es mediante BluetoothManager.getAdapter() desde API 18+. La alternativa es el método estático BluetoothAdapter.getDefaultAdapter(), que funciona desde API 5 pero es menos flexible. Ambos métodos devuelven null si el dispositivo no tiene módulo Bluetooth (tabletas solo Wi-Fi, emulador). La comprobación de null es obligatoria: la aplicación debe finalizar correctamente o deshabilitar las funciones BLE.
// Obtener BluetoothAdapter (recomendado)
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
class BluetoothHelper(context: Context) {
private val bluetoothAdapter: BluetoothAdapter?
init {
// Método 1: mediante BluetoothManager (API 18+)
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
as BluetoothManager?
bluetoothAdapter = manager?.adapter
// Método 2: mediante método estático (API 5+)
// val adapter = BluetoothAdapter.getDefaultAdapter()
// Comprobación de null
if (bluetoothAdapter == null) {
// El dispositivo no admite Bluetooth
}
}
// Verificar estado de Bluetooth
fun isBluetoothEnabled(): Boolean {
return bluetoothAdapter?.isEnabled == true
}
// Obtener información del adaptador
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")
)
}
}
La clase BluetoothHelper demuestra cómo obtener BluetoothAdapter mediante BluetoothManager con una comprobación de null posterior. isBluetoothEnabled verifica isEnabled, una condición obligatoria antes de cualquier operación BLE. getAdapterInfo devuelve el nombre del dispositivo, la dirección MAC, el estado y el modo de visibilidad. Importante: en Android 10+ (API 29+), el servicio del sistema devuelve una dirección MAC ficticia (02:00:00:00:00:00) si la aplicación no tiene permisos BLUETOOTH_ADMIN y ACCESS_FINE_LOCATION.
BluetoothAdapter proporciona métodos para controlar la radio Bluetooth. enable() y disable() activan y desactivan el Bluetooth. Ambos métodos requieren el permiso BLUETOOTH_ADMIN y se ejecutan de forma asíncrona: después de llamar a enable(), el sistema inicia el proceso de activación de la radio y el estado se rastrea mediante BroadcastReceiver con la acción BluetoothAdapter.ACTION_STATE_CHANGED. En Android 10+, enable() y disable() requieren privilegios adicionales del sistema: las aplicaciones normales no pueden controlar Bluetooth mediante programación sin un diálogo de usuario.
getState() devuelve el estado actual del adaptador: STATE_OFF (10), STATE_TURNING_ON (11), STATE_ON (12), STATE_TURNING_OFF (13). getAddress() devuelve la dirección MAC del adaptador Bluetooth. En Android 6+, se requiere ACCESS_FINE_LOCATION (o ACCESS_COARSE_LOCATION para API 31+) para solicitar la dirección MAC. En Android 10+, getAddress() devuelve la dirección constante 02:00:00:00:00:00 — la dirección real no está disponible a través de la API pública.
getScanMode() determina el modo de visibilidad del adaptador: SCAN_MODE_NONE (invisible), SCAN_MODE_CONNECTABLE (visible para dispositivos conectados), SCAN_MODE_CONNECTABLE_DISCOVERABLE (visible para todos). El modo de visibilidad está limitado en el tiempo (normalmente 60–300 segundos) por seguridad. Establecer el modo mediante setScanMode() requiere BLUETOOTH_ADMIN y permiso del sistema en Android 10+.
| Método | Descripción | Permiso requerido |
|---|---|---|
| enable() | Activar radio Bluetooth | BLUETOOTH_ADMIN |
| disable() | Desactivar radio Bluetooth | BLUETOOTH_ADMIN |
| getState() | Estado actual del adaptador | BLUETOOTH |
| getAddress() | Dirección MAC del adaptador | BLUETOOTH + ACCESS_FINE_LOCATION (API 23+) |
| getScanMode() | Modo de visibilidad del dispositivo | BLUETOOTH |
| setScanMode() | Establecer modo de visibilidad | BLUETOOTH_ADMIN |
BluetoothAdapter admite dos tipos de escaneo. El escaneo Bluetooth clásico (BR/EDR) se inicia mediante startDiscovery(): descubre dispositivos Bluetooth de todo tipo, incluidos teléfonos y auriculares. Los resultados se devuelven mediante BroadcastReceiver con la acción BluetoothDevice.ACTION_FOUND. startDiscovery() funciona durante 12 segundos y puede cancelarse llamando a cancelDiscovery(). Este método está obsoleto para BLE: use BluetoothLeScanner.
El escaneo BLE mediante BluetoothAdapter utiliza el método obsoleto startLeScan(LeScanCallback). A partir de API 21, Google recomienda usar BluetoothLeScanner, obtenido mediante BluetoothAdapter.getBluetoothLeScanner(). BluetoothLeScanner proporciona una API más flexible: configuración de escaneo mediante ScanSettings (modo, tipo de callback, modo de coincidencia), filtrado mediante ScanFilter (por UUID de servicio, nombre de dispositivo, dirección MAC) y soporte para PendingIntent para escaneo en segundo plano.
// API de escaneo BLE antigua (obsoleta) vs nueva
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.*
class BLEScanner(private val bluetoothAdapter: BluetoothAdapter?) {
// OBSOLETO: startLeScan (API 18+, API 21)
@Suppress("DEPRECATION")
fun legacyScan() {
bluetoothAdapter?.startLeScan { device, rssi, scanRecord ->
print("Found (LE Scan): $device.name, RSSI: $rssi")
}
}
// NUEVO: BluetoothLeScanner (API 21+)
fun modernScan() {
val scanner = bluetoothAdapter?.bluetoothLeScanner
?: return
// Configuración de escaneo
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.build()
// Filtrar por servicio (UUID de frecuencia cardíaca)
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
)
// Iniciar escaneo
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")
}
})
}
}
La clase BLEScanner compara el obsoleto startLeScan y el moderno BluetoothLeScanner. En legacyScan, el LeScanCallback recibe BluetoothDevice, RSSI y scanRecord sin procesar. En modernScan, se usan ScanSettings con modo LOW_LATENCY (máxima velocidad de detección) y ScanFilter para filtrar por UUID del servicio de frecuencia cardíaca (0x180D). ScanCallback proporciona onScanResult con un objeto ScanResult que contiene información ampliada: nombre, RSSI, datos de publicidad, tipo de conexión.
BluetoothManager es un servicio de sistema Android, introducido en API 18 (Android 4.3), para gestionar operaciones Bluetooth. Antes de API 18, la única forma de obtener BluetoothAdapter era el método estático getDefaultAdapter(). BluetoothManager proporciona: adapter — una instancia de BluetoothAdapter, getConnectedDevices() — una lista de dispositivos conectados, getDevicesMatchingConnectionStates() — filtrado por estado. BluetoothManager también se usa para obtener BluetoothLeScanner en APIs antiguas.
Ventajas de BluetoothManager sobre llamar directamente a BluetoothAdapter.getDefaultAdapter(): la aplicación no depende de un singleton estático, el gestor respeta el contexto (Activity/Application), lo que es importante para escenarios multi-cuenta de Android Enterprise. En Android Automotive con múltiples chips Bluetooth, BluetoothManager.getAdapterList() devuelve todos los adaptadores disponibles — BluetoothAdapter.getDefaultAdapter() devuelve solo el primero.
// Usar BluetoothManager para BLE
class BLEConnection(context: Context) {
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val adapter: BluetoothAdapter? = bluetoothManager.adapter
// Obtener lista de dispositivos BLE conectados
fun getConnectedDevices(): List<BluetoothDevice> {
return bluetoothManager.getConnectedDevices(
BluetoothProfile.GATT
)
}
// Filtrar dispositivos por estado
fun getDevicesByState(states: IntArray): List<BluetoothDevice> {
return bluetoothManager.getDevicesMatchingConnectionStates(
BluetoothProfile.GATT, states
)
}
// Verificar soporte BLE en el dispositivo
fun isBLESupported(): Boolean {
return adapter != null && context.packageManager
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
// Solicitar activación de Bluetooth mediante diálogo del sistema
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
}
}
La clase BLEConnection usa BluetoothManager para acceder a BluetoothAdapter y obtener la lista de dispositivos GATT conectados. isBLESupported verifica la presencia de radio BLE mediante PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) — una comprobación importante para dispositivos con Bluetooth Classic sin BLE. requestEnableBluetooth muestra el diálogo del sistema para activar Bluetooth (ACTION_REQUEST_ENABLE), sin requerir permiso BLUETOOTH_ADMIN — esta es la única forma legal de activar Bluetooth en Android 10+ sin una aplicación de sistema.
Los permisos para BluetoothAdapter han evolucionado con cada versión de Android. En Android 6–11 (API 23–30), se requieren BLUETOOTH, BLUETOOTH_ADMIN y ACCESS_FINE_LOCATION para el escaneo BLE. En Android 12+ (API 31+), Google dividió los permisos: ACCESS_FINE_LOCATION se reemplaza por BLUETOOTH_SCAN (escaneo), BLUETOOTH_CONNECT (conexión), BLUETOOTH_ADVERTISE (publicidad). Para la detección de dispositivos BLE, BLUETOOTH_SCAN es suficiente, no se requiere ubicación.
Tabla de permisos por versión de Android:
| Operación | API 23–30 | API 31+ |
|---|---|---|
| Escaneo BLE | ACCESS_FINE_LOCATION | BLUETOOTH_SCAN (sin ubicación) |
| Conexión BLE | ACCESS_FINE_LOCATION | BLUETOOTH_CONNECT |
| Publicidad BLE | ACCESS_FINE_LOCATION | BLUETOOTH_ADVERTISE |
| Activar/desactivar | BLUETOOTH_ADMIN | BLUETOOTH_ADMIN (sistema) |
| Obtener dirección MAC | ACCESS_FINE_LOCATION | BLUETOOTH_CONNECT (dirección ficticia) |
En Android 12+, todos los permisos Bluetooth son permisos de tiempo de ejecución: deben solicitarse en tiempo de ejecución mediante ActivityResultContracts.RequestMultiplePermissions. BLUETOOTH_SCAN y BLUETOOTH_ADVERTISE pertenecen al grupo NEARBY_DEVICES, BLUETOOTH_CONNECT pertenece al mismo grupo. Los permisos BLUETOOTH y BLUETOOTH_ADMIN permanecen en el manifiesto para compatibilidad con API < 31, pero para API 31+ se ignoran — Google requiere especificar explícitamente los nuevos permisos.
// Solicitar permisos Bluetooth en 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+: BLE-
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADVERTISE
)
} else {
// Android 6-11: BLE
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
)
}
}
// Verificar todos los permisos
fun hasPermissions(context: Context): Boolean {
return getRequiredPermissions().all { permission ->
ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED
}
}
}
La clase PermissionHelper devuelve el conjunto correcto de permisos según el nivel de API. En Android 12+, se usan BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE sin ubicación. En Android 6–11, todavía se requiere ACCESS_FINE_LOCATION para el escaneo BLE. El desarrollador debe considerar ambos escenarios al solicitar permisos mediante ActivityResultContracts o RxPermissions.
Ejemplo completo de una aplicación BLE en Kotlin que usa BluetoothAdapter para escanear, conectar y leer datos de un dispositivo BLE. El ejemplo cubre la verificación de permisos, la obtención del adaptador, el escaneo mediante BluetoothLeScanner y la conexión mediante BluetoothDevice.connectGatt.
// Gestor BLE completo en Kotlin
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. Descubrimiento de servicios
fun canScan(): Boolean {
return adapter?.isEnabled == true
&& scanner != null
&& PermissionHelper(context).hasPermissions(context)
}
// 2. con filtro
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. Detener escaneo
fun stopScanning() {
scanner?.stopScan(object : ScanCallback() {})
}
// 4. Conectar al dispositivo 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) {
// Servicios encontrados, se pueden leer características
}
},
BluetoothDevice.TRANSPORT_LE
)
}
// 5. Liberar recursos
fun disconnect() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
La clase BLEManager unifica el ciclo BLE completo en Android: verificación del adaptador y permisos (canScan), escaneo mediante BluetoothLeScanner con ScanSettings (startScanning), conexión mediante BluetoothDevice.connectGatt con TRANSPORT_LE (connectToDevice) y liberación de recursos (disconnect). Todas las operaciones BLE se ejecutan en el hilo de la interfaz de usuario: Android llama a las devoluciones de llamada BluetoothGattCallback en el hilo principal. Para tareas BLE intensivas, se recomienda trasladar las operaciones GATT a un HandlerThread en segundo plano.
Preguntas frecuentes
BluetoothAdapter es una clase que representa el adaptador Bluetooth local de un dispositivo Android. Se obtiene mediante BluetoothManager.getAdapter() (API 18+) o BluetoothAdapter.getDefaultAdapter(). Proporciona métodos para activar/desactivar Bluetooth, escanear dispositivos, gestionar la visibilidad y obtener información del adaptador. Devuelve null en dispositivos sin módulo Bluetooth.
La razón es la ausencia de radio Bluetooth en el dispositivo. Es típico en tabletas solo Wi-Fi, emulador Android y Android TV sin Bluetooth. Verifique getDefaultAdapter() como null al iniciar la aplicación y deshabilite las funciones BLE si el adaptador está ausente. Una alternativa es verificar mediante PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) para una detección más precisa.
BluetoothLeScanner (API 21+) es la API moderna para escaneo BLE con soporte para ScanFilter, ScanSettings y PendingIntent. startLeScan (API 18+) es un método obsoleto de BluetoothAdapter que acepta LeScanCallback con un conjunto de datos limitado. Google recomienda BluetoothLeScanner para todos los proyectos nuevos, permite filtrar por UUID, configurar el modo de consumo de energía y funcionar en segundo plano mediante PendingIntent.
En Android 12+ (API 31), el escaneo BLE requiere BLUETOOTH_SCAN, la conexión requiere BLUETOOTH_CONNECT y la publicidad requiere BLUETOOTH_ADVERTISE. El permiso de ubicación ACCESS_FINE_LOCATION ya no es necesario para BLE. En Android 6–11, se requiere ACCESS_FINE_LOCATION. Todos los permisos se solicitan en tiempo de ejecución mediante ActivityResultContracts.
En Android 10+, la activación programática de Bluetooth sin diálogo del sistema solo está disponible para aplicaciones de sistema con permiso BLUETOOTH_PRIVILEGED. Las aplicaciones normales deben usar Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) y startActivityForResult: el usuario confirma la activación en el diálogo del sistema. BLUETOOTH_ADMIN en el manifiesto no otorga derechos enable() en Android 10+.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también