Android Bluetooth 之经典蓝牙

819 阅读4分钟

Android Bluetooth 之经典蓝牙

  • 蓝牙开发分为经典蓝牙(Bluetooth Classic,标准蓝牙)和 BLE 低功耗蓝牙(Bluetooth Low Energy,低能耗蓝牙)两种,经典蓝牙速度快,功耗高,适合大数据传输,低功耗蓝牙能耗低,但数据速率较低,适用于传感器数据采集
  • 基于 RFCOMM 协议(Radio Frequency Communication),通过 BluetoothSocket 进行连接和数据传输(一个 UUID 唯一标识对应一个服务,比如 SPP_UUID 串口通信、A2DP_UUID 音频传输和 HEADFREE_UUID 免提等),SPP 串行端口配置文件(Serial Port Profile),允许两个设备建立 P2P 点对点连接,模拟 RS232 串口,使用 RFCOMM 协议进行数据传输
  • 可以同时使用传统蓝牙和低功耗蓝牙,但是不能同时扫描(搜索、发现、查询)低功耗蓝牙设备和传统蓝牙设备

基本流程

  • 1 权限声明、权限申请、硬件检测与蓝牙启用
  • 2 BluetoothAdapter#startDiscovery 蓝牙设备发现(配合 BroadcastReceiver 接收发现新设备的结果)
  • 3 BluetoothSocket#connect 蓝牙设备连接
  • 4 数据读写
  • 5 释放清理资源

1 权限声明、权限申请、硬件检测与蓝牙启用

权限相关

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- 高版本额外需要位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 可选硬件声明,声明设备支持 BLE,防止不支持的设备安装 -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

检查硬件支持情况

//使用 PackageManager 来检查设备是否支持 BLE 功能
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)

蓝牙启用

val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager.adapter //获取蓝牙适配器
if (bluetoothAdapter?.isEnabled != true) {
    //询问用户开启蓝牙
    Log.e(TAG, "请先开启蓝牙")
    val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
    startActivityForResult(enableBtIntent, REQUEST_CODE_ENABLE_BT)
    return
}

2 蓝牙设备发现

private val REQUEST_CODE_ENABLE_BT = 1
private val SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") //固定的 SPP 协议 UUID,连接串口设备(标准串口协议)
private val DISCOVERY_TIMEOUT = 10_000L //设备发现超时时间
private var bluetoothAdapter: BluetoothAdapter? = null
//
private val handler = Handler(Looper.getMainLooper())
//接收发现新设备的结果的广播接收器
private val discoveryBroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (BluetoothDevice.ACTION_FOUND == intent.action) {
            //发现新设备
            val bluetoothDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                intent.getParcelableExtra(
                    BluetoothDevice.EXTRA_DEVICE,
                    BluetoothDevice::class.java
                )
            } else {
                intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
            }
            //处理发现到的设备(比如展示在列表中供选择)
            if (bluetoothDevice?.bondState != BluetoothDevice.BOND_BONDED) {
                Log.e(
                    TAG,
                    "onReceive: name:${bluetoothDevice?.name} address:${bluetoothDevice?.address}"
                )
                //发起配对(需用户确认)
                val pairIntent = Intent(BluetoothDevice.ACTION_PAIRING_REQUEST)
                pairIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, bluetoothDevice)
                startActivity(pairIntent)
            }
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED == intent.action) {
            //设备发现完成回调
            stopBtDiscovery()
        }
    }
}
private fun startBtDiscovery() {
    val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    bluetoothAdapter = bluetoothManager.adapter
    if (bluetoothAdapter?.isEnabled != true) {
        //开启蓝牙(需用户确认)
        Log.e(TAG, "startBtDiscovery: 请先开启蓝牙")
        val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
        startActivityForResult(enableBtIntent, REQUEST_CODE_ENABLE_BT)
        return
    }
    //查询已绑定(配对)设备
    val bondedDeviceSet = bluetoothAdapter?.bondedDevices
    bondedDeviceSet?.let {
        if (bondedDeviceSet.size > 0) {
            for (bondedDevice in bondedDeviceSet) {
                //处理已绑定设备(可以用列表展示出来)
            }
        }
    }
    //注册广播接收器
    val intentFilter = IntentFilter()
    intentFilter.addAction(BluetoothDevice.ACTION_FOUND)
    intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
    registerReceiver(discoveryBroadcastReceiver, intentFilter)
    //启动发现
    stopBtDiscovery()
    bluetoothAdapter?.startDiscovery()
    //定时停止设备发现
    handler.postDelayed({ stopBtDiscovery() }, DISCOVERY_TIMEOUT)
}
//停止设备发现
private fun stopBtDiscovery() {
    if (bluetoothAdapter?.isDiscovering() == true) {
        bluetoothAdapter?.cancelDiscovery()
    }
}

3 蓝牙设备连接、4 数据读写

private fun connectBtDevice(macAddress: String) {
    stopBtDiscovery()
    //
    Thread {
        var inputStream: InputStream? = null
        var outputStream: OutputStream? = null
        var bluetoothSocket: BluetoothSocket? = null
        try {
            val bluetoothDevice = bluetoothAdapter?.getRemoteDevice(macAddress)
            bluetoothDevice.createBond() //手动触发配对
            //创建 Rfcomm 套接字(传统蓝牙的核心通信方式)
            bluetoothSocket = bluetoothDevice?.createRfcommSocketToServiceRecord(SPP_UUID)
            bluetoothSocket?.connect() //阻塞式连接,需在子线程执行
            Log.e(TAG, "connectBtDevice: 连接成功")
            inputStream = bluetoothSocket?.inputStream
            outputStream = bluetoothSocket?.outputStream
            //读取数据(实际情况通常需要死循环不断读取数据  while (true) { })
            inputStream?.let {
                val bufferBytes = ByteArray(1024)
                var length: Int
                while (inputStream.read(bufferBytes).also { length = it } != -1) {
                    val receivedData = String(bufferBytes, 0, length)
                    Log.e(TAG, "收到数据: $receivedData")
                }
            }
            //写入数据
            val message = "Hello Bluetooth!"
            outputStream?.write(message.toByteArray())
        } catch (e: IOException) {
            e.printStackTrace()
            Log.e(TAG, "connectBtDevice: 连接失败:${e.message}")
        } finally {
            inputStream?.close()
            outputStream?.close()
            bluetoothSocket?.close()
        }
    }.start()
}
//服务端启动监听
fun startServer() {
    Thread {
        var inputStream: InputStream? = null
        var outputStream: OutputStream? = null
        var bluetoothServerSocket: BluetoothServerSocket? = null
        var bluetoothSocket: BluetoothSocket? = null
        try {
            //服务端 Socket(UUID 需与客户端一致)
            bluetoothServerSocket = bluetoothAdapter?.listenUsingRfcommWithServiceRecord("BtServer",SPP_UUID)
            //接受客户端连接
            bluetoothSocket = bluetoothServerSocket?.accept()
            Log.d("BluetoothServer", "客户端已连接")
            inputStream = bluetoothSocket?.inputStream
            outputStream = bluetoothSocket?.outputStream
            //读取数据(实际情况通常需要死循环不断读取数据  while (true) { })
            inputStream?.let {
                val bufferBytes = ByteArray(1024)
                var length: Int
                while (inputStream.read(bufferBytes).also { length = it } != -1) {
                    val receivedData = String(bufferBytes, 0, length)
                    Log.e(TAG, "收到数据: $receivedData")
                }
            }
            //写入数据
            val message = "Hello Bluetooth!"
            outputStream?.write(message.toByteArray())
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            inputStream?.close()
            outputStream?.close()
            bluetoothSocket?.close()
            bluetoothServerSocket?.close()
        }
    }.start()
}
//服务端设置为可发现模式(让设备可被检测)
fun discoverable() {
    val discoverableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
    //默认不传是 120 秒,传 0 代表一直可被检测到(官方不建议使用),最长支持 3600 秒
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)
    startActivity(discoverableIntent)
}

5 释放清理资源

fun releaseDiscovery() {
    unregisterReceiver(discoveryBroadcastReceiver)
    bluetoothAdapter?.cancelDiscovery()
    bluetoothAdapter = null
}

总结

  • 经典蓝牙基于客户端-服务端架构,使用 RFCOMM 通信开启模拟串口,通过 SPP_UUID 进行交互
  • 通过 BluetoothSocket 的 InputStream 和 OutputStream 输入输出流读写数据,从而实现双向通信