观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

版本声明

本文来自博客园,作者:观心静 ,转载请注明原文链接: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

posted on 2022-06-08 13:53  观心静  阅读(445)  评论(0编辑  收藏  举报