前言
请注意,这里说的是经典蓝牙开发。就是手机设置中的蓝牙开发(代码的使用场景是一些Launcher开发与物联设备开发),并不是ble蓝牙开发。使用kotlin与建造者模式封装,直接复制完整封装代码就可以使用(包含了数据类与枚举类),有大量的反射代码(kotlin第一次启动反射会有较大耗时,后续调用正常)。
使用
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.os.Handler import android.os.Looper import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext 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 var mHandler: Handler? = null private fun init(build: Build) { mBuild = build mHandler = Handler(Looper.getMainLooper()) registeredBroadcastReceiver() } /** * 销毁 */ fun destroy() { unregisteredBroadcastReceiver() mHandler?.removeCallbacksAndMessages(null) mHandler = null 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 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.IO) { //判断设备是否配对,没有配对在配,配对了就不需要配了 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) GlobalScope.launch(Dispatchers.IO) { //判断设备是否配对,没有配对就不用取消了 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!") } } } } /** * 已连接的设备 */ private fun refreshConnectedDevice() { GlobalScope.launch(Dispatchers.IO) { 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) { mBuild?.mAlreadyPairedDeviceCallback?.invoke(mPairDeviceList) } } } 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 } BluetoothDevice.ACTION_ACL_DISCONNECTED -> { val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice mHandler?.postDelayed(Runnable { refreshConnectedDevice() }, 1000) } 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 -> { //配对结束 startDiscovery() } BluetoothDevice.BOND_NONE -> { //取消配对/未配对 startDiscovery() } } } //发现设备 BluetoothDevice.ACTION_FOUND -> { val bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice //设备类型 BluetoothTypeUtil.getDeviceType(bluetoothDevice.bluetoothClass) val blDevice = BtDeviceData(bluetoothDevice, BluetoothTypeUtil.getDeviceType(bluetoothDevice.bluetoothClass), 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) } } //开始搜索 BluetoothAdapter.ACTION_DISCOVERY_STARTED -> { noPairList.clear() mBuild?.mStartDiscoveryCallback?.invoke() refreshConnectedDevice() } //搜索完毕 BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { mBuild?.mFinishDiscoveryCallback?.invoke(noPairList) } } } } } 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
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/272965.html