蓝牙使用详解

376 阅读7分钟

目前蓝牙连接可分为两种模式,即:传统蓝牙模式和低功耗模式。区别在于,传统蓝牙模式对资源消耗比较大,适用于大流量数据传输要求场景;低功耗模式则比较节能,满足对于能耗方面要求比较高的设备。

无论哪种模式,都要经过如下几个步骤来实现设备间的无线蓝牙传输:  

  1. 蓝牙相关权限申请 

  2. 设备蓝牙功能是否可用

  3. 发现设备

  4. 连接设备

  5. 数据传输

  6. 销毁连接

1. BLE低功耗蓝牙模式–GATT服务

顾名思义,低功耗蓝牙旨在降低蓝牙功耗,为功率要求比较严格的BLE设备提供蓝牙连接支持,适用于设备间少量数据传输。如:近程传感器、心率检测仪、健康设备等。

低功耗蓝牙连接基本步骤和传统蓝牙一样,只不过某些步骤方式稍显不同;

1.1 蓝牙权限

参照 2.1 蓝牙权限

1.2 蓝牙功能可用性

参照 2.2 蓝牙功能可用性

1.3 发现设备

我们使用BluetoothLeScanner类提供的startScan方法执行蓝牙扫描动作,通过ScanFilter设置特定过滤条件获取指定的蓝牙设备。

扫描非常耗电,因此应遵循以下准则:

  • 找到所需设备后,立即停止扫描。

  • 绝对不进行循环扫描,并设置扫描时间限制。之前可用的设备可能已超出范围,继续扫描会耗尽电池电量。

@SuppressLint("MissingPermission")

private fun scanLeDevice(enable: Boolean) {

    if(mScanning) {

        Toast.makeText(applicationContext, "当前正在扫描,请稍后重试", Toast.LENGTH_SHORT).show()

        return

    }

    when (enable) {

        true -> {

            Log.i(tag, "[scanLeDevice]: 开始蓝牙扫描")

            // Stops scanning after a pre-defined scan period.

            handler.postDelayed({

                Log.i(tag, "[scanLeDevice]: 一定时间后自动结束蓝牙扫描")

                mScanning = false

                bluetoothLeScanner?.stopScan(leScanCallback)

            }, SCAN_PERIOD)

            mScanning = true

            bluetoothLeScanner?.startScan(listOf(getScanFilter()), getScanSettings(), leScanCallback)

        }

        else -> {

            Log.i(tag, "[scanLeDevice]: 手动结束蓝牙扫描")

            mScanning = false

            bluetoothLeScanner?.stopScan(leScanCallback)

        }

    }

}

 

 

//扫描结果回调

private val leScanCallback = object : ScanCallback() {

    override fun onScanFailed(errorCode: Int) {

        super.onScanFailed(errorCode)

        Log.e(tag, "[onScanFailed]: 蓝牙设备扫描失败, errorCode: $errorCode")

    }

 

    @SuppressLint("MissingPermission")

    override fun onScanResult(callbackType: Int, result: ScanResult?) {

        super.onScanResult(callbackType, result)

        val device = result?.device

        if (device != null && device.name?.isNotBlank() == true) {

            //将符合条件的设备添加到集合列表里

            val item = scanStateList.find { it.device.address == device.address }

            if (item != null) {

                item.rssi = result.rssi

            } else {

                scanStateList.add(ScanStateResult(device, STATE_NONE, result.rssi))

            }

        } else {

            Log.d(tag, "[onScanResult]:ignore scan result: $callbackType, $result")

        }

        Log.i(tag, "[onScanResult]: callbackType: $callbackType, device name: ${result?.device?.name}, address: ${result?.device?.address}, rssi: ${result?.rssi}")

    }

 

    @SuppressLint("MissingPermission")

    override fun onBatchScanResults(results: MutableList<ScanResult>?) {

        super.onBatchScanResults(results)

        results?.forEach {

            Log.i(tag, "[onBatchScanResults]: device name: ${it.device?.name}, address: ${it.device?.address}")

        }

    }

}

 

 

//配置扫描筛选条件

private fun getScanFilter(): ScanFilter{

    return ScanFilter.Builder().

    setServiceUuid(ParcelUuid.fromString(SERVICE_UUID)).

    build()

}

 

 

/**

 * 扫描配置

 * 如:模式配置等

 */

private fun getScanSettings(): ScanSettings {

    return ScanSettings.Builder().

    setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).

    build()

}

 

1.4 连接设备

与 BLE 设备交互的第一步便是连接到 GATT 服务器。更具体地说,是连接到设备上的 GATT 服务器。使用BluetoothDevice类的connectGatt方法连接设备GATT服务器。

bluetoothGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

    it?.device?.connectGatt(this, false, getGattCallback(), BluetoothDevice.TRANSPORT_LE)

} else {

    it?.device?.connectGatt(this, false, getGattCallback())

}
engine = TransportEngine(bluetoothGatt)

连接过程中,在相应的客户端GATT结果回调中,需要根据设备的配置文件信息,执行响应的连接认证数据传输,才能确保连接成功,过程大体如下:

连接设备 → 发现并连接服务 → 执行连接认证 → 连接成功

/**

 * Gatt服务连接结果回调

 */

fun BLEActivity.getGattCallback(): BluetoothGattCallback {

    return object : BluetoothGattCallback() {

        /**

         * 连接状态回调

         */

        @SuppressLint("MissingPermission")

        override fun onConnectionStateChange(

            gatt: BluetoothGatt,

            status: Int,

            newState: Int

        ) {

            val intentAction: String

            when (newState) {

                BluetoothProfile.STATE_CONNECTED -> {

                    intentAction = ACTION_GATT_CONNECTED

                    connectionState = STATE_CONNECTED

                    //发送连接状态广播

                    broadcastUpdate(intentAction)

                    //发现服务

                    Log.i(TAG, "Connected to GATT server.")

                    Log.i(TAG, "Attempting to start service discovery: " +

                            gatt.discoverServices())

                }

                BluetoothProfile.STATE_DISCONNECTED -> {

                    intentAction = ACTION_GATT_DISCONNECTED

                    connectionState = STATE_DISCONNECTED

                    Log.i(TAG, "Disconnected from GATT server.")

                    //发送连接状态广播

                    broadcastUpdate(intentAction)

                }

            }

        }

 

        // New services discovered

        @SuppressLint("MissingPermission")

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {

            when (status) {

                BluetoothGatt.GATT_SUCCESS -> {

                    broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)

                    val gattService = gatt.getService(UUID.fromString(SERVICE_UUID))

                    Log.i(TAG, "Discovered service: " + (gattService != null))

                    if (gattService != null) {

                        for (characteristic in gattService.characteristics) {

                            Log.i(TAG, "Characteristic uuid: " + characteristic.uuid)

                            if (characteristic.uuid == WRITE_UUID) {

                                //获取数据写入特征,用于后续数据发送

                                engine?.setWriteCharacteristic(characteristic)

                            } else if (characteristic.uuid == NOTIFY_UUID) {

                                //设置设备可接受数据

                                val success =

                                    gatt.setCharacteristicNotification(characteristic, true)

                                if (success) {

                                    //向远程设备写入特征描述

                                    for (dp in characteristic.descriptors) {

                                        if (dp != null) {

                                            if (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {

                                                dp.value =

                                                    BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE

                                            } else if (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {

                                                dp.value =

                                                    BluetoothGattDescriptor.ENABLE_INDICATION_VALUE

                                            }

                                            gatt.writeDescriptor(dp)

                                        }

                                    }

                                }

                            }

                        }

                        connectionState = STATE_CONNECTED

                    } else {

                        Log.e(TAG, "device not support service")

                        clearGatt()

                        connectionState = STATE_DISCONNECTED

                    }

                }

                else -> {

                    Log.e(TAG, "onServicesDiscovered received: $status")

                    clearGatt()

                    connectionState = STATE_DISCONNECTED

                }

            }

        }

 

        // Result of a characteristic read operation

        override fun onCharacteristicRead(

            gatt: BluetoothGatt,

            characteristic: BluetoothGattCharacteristic,

            status: Int

        ) {

            when (status) {

                BluetoothGatt.GATT_SUCCESS -> {

                    Log.i(TAG, "[onCharacteristicRead]")

//                    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)

                }

            }

        }

 

        private fun broadcastUpdate(action: String) {

            val intent = Intent(action)

            sendBroadcast(intent)

        }

 

        private fun broadcastUpdate(action: String, characteristic: BluetoothGattCharacteristic) {

            val intent = Intent(action)

 

            // This is special handling for the Heart Rate Measurement profile. Data

            // parsing is carried out as per profile specifications.

            when (characteristic.uuid) {

                UUID.fromString(SERVICE_UUID) -> {

                    val flag = characteristic.properties

                    val format = when (flag and 0x01) {

                        0x01 -> {

                            Log.i(TAG, "Heart rate format UINT16.")

                            BluetoothGattCharacteristic.FORMAT_UINT16

                        }

                        else -> {

                            Log.i(TAG, "Heart rate format UINT8.")

                            BluetoothGattCharacteristic.FORMAT_UINT8

                        }

                    }

                    val heartRate = characteristic.getIntValue(format, 1)

                    Log.i(TAG, String.format("Received heart rate: %d", heartRate))

                    intent.putExtra(EXTRA_DATA, (heartRate).toString())

                }

                else -> {

                    // For all other profiles, writes the data formatted in HEX.

                    val data: ByteArray? = characteristic.value

                    if (data?.isNotEmpty() == true) {

                        val hexString: String = data.joinToString(separator = " ") {

                            String.format("%02X", it)

                        }

                        intent.putExtra(EXTRA_DATA, "$data\n$hexString")

                    }

                }

 

            }

            sendBroadcast(intent)

        }

 

        override fun onCharacteristicWrite(

            gatt: BluetoothGatt?,

            characteristic: BluetoothGattCharacteristic,

            status: Int

        ) {

            super.onCharacteristicWrite(gatt, characteristic, status)

            Log.i(TAG, "onCharacteristicWrite: " + characteristic.uuid.toString() + ", " + status)

        }

 

        /**

         * 接收数据

         */

        override fun onCharacteristicChanged(

            gatt: BluetoothGatt?,

            characteristic: BluetoothGattCharacteristic

        ) {

            Log.i(TAG, "onCharacteristicChanged: $characteristic")

            val value = characteristic.value

            engine?.onReceiveData(value)

        }

 

        /**

         * 向远程设备写入特征描述结果回调

         */

        override fun onDescriptorWrite(

            gatt: BluetoothGatt?,

            descriptor: BluetoothGattDescriptor?,

            status: Int

        ) {

            Log.i(TAG, "onDescriptorWrite: $status")

            val address: Array<String>? = currentDevice?.address?.split(":")?.toTypedArray()

            val code = address?.size?.let { ByteArray(it) }

            var i = address?.size?.minus(1)

            var t = 0

            if (i != null) {

                while (i >= 0) {

                    val v = ByteUtil.stringToHex(address!![i])

                    code?.set(t++, (v xor 0x0A5.toByte()))

                    i--

                }

            }

            //执行认证

            engine!!.verify(code)

        }

    }

}

1.5 数据传输与接收

1.5.1 接收数据

BLE设备会在特征发生改变时收到通知;

首先需要设置接收通知:

lateinit var bluetoothGatt: BluetoothGatt

lateinit var characteristic: BluetoothGattCharacteristic

var enabled: Boolean = true

...

bluetoothGatt.setCharacteristicNotification(characteristic, enabled)

val uuid: UUID = UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)

val descriptor = characteristic.getDescriptor(uuid).apply {

    value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE

}

bluetoothGatt.writeDescriptor(descriptor)

然后就可以接收数据通知:

/**

 * 接收数据

 */

override fun onCharacteristicChanged(

    gatt: BluetoothGatt?,

    characteristic: BluetoothGattCharacteristic

) {

    Log.i(TAG, "onCharacteristicChanged: $characteristic")

    val value = characteristic.value

    engine?.onReceiveData(value)

}

1.5.2 写入数据

private final BluetoothGatt gatt;

private BluetoothGattCharacteristic writeCharacteristic;

/**

 * 发送数据

 * @param data

 * @return

 */

@SuppressLint("MissingPermission")

public boolean write(byte[] data) {

    writeCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);

    writeCharacteristic.setValue(data);

    return gatt.writeCharacteristic(writeCharacteristic);

}

1.6 销毁连接

完成使用后,释放资源;

/**

*清除Gatt

*/

private void clearGatt() {

    Log.d(TAG, "clearGatt " + gatt);

    if (gatt != null) {

        gatt.disconnect();

        gatt.close();

        gatt = null;

    }

}

2. 传统蓝牙模式

2.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" />

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

BLUETOOTH、BLUETOOTH_ADMIN:允许用户发现和连接设备

ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION:获取位置信息,对于适配Android9(API28)或者更低版本的应用,声明ACCESS_COARSE_LOCATION权限,否则声明ACCESS_FINE_LOCATION权限。

其中ACCESS_FINE_LOCATION权限属于敏感权限,需要代码中手动申请:

//所需权限

private val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)

/**

*申请权限

*/

private fun permission(permissions: Array<String>) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

        requestPermissions(permissions, PERMISSION_REQUEST_CODE)

    }

}

 

2.2 蓝牙功能可用性

2.2.1 设置蓝牙

private var bluetoothAdapter: BluetoothAdapter? = null

//初始化蓝牙

bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter

if (bluetoothAdapter == null) {

    // Device doesn't support Bluetooth

    Toast.makeText(applicationContext, "设备不支持蓝牙功能", Toast.LENGTH_SHORT).show()

}

2.2.2 检测蓝牙功能是否开启

//检查蓝牙是否开启,未开启需申请打开系统的蓝牙开启Activity

//新版registerForActivityResult方法使用如下,老版已废弃

if (bluetoothAdapter?.isEnabled == false) {

    val launcher = registerForActivityResult(object : ActivityResultContract<Bundle, Int>(){

        override fun createIntent(context: Context, input: Bundle?): Intent {

            //弹窗提示框,开启蓝牙

            val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)

            if (input != null) enableBtIntent.putExtras(input)

            return enableBtIntent

        }

 

        override fun parseResult(resultCode: Int, intent: Intent?): Int {

            return resultCode

        }

    }

    ) {

        Log.i(tag, "resultCode: $it")

    }

    launcher.launch(null)

}

2.3 发现设备

使用BluetoothAdapter的startDiscovery方法执行发现过程,该方法是一个异步方法,返回布尔值表示发现进程是否开启。

发现进程是一个非常耗资源的操作,本轮扫描结束后应该及时销毁

这里需要注意一个重要问题:在取消发现进程前,必须先解除广播接收器,才能有效取消该进程;否则会影响重新启动发现进程


//注册扫描接收器

registerBleReceiver()

//开始扫描蓝牙

bluetoothAdapter?.startDiscovery()

...

/**

 * 蓝牙扫描结果接收器

 */

private val receiver = object : BroadcastReceiver() {

    @SuppressLint("MissingPermission")

    override fun onReceive(context: Context?, intent: Intent?) {

        when(intent?.action) {

            BluetoothDevice.ACTION_FOUND -> {

                intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE).let {

                    val name = it?.name

                    val address = it?.address

                    Log.i(tag, "[broadcast onReceive]: device name: $name")

                    Log.i(tag, "[broadcast onReceive]: device address: $address")

                }

            }

            BluetoothAdapter.ACTION_DISCOVERY_STARTED -> Log.i(tag, "[broadcast onReceive]: 开始扫描蓝牙")

            BluetoothAdapter.ACTION_DISCOVERY_FINISHED-> {

                //扫描结束及时取消发现进程

                val result = cancelBleDiscovery()

                Log.i(tag, "[broadcast onReceive]: 蓝牙扫描结束: $result")

            }

        }

    }

}

 

 

/**

 * 取消蓝牙扫描进程

 * 必须先解除广播接收器,才能有效取消该进程,

 * 以便于重新扫描

 */

@SuppressLint("MissingPermission")

private fun cancelBleDiscovery(): Boolean? {

    //解除广播接收器

    unregisterReceiver(receiver)

    //关闭蓝牙扫描进程

    return bluetoothAdapter?.cancelDiscovery()

}

2.4 连接设备、传输数据

官方提供了一些通用的蓝牙配置文件,适用于设备间蓝牙无线连接通信接口规范,比如:免提、耳机、A2DP、健康设备等蓝牙无线连接,但是这里要注意,健康设备协议HDP、MCAP在新版api里面已经不被支持,建议使用GATT等低功耗模式实现蓝牙无线连接。

2.4.1 作为蓝牙服务器运行, 用于监听连接请求,响应连接

/**

 * 作为蓝牙服务器端运行

 * 用于监听连接请求,响应连接

 */

@SuppressLint("MissingPermission")

private inner class AcceptThread : Thread() {

    private val TAG = "AcceptThread"

 

    private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {

        bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID)

    }

 

    override fun run() {

        // Keep listening until exception occurs or a socket is returned.

        var shouldLoop = true

        while (shouldLoop) {

            val socket: BluetoothSocket? = try {

                mmServerSocket?.accept()

            } catch (e: IOException) {

                Log.e(TAG, "Socket's accept() method failed", e)

                shouldLoop = false

                null

            }

            socket?.also {

                //新线程从远端设备读取数据和写入数据

                MyBluetoothService(Handler(Looper.getMainLooper()), it)

                mmServerSocket?.close()

                shouldLoop = false

            }

        }

    }

 

    // Closes the connect socket and causes the thread to finish.

    fun cancel() {

        try {

            mmServerSocket?.close()

        } catch (e: IOException) {

            Log.e(TAG, "Could not close the connect socket", e)

        }

    }

}

2.4.2 客户端发起连接请求

/**

 * 发起蓝牙连接的客户端示例

 */

@SuppressLint("MissingPermission")

private inner class ConnectThread(device: BluetoothDevice) : Thread() {

    private val TAG = "ConnectThread"

    private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {

        device.createRfcommSocketToServiceRecord(MY_UUID)

    }

 

    public override fun run() {

        // Cancel discovery because it otherwise slows down the connection.

        bluetoothAdapter?.cancelDiscovery()

 

        mmSocket?.use { socket ->

            // Connect to the remote device through the socket. This call blocks

            // until it succeeds or throws an exception.

            socket.connect()

 

            // The connection attempt succeeded. Perform work associated with

            // the connection in a separate thread.

            //新线程从远端设备读取数据和写入数据

            MyBluetoothService(Handler(Looper.getMainLooper()), socket)

 

        }

    }

 

    // Closes the client socket and causes the thread to finish.

    fun cancel() {

        try {

            mmSocket?.close()

        } catch (e: IOException) {

            Log.e(TAG, "Could not close the client socket", e)

        }

    }

}

2.4.3 传输数据

/**

 * 从连接设备读取数据和向连接设备写入数据示例

 */

class MyBluetoothService(

    // handler that gets info from Bluetooth service

    private val handler: Handler,

    private val mmSocket: BluetoothSocket

) {

    private val TAG = "MyBluetoothService"

 

    // Defines several constants used when transmitting messages between the

    // service and the UI.

    private val MESSAGE_READ: Int = 0

    private val MESSAGE_WRITE: Int = 1

    private val MESSAGE_TOAST: Int = 2

    // ... (Add other message types here as needed.)

 

    private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() {

 

        private val mmInStream: InputStream = mmSocket.inputStream

        private val mmOutStream: OutputStream = mmSocket.outputStream

        private val mmBuffer: ByteArray = ByteArray(1024) // mmBuffer store for the stream

 

        override fun run() {

            var numBytes: Int // bytes returned from read()

 

            // Keep listening to the InputStream until an exception occurs.

            while (true) {

                // Read from the InputStream.

                numBytes = try {

                    mmInStream.read(mmBuffer)

                } catch (e: IOException) {

                    Log.d(TAG, "Input stream was disconnected", e)

                    break

                }

 

                // Send the obtained bytes to the UI activity.

                val readMsg = handler.obtainMessage(

                    MESSAGE_READ, numBytes, -1,

                    mmBuffer)

                readMsg.sendToTarget()

            }

        }

 

        // Call this from the main activity to send data to the remote device.

        fun write(bytes: ByteArray) {

            try {

                mmOutStream.write(bytes)

            } catch (e: IOException) {

                Log.e(TAG, "Error occurred when sending data", e)

 

                // Send a failure message back to the activity.

                val writeErrorMsg = handler.obtainMessage(MESSAGE_TOAST)

                val bundle = Bundle().apply {

                    putString("toast", "Couldn't send data to the other device")

                }

                writeErrorMsg.data = bundle

                handler.sendMessage(writeErrorMsg)

                return

            }

 

            // Share the sent message with the UI activity.

            val writtenMsg = handler.obtainMessage(

                MESSAGE_WRITE, -1, -1, mmBuffer)

            writtenMsg.sendToTarget()

        }

 

        // Call this method from the main activity to shut down the connection.

        fun cancel() {

            try {

                mmSocket.close()

            } catch (e: IOException) {

                Log.e(TAG, "Could not close the connect socket", e)

            }

        }

    }

}

2.4.4 使用配置文件连接设备

如耳机、A2DP设备可使用API支持的配置文件连接设备,涉及相关类:BluetoothHeadset、BluetoothA2dp类等,基本步骤如下:

1. 获取默认适配器

2. 设置 BluetoothProfile.ServiceListener。此侦听器会在 BluetoothProfile 客户端连接到服务或断开服务连接时向其发送通知。

3. 使用 getProfileProxy() 与配置文件所关联的配置文件代理对象建立连接。在以下示例中,配置文件代理对象是一个 BluetoothHeadset 实例。

4. 在 onServiceConnected() 中,获取配置文件代理对象的句柄。

5. 获得配置文件代理对象后,您可以用其监视连接状态,并执行与该配置文件相关的其他操作。

var bluetoothHeadset: BluetoothHeadset? = null

 

// Get the default adapter

val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()

 

private val profileListener = object : BluetoothProfile.ServiceListener {

 

    override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {

        if (profile == BluetoothProfile.HEADSET) {

            bluetoothHeadset = proxy as BluetoothHeadset

        }

    }

 

    override fun onServiceDisconnected(profile: Int) {

        if (profile == BluetoothProfile.HEADSET) {

            bluetoothHeadset = null

        }

    }

}

 

// Establish connection to the proxy.

bluetoothAdapter?.getProfileProxy(context, profileListener, BluetoothProfile.HEADSET)

 

// ... call functions on bluetoothHeadset

 

// Close proxy connection after use.

bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset)

2.5 销毁蓝牙

始终记得在退出使用的时候,关闭蓝牙连接

/**关闭连接*/

fun cancel() {

            try {

                socket.close()

            } catch (e: IOException) {

                Log.e(TAG, "Could not close the connect socket", e)

            }

 }

 

 

/**

*加入使用配置文件连接设备,调用如下方法关闭连接,

*这里以耳机为例

*/

bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset)