版本声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16355400.html
前言
请注意,这里说的是经典蓝牙开发。就是手机设置中的蓝牙开发(代码的使用场景是一些Launcher开发与物联设备开发),并不是ble蓝牙开发。使用kotlin与建造者模式封装,直接复制完整封装代码就可以使用(包含了数据类与枚举类),有大量的反射代码(kotlin第一次启动反射会有较大耗时,后续调用正常)。
使用
另外注意,BLE设备都有已经配对概念并且有已经连接与未连接的概念。 但是手机设备是只有配对的概念,没有连接与未连接的概念。 请注意区分,在UI设计上手机设备不要设计连接状态
override fun onResume() { super.onResume() mBtHelp.startDiscovery() } override fun onStop() { super.onStop() mBtHelp.cancelDiscovery() } override fun onDestroy() { super.onDestroy() mBtHelp.destroy() } private fun initBt() { mBtHelp = BtHelp.Build(this) .setBluetoothStatusListener { Log.e("zh", "蓝牙开关状态:${it} ") if (it == BluetoothAdapter.STATE_OFF) { mAdapter.refreshPairData(null) mAdapter.refreshNoPairData(null) mAdapter.setEnableBluetoothStatus(mBtHelp.isEnabled()) } if (it == BluetoothAdapter.STATE_ON) { mFirstSearch = true mAdapter.setEnableBluetoothStatus(mBtHelp.isEnabled()) mBtHelp.startDiscovery() } } .setStartDiscoveryCallback { Log.d("zh", "开始搜索: ") if (mFirstSearch) { mAdapter.refreshNoPairData(null) } } .setDiscoveringCallback { blDevice, list -> val sb = StringBuilder() list.forEach { sb.append("${it.bluetoothDevice.name}, ") } Log.d("zh", "搜索中发现: ${blDevice?.bluetoothDevice?.name} blDevice=${blDevice} list=${sb} ") mAdapter.addNoPairData(blDevice) } .setFinishDiscoveryCallback { val sb = StringBuilder() it.forEach { sb.append("${it.bluetoothDevice.name}, ") } Log.d("zh", "搜索完成: list=${sb}") mAdapter.refreshNoPairData(it) mFirstSearch = false mBtHelp.startDiscovery() } .setAlreadyPairedDeviceCallback { val sb = StringBuilder() it.forEach { sb.append("${it.bluetoothDevice.name}, ") } Log.d("zh", "已经配对设备: list=${sb}") mAdapter.refreshPairData(it) } .setPairStatusListener { btDeviceData, i -> when (i) { BluetoothDevice.BOND_BONDING -> { mAdapter.setPairDeviceStatus(btDeviceData to "正在配对") } BluetoothDevice.BOND_BONDED -> { mAdapter.setPairDeviceStatus(btDeviceData to "配对成功") mIsPair = false mAdapter.refreshNoPairData(null) } BluetoothDevice.BOND_NONE -> { if (mIsPair) { Toast.makeText(this, "配对不成功", Toast.LENGTH_SHORT).show() } mIsPair = false } } } .build() }
代码
所需权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
代码
import android.annotation.SuppressLint import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothClass import android.bluetooth.BluetoothDevice import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.Log import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.lang.reflect.Method import kotlin.reflect.full.declaredFunctions @SuppressLint("MissingPermission") class BtHelp { private val TAG = "lwlx" private val mBlAdapter by lazy { BluetoothAdapter.getDefaultAdapter() } private var mBtReceiver: BtReceiver? = null private var mBuild: Build? = null private val mPairDeviceList = mutableListOf<BtDeviceData>() //刷新连接设备互斥锁 private val mRefreshConnectedDeviceMutex = Mutex() private fun init(build: Build) { mBuild = build registeredBroadcastReceiver() } /** * 销毁 */ fun destroy() { unregisteredBroadcastReceiver() mBuild?.mContext = null mBuild?.mStartDiscoveryCallback = null mBuild?.mDiscoveringCallback = null mBuild?.mFinishDiscoveryCallback = null mBuild?.mAlreadyPairedDeviceCallback = null mBuild?.mBluetoothStatusListener = null mBuild?.mPairStatusListener = null mBuild = null } /** * 蓝牙名称 */ fun getBluetoothName() = mBlAdapter.name /** * 设置开启or关闭蓝牙 */ fun setEnabled(isEnabled: Boolean): Boolean { if (mBlAdapter == null) { Log.e("lwlx", "该设备不支持蓝牙") return false } if (isEnabled) { if (!mBlAdapter.isEnabled) { return mBlAdapter.enable() } } else { if (mBlAdapter.isEnabled) { return mBlAdapter.disable() } } return false } /** * 是否启用蓝牙 */ fun isEnabled(): Boolean { if (mBlAdapter == null) { return false } return mBlAdapter.isEnabled } /** * 蓝牙可见(蓝牙可以被其他设备发现) * @Hide */ fun btVisible() { //开启被其它蓝牙设备发现的功能 //getScanMode 获得扫描模式 扫描-模式-连接-发现 if (mBlAdapter.scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { val i = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE) //行动-请求-发现 //设置为一直开启 0是一直开着,如果设置了时间就会按照设置时间显示蓝牙可见 // i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0) // startActivity(i) } } /** * 搜索发现设备 */ fun startDiscovery() { if (mBlAdapter == null) { return } if (!mBlAdapter.isDiscovering) { mBlAdapter.startDiscovery() } } /** * 是否在搜索蓝牙 */ fun isDiscovering() = mBlAdapter.isDiscovering /** * 取消搜索发现设备 */ fun cancelDiscovery() { if (mBlAdapter == null) { return } if (mBlAdapter.isDiscovering) { mBlAdapter.cancelDiscovery() } } /** * 配对(配对成功与失败通过广播返回) * * @param device */ fun pairDevice(device: BluetoothDevice?) { if (device == null) { Log.e(TAG, "bond device null") return } if (!mBlAdapter.isEnabled) { Log.e(TAG, "Bluetooth not enable!") return } //配对之前把扫描关闭 if (mBlAdapter.isDiscovering()) { mBlAdapter.cancelDiscovery() } GlobalScope.launch(Dispatchers.Default) { //判断设备是否配对,没有配对在配,配对了就不需要配了 if (device.bondState == BluetoothDevice.BOND_NONE) { try { val createBondMethod: Method = device.javaClass.getMethod("createBond") createBondMethod.invoke(device) } catch (e: Exception) { e.printStackTrace() Log.e(TAG, "attemp to bond fail!") } } } } /** * 取消配对(取消配对成功与失败通过广播返回 也就是配对失败) * * @param device */ fun removeDevice(device: BluetoothDevice?) { if (device == null) { Log.d(TAG, "cancel bond device null") return } if (!mBlAdapter.isEnabled) { Log.e(TAG, "Bluetooth not enable!") return } val index = mPairDeviceList.indexOf(mPairDeviceList.find { it.bluetoothDevice.address == device.address }) mPairDeviceList.removeAt(index) mBuild?.mAlreadyPairedDeviceCallback?.invoke(mPairDeviceList.toMutableList()) GlobalScope.launch(Dispatchers.Default) { //判断设备是否配对,没有配对就不用取消了 if (device.bondState != BluetoothDevice.BOND_NONE) { try { val removeBondMethod: Method = device.javaClass.getMethod("removeBond") removeBondMethod.invoke(device) } catch (e: Exception) { e.printStackTrace() Log.e(TAG, "attemp to cancel bond fail!") } } } } /** * 已连接的设备 */ public fun refreshConnectedDevice() { GlobalScope.launch(Dispatchers.Default) { mRefreshConnectedDeviceMutex.withLock { val declaredFunctions = BluetoothAdapter::class.declaredFunctions mPairDeviceList.clear() for (f in declaredFunctions) { if (f.name == "getConnectionState") { //已经绑定过的全部设备,这些设备可能没有连接 val devices: Set<BluetoothDevice> = mBlAdapter.getBondedDevices() for (device in devices) { val declaredFunctions = BluetoothDevice::class.declaredFunctions for (item in declaredFunctions) { if (item.name == "isConnected") { val isConnected = item.call(device) as Boolean val status = if (isConnected) BluetoothStatus.PAIR_CONNECT else BluetoothStatus.PAIR_NO_CONNECT mPairDeviceList.add(BtDeviceData(device, BluetoothTypeUtil.getDeviceType(device.bluetoothClass), status)) } } } } } withContext(Dispatchers.Main) { //toMutableList拷贝List,防止传出外部列表出现迭代器出现并发修改异常 mBuild?.mAlreadyPairedDeviceCallback?.invoke(mPairDeviceList.toMutableList()) } } } } private fun registeredBroadcastReceiver() { mBtReceiver = BtReceiver() val intentFilter = IntentFilter() //蓝牙开关状态 intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) //蓝牙搜索 intentFilter.addAction(BluetoothDevice.ACTION_FOUND) //开始搜索 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //结束搜索 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) //蓝牙设备连接 intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) //蓝牙设备断开 intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) //配对广播 intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED) mBuild?.mContext?.registerReceiver(mBtReceiver, intentFilter) } private fun unregisteredBroadcastReceiver() { mBuild?.mContext?.unregisterReceiver(mBtReceiver) } class Build(context: Context) { internal var mContext: Context? = context //开始搜索 internal var mStartDiscoveryCallback: (() -> Unit)? = null //搜索发现中 internal var mDiscoveringCallback: ((BtDeviceData, List<BtDeviceData>) -> Unit)? = null //完成搜索 internal var mFinishDiscoveryCallback: ((List<BtDeviceData>) -> Unit)? = null //已经配对设备 internal var mAlreadyPairedDeviceCallback: ((List<BtDeviceData>) -> Unit)? = null //蓝牙状态 internal var mBluetoothStatusListener: ((Int) -> Unit)? = null //配对状态 internal var mPairStatusListener: ((BtDeviceData, Int) -> Unit)? = null /** * 设置开始搜索回调 */ fun setStartDiscoveryCallback(callback: (() -> Unit)): Build { mStartDiscoveryCallback = callback return this } /** * 设置搜索发现设备回调 */ fun setDiscoveringCallback(callback: ((BtDeviceData, List<BtDeviceData>) -> Unit)): Build { mDiscoveringCallback = callback return this } /** * 设置结束搜索回调 */ fun setFinishDiscoveryCallback(callback: (List<BtDeviceData>) -> Unit): Build { mFinishDiscoveryCallback = callback return this } /** * 设置已配对设备回调 */ fun setAlreadyPairedDeviceCallback(callback: ((List<BtDeviceData>) -> Unit)): Build { mAlreadyPairedDeviceCallback = callback return this } /** * 蓝牙状态监听 * [Int] 蓝牙状态 * BluetoothAdapter.STATE_TURNING_ON //蓝牙正在开启 * BluetoothAdapter.STATE_ON //蓝牙已经开启 * BluetoothAdapter.STATE_TURNING_OFF //蓝牙正在关闭 * BluetoothAdapter.STATE_OFF //蓝牙已经关闭 * BluetoothAdapter.ERROR //异常 */ fun setBluetoothStatusListener(listener: ((Int) -> Unit)): Build { mBluetoothStatusListener = listener return this } /** * 设置蓝牙配对状态监听 * [BtDeviceData] 正在配对的设备 * [Int] 配对状态 * BluetoothDevice.BOND_BONDING //正在配对 * BluetoothDevice.BOND_BONDED //配对结束 * BluetoothDevice.BOND_NONE //取消配对/未配对 */ fun setPairStatusListener(listener: ((BtDeviceData, Int) -> Unit)): Build { mPairStatusListener = listener return this } fun build(): BtHelp { val btHelp = BtHelp() btHelp.init(this) return btHelp } } inner class BtReceiver : BroadcastReceiver() { private val noPairList = mutableListOf<BtDeviceData>() override fun onReceive(context: Context?, intent: Intent) { when (intent.action) { BluetoothAdapter.ACTION_STATE_CHANGED -> { val blState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0) mBuild?.mBluetoothStatusListener?.invoke(blState) } BluetoothDevice.ACTION_ACL_CONNECTED -> { //已经连接 val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice refreshConnectedDevice() } BluetoothDevice.ACTION_ACL_DISCONNECTED -> { //已经断开 val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice GlobalScope.launch(Dispatchers.Default) { delay(1000) refreshConnectedDevice() } } BluetoothDevice.ACTION_BOND_STATE_CHANGED -> { val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) val blDevice = BtDeviceData(device, BluetoothTypeUtil.getDeviceType(device.bluetoothClass), BluetoothStatus.NO_PAIR) mBuild?.mPairStatusListener?.invoke(blDevice, device.bondState) when (device.bondState) { BluetoothDevice.BOND_BONDING -> {} //正在配对 BluetoothDevice.BOND_BONDED -> { //配对结束 refreshConnectedDevice() startDiscovery() } BluetoothDevice.BOND_NONE -> { //取消配对/未配对 startDiscovery() } } } //发现设备 BluetoothDevice.ACTION_FOUND -> { val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice //设备类型 val type = BluetoothTypeUtil.getDeviceType(bluetoothDevice.bluetoothClass) val blDevice = BtDeviceData(bluetoothDevice, type, BluetoothStatus.NO_PAIR) //去重未配对的列表 val isRepeat = noPairList.contains(blDevice) //排除已经配对的设备 val paired = mPairDeviceList.find { bluetoothDevice.address == it.bluetoothDevice.address } if (!blDevice.bluetoothDevice.name.isNullOrEmpty() && !isRepeat && paired == null) { noPairList.add(blDevice) mBuild?.mDiscoveringCallback?.invoke(blDevice, noPairList.toMutableList()) } } //开始搜索 BluetoothAdapter.ACTION_DISCOVERY_STARTED -> { noPairList.clear() mBuild?.mStartDiscoveryCallback?.invoke() refreshConnectedDevice() } //搜索完毕 BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { mBuild?.mFinishDiscoveryCallback?.invoke(noPairList.toMutableList()) } } } } } data class BtDeviceData(val bluetoothDevice: BluetoothDevice, val bluetoothType: BluetoothType, val status: BluetoothStatus) /** * 设备类型 */ enum class BluetoothType { /** * 电脑 */ COMPUTER, /** * 手机 */ PHONE, /** * 外围设备(键盘,鼠标,遥控器) */ PERIPHERAL, /** * 摄像头 */ IMAGING, /** * 耳机 */ HEADPHONES, /** * 未知 */ UNKNOWN } enum class BluetoothStatus { /** * 配对且连接 */ PAIR_CONNECT /** * 配对未连接 */ , PAIR_NO_CONNECT, /** * 没有配对 */ NO_PAIR } private object BluetoothTypeUtil { const val PROFILE_HEADSET = 0 const val PROFILE_A2DP = 1 const val PROFILE_OPP = 2 const val PROFILE_HID = 3 const val PROFILE_PANU = 4 const val PROFILE_NAP = 5 const val PROFILE_A2DP_SINK = 6 fun getDeviceType(bluetoothClass: BluetoothClass?): BluetoothType { return if (bluetoothClass == null) { BluetoothType.UNKNOWN } else if (doesClassMatch(bluetoothClass, PROFILE_HEADSET) || doesClassMatch(bluetoothClass, PROFILE_A2DP)) { BluetoothType.HEADPHONES } else when (bluetoothClass.majorDeviceClass) { BluetoothClass.Device.Major.COMPUTER -> BluetoothType.COMPUTER BluetoothClass.Device.Major.PHONE -> BluetoothType.PHONE BluetoothClass.Device.Major.PERIPHERAL -> BluetoothType.PERIPHERAL BluetoothClass.Device.Major.IMAGING -> BluetoothType.IMAGING else -> BluetoothType.UNKNOWN } } fun doesClassMatch(bluetoothClass: BluetoothClass, profile: Int): Boolean { return if (profile == PROFILE_A2DP) { if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) { return true } when (bluetoothClass.deviceClass) { BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES, BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true else -> false } } else if (profile == PROFILE_A2DP_SINK) { if (bluetoothClass.hasService(BluetoothClass.Service.CAPTURE)) { return true } when (bluetoothClass.deviceClass) { BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX, BluetoothClass.Device.AUDIO_VIDEO_VCR -> true else -> false } } else if (profile == PROFILE_HEADSET) { // The render service class is required by the spec for HFP, so is a // pretty good signal if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) { return true } when (bluetoothClass.deviceClass) { BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true else -> false } } else if (profile == PROFILE_OPP) { if (bluetoothClass.hasService(BluetoothClass.Service.OBJECT_TRANSFER)) { return true } when (bluetoothClass.deviceClass) { BluetoothClass.Device.COMPUTER_UNCATEGORIZED, BluetoothClass.Device.COMPUTER_DESKTOP, BluetoothClass.Device.COMPUTER_SERVER, BluetoothClass.Device.COMPUTER_LAPTOP, BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA, BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA, BluetoothClass.Device.COMPUTER_WEARABLE, BluetoothClass.Device.PHONE_UNCATEGORIZED, BluetoothClass.Device.PHONE_CELLULAR, BluetoothClass.Device.PHONE_CORDLESS, BluetoothClass.Device.PHONE_SMART, BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY, BluetoothClass.Device.PHONE_ISDN -> true else -> false } } else if (profile == PROFILE_HID) { bluetoothClass.deviceClass and BluetoothClass.Device.Major.PERIPHERAL == BluetoothClass.Device.Major.PERIPHERAL } else if (profile == PROFILE_PANU || profile == PROFILE_NAP) { // No good way to distinguish between the two, based on class bits. if (bluetoothClass.hasService(BluetoothClass.Service.NETWORKING)) { true } else bluetoothClass.deviceClass and BluetoothClass.Device.Major.NETWORKING == BluetoothClass.Device.Major.NETWORKING } else { false } } }
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16355400.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!