Ble扫描
Ble权限适配
权限问题点总结:
如何适配不同sdk版本,以确保获取到蓝牙权限?
如何过谷歌权限隐私审核要求?即以扫描标准进行权限申请?
清单文件配置
针对不同sdk版本权限,需要定制不同的清单文件,以确保清单文件的权限能被谷歌审核通过,因此需要按照官方要求进行适配。官方链接:https://developer.android.com/guide/topics/connectivity/bluetooth#Permissions
旧版本所需权限如下:
android.permission.BLUETOOTH:用于蓝牙连接
android.permission.BLUETOOTH_ADMIN: 用于蓝牙扫描发现,即找到其他蓝牙设备所需权限
android.permission.ACCESS_COARSE_LOCATION:网络定位权限,只兼容android9 即sdk28以下
android.permission.ACCESS_FINE_LOCATION: Gps精准定位,兼容所有版本
所以需要在清单文件中指令权限最大使用的sdk版本,android11 即sdk30,需要在旧权限上申明此信息
<?xml version="1.0" encoding="utf-8"?><manifest <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.typhur.module.ble">
<!--旧版蓝牙权限 BLUETOOTH蓝牙连接,BLUETOOTH_ADMIN蓝牙扫描发现-->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!--Android6-11低版本蓝牙权限兼容,ACCESS_FINE_LOCATION精准的GPS定位,ACCESS_COARSE_LOCATION网络定位-->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="30" />
</manifest>="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.typhur.module.ble"> <!--旧版蓝牙权限 BLUETOOTH蓝牙连接,BLUETOOTH_ADMIN蓝牙扫描发现--> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <!--Android6-11低版本蓝牙权限兼容,ACCESS_FINE_LOCATION精准的GPS定位,ACCESS_COARSE_LOCATION网络定位--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" /></manifest>
新版本所需如下:
android.permission.BLUETOOTH_CONNECT: 用于android12 蓝牙设备通信和检测蓝牙是否打开
android.permission.BLUETOOTH_SCAN: 扫描权限,最好申明不获取位置信息,即neverForLocation
android.permission.BLUETOOTH_ADVERTISE: 当前手机蓝牙能被检测扫描权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.typhur.module.ble">
<!--Android12 的蓝牙权限 如果您的应用与已配对的蓝牙设备通信或者获取当前手机蓝牙是否打开-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!--Android12 的蓝牙权限 如果您的应用查找蓝牙设备(如蓝牙低功耗 (BLE) 外围设备)-->
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<!--Android12 的蓝牙权限 如果您的应用使当前设备可被其他蓝牙设备检测到-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>
动态权限申请
总结要点:
区分android12以上和以下权限android12PermissionList 和 android12LowerPermissionList
获取当前设备sdk版本,并进行权限判断,分别为isAndroid12()和isPermissionGrant()
进行权限申请,使用官方的权限申请新方法ActivityResultContract
用safeRun()确保权限申请获得后,执行扫描代码
object BlePermissionHelp {
/**
@description android12高版本权限
*/
@RequiresApi(Build.VERSION_CODES.S)
private val android12PermissionList = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
)
/**
@description 低版本权限
*/
private val android12LowerPermissionList = arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION,
)
@SuppressLint("AnnotateVersionCheck")
private fun isAndroid12() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
/**
@description 权限安全执行
*/
fun safeRun(
helper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>,
block: () -> Unit
) {
if (isAndroid12()) {
requestPermission(helper, android12PermissionList) {
block.invoke()
}
} else {
requestPermission(helper, android12LowerPermissionList) {
block.invoke()
}
}
}
/**
@description 是权限否授权
*/
fun isPermissionGrant(): Boolean {
return if (isAndroid12()) {
android12PermissionList.hasPermission()
} else {
android12LowerPermissionList.hasPermission()
}
}
/**
@description 检测权限列表是否授权,如果未授权,遍历请求授权
*/
private fun requestPermission(
permission: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>,
permissionList: Array<String>,
block: () -> Unit
) {
permission.launch(permissionList) {
// 检测权限完整
var hasPermission = false
for (entry in it) {
if (!entry.value) {
hasPermission = false
break
} else {
hasPermission = true
}
}
if (hasPermission) block.invoke()
}
}
}
Ble扫描代码实现
蓝牙通识:
通常蓝牙扫描会根据蓝牙芯片进行区分,即为普通蓝牙和低功耗蓝牙,在4.0之前蓝牙只有一种模式,在4.0时则区分了普通蓝牙和低功耗蓝牙,4.1/4.2/5.0的蓝牙芯片即包含经典蓝牙(BT)又包含低功耗蓝牙(Ble)
技术说明:
针对google的蓝牙Api,区分了广播注册方式获取和使用蓝牙设配器BluetoothManager.BluetoothAdapter中的BluetoothLeScanner,基于api的新旧,笔者优先考虑了后者BluetoothLeScanner,因此在此不对广播注册方式的蓝牙扫描做过多的展开。如需广播注册方式,请参考官方链接:https://developer.android.com/guide/topics/connectivity/bluetooth#FindDevices
ble扫描所需的api分别为startScan(List <scanfilter>filters, ScanSettings settings, ScanCallback callback) 和 stopScan(ScanCallback callback), 由于需要对扫描的ble进行条件过滤和配置,所以用了一个辅助类去管理此职责</scanfilter>
BleScanHelper (蓝牙扫描辅助类)
思路整理:
扫描过程会扫到许多蓝牙设备,为了确保性能需要对此进行过滤,通常利用ScanFilter类,对service uuid进行过滤,其中蓝牙标准的uuid 就是"0000ffff-0000-1000-8000-00805F9B34FB",当然也可以针对名称等其他条件进行过滤
对于不同版本sdk,由于api的差异,我们需要配置不同的ScanSettings,对于android6.0以上的蓝牙版本,可以设置平衡模式和全匹配规则,以便更快捷扫描;对于低版本的智能使用低功耗模式用于节电
正式开始扫描startScan(),为了防止权限问题,对权限进行了二次校验,并且为了防止重复扫描,在executeScan()中打了标记isScanning,确保只会执行以此扫描
由于蓝牙扫描是耗时行为,所以通常需要定义超时概念,来结果蓝牙扫描,因此需要stopScan()停止此扫描行为
@SuppressLint("MissingPermission")
object BleScanHelper {
var SERVICE_UUID = "0000ffff-0000-1000-8000-00805F9B34FB"
/**
@description 回调
*/
var callback: ScanCallback? = null
/**
@description 扫描控制类
*/
var leScanner: BluetoothLeScanner? = null
/**
@description 是否正在扫描中
*/
private var isScanning = false
private val scanFilterList = mutableListOf<ScanFilter>().apply {
add(0, getDefFilter())
}
private fun getDefFilter(): ScanFilter {
return ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(SERVICE_UUID)).build()
}
private val scanSettings: ScanSettings = getScanSettings()
/**
@description 获取扫描配置
*/
private fun getScanSettings(): ScanSettings {
val builder = ScanSettings.Builder()
if (Build.VERSION.SDK_INT >= 23) {
builder.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
} else {
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
}
return builder.build()
}
/**
@description 开启蓝牙扫描
@return 是否开启成功
*/
fun startScan(
permissionHelper:
ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>?,
) {
permissionHelper?.run {
val permissionGrant = BlePermissionHelp.isPermissionGrant()
logD { "startScan() permissionGrant = $permissionGrant" }
if (permissionGrant) {
executeScan()
} else {
BlePermissionHelp.safeRun(this) {
executeScan()
}
}
}
}
/**
@description 执行扫描操作
*/
private fun executeScan() {
logD { "executeScan()" }
if (!isScanning){
isScanning = true
leScanner?.startScan(scanFilterList, scanSettings, callback)
}
}
/**
@description 取消扫描
*/
fun stopScan(scanner: BluetoothLeScanner? = null) {
logD { "stopScan()" }
isScanning = false
if (scanner != null) leScanner = scanner
try {
leScanner?.stopScan(callback)
} catch (e:Exception) {
e.printStackTrace()
}
}
}
ScanTimeoutCallback(蓝牙超时回调)
用于统一处理蓝牙扫描超时的而定义出的回调
interface ScanTimeoutCallback {
/**
@description ble扫描列表回调
*/
fun onScanList(list: List<BleDeviceInfo>)
/**
@description 扫描超时回调
*/
fun onTimeout()
}
BleScanManager(蓝牙扫描管理类)
思路整理:
由于该类是管理类,为了确保线程安全,使用了单例方式进行校验申请
蓝牙扫描需要有超时时间配置和权限launch进行权限申请,因此定义了initConfig()方法
在执行扫描前,需要判断蓝牙是否打开,因此定义了isBleEnable(),利用BluetoothAdapter进行判断
开始扫描startScan(callback: ScanTimeoutCallback),主要通过ScanTimeoutCallback的封装,对超时情况和列表扫描结果进行回调
stopScan()为了防止内存泄露,对此进行超时的取消和对回调的清除,同事为防止出现权限问题,也进行了权限校验
使用总结:
initConfig() -> isBleEnable() -> startScan(callback: ScanTimeoutCallback) -> stopScan()
class BleScanManager {
companion object {
@Volatile
var instance: BleScanManager? = null
fun isInited() = instance != null
fun newInstance(): BleScanManager {
if (instance == null) {
synchronized(BleScanManager::class.java) {
if (instance == null) {
instance = BleScanManager()
}
}
}
return instance!!
}
}
@SuppressLint("ServiceCast")
val bluetoothManager: BluetoothManager =
BaseGlobalConst.app.applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
/**
@description 权限辅助类
*/
private var permissionHelper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>? =
null
/**
@description 蓝牙列表结果
*/
private val scanCallback = BleScanDefCallback {
// 子线程回调,需要切换线程
BaseGlobalConst.mainScope.launchOnUi {
scanTimeoutCallback?.onScanList(it)
// 列表不为空,移除超时
if (it.isNotEmpty()){
BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
}
}
}
/**
@description 二次封装回调,用于处理扫描超时回调
*/
private var scanTimeoutCallback: ScanTimeoutCallback?= null
private var scanTimeoutMillis = 40_000L
private val scanCancelRunnable = Runnable {
scanTimeoutCallback?.onTimeout()
stopScan()
}
/**
@description 初始化配置
*/
fun initConfig(
scanTimeoutMillis: Long = 40_000L,
helper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>
) {
this.scanTimeoutMillis = scanTimeoutMillis
permissionHelper = helper
}
/**
@description 打开蓝牙
*/
fun enableBle(activityForResult: ActivityResultHelper<Intent, ActivityResult>): Boolean {
if (!BlePermissionHelp.isPermissionGrant()) throw IllegalStateException("not grant ble permission list")
var isSuc = false
if (bluetoothAdapter?.isEnabled == false) {
activityForResult.launch(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
isSuc = it.resultCode == RESULT_OK
}
}
return isSuc
}
/**
@description 当前蓝牙是否打开
*/
fun isBleEnable(): Boolean = bluetoothAdapter?.isEnabled ?: false
/**
@description 开启蓝牙扫描,传递一个回调类
*/
fun startScan(callback: ScanTimeoutCallback) {
if (!isBleEnable()) return
// 设置回调
scanTimeoutCallback = callback
BleScanHelper.callback = scanCallback
BleScanHelper.leScanner = bluetoothAdapter?.bluetoothLeScanner
executeScan()
}
private fun executeScan(){
BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
BleScanHelper.startScan(permissionHelper)
BaseGlobalConst.mainHandler.postDelayed(scanCancelRunnable, scanTimeoutMillis)
}
fun stopScan() {
BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
scanTimeoutCallback = null
if (!BlePermissionHelp.isPermissionGrant()) throw IllegalStateException("not grant ble permission list")
BleScanHelper.stopScan(bluetoothAdapter?.bluetoothLeScanner)
}
}
Blufi设备配网
官方参考:https://github.com/EspressifApp/EspBlufiForAndroid
Blufi是用于解决设备配网的一个三方工具,由于硬件使用此套第三方交互,因此使用此库,但为了防止以后需要定制化开发,在此也提供了另外的解决方案,参考博客:https://cloud.tencent.com/developer/article/1875770
总结思考:
设备配网本质是一种蓝牙数据通信,所以需要用到官方的BluetoothGattCallback,使用BluetoothDevice.connectGatt()进行gatt连接通信
需要对BluetoothGattCallback的回调顺序有所理解,参考博客中清晰地备注了此方法
blufi主要封装了建立完Gatt通道后,对信息的加密传输,对设备能访问到的wifi列表信息穿肚、对设备配网信息的结果回调处理,因此有了BlufiCallback
GattCallback(低功耗蓝牙回调)
思路整理:
BluetoothDevice.connectGatt()进行低功耗蓝牙同道连接,连接成功会执行onConnectionStateChange,通常在连接成功时,需要调用gatt.discoverServices(),进行服务发现
mtu指的是单包蓝牙传输数据大小,gatt.requestMtu(512),默认是512字节,如果请求失败再设置为20字节,这是由于低版本ble有的只支持20字节大小的传输,因此通常可以在onConnectionStateChange进行配置
当执行完毕gatt.discoverServices(),就会回调onServicesDiscovered,如果发现不了服务,则需要调用gatt.disconnect()进行断开连接;如果发现服务,则需要进行服务的获取gatt.getService(UUID_SERVICE),其中UUID_SERVICE 就是之前过滤用的" 0000ffff-0000-1000-8000-00805F9B34FB "。在获取完service之后,需要对进行读、写通道的特征码进行校验service.getCharacteristic(uuid),校验完毕就可以执行写入描述字符gatt.writeDescriptor(notifyDesc)
由于writeDescriptor()后,会回调onDescriptorWrite,会在此进行读uuid的校验和写描述符的校验,来确保设备的合法性来源,校验完毕后可以对写入状态结果进行回调判断
最终再通过gatt.writeCharacteristic(characteristic)进行特征码写入,会回调onCharacteristicWrite
@SuppressLint("MissingPermission")
private class GattCallback : BluetoothGattCallback() {
/**
@description 1.检测gatt低功耗蓝牙的连接状态,连接成功后,BlufiClientImpl 执行了gatt.discoverServices()
@param status 操作开关状态,为了区别于连接状态,用于处理断开连接后不回调
@param newState 连接状态 BluetoothProfile.STATE_CONNECTED 代表连接上
*/
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val address = gatt.device.address
logD { "onMtuChanged address=$address, status=$status, newState=$newState" }
// 当前蓝牙状态连接成功,
isConnecting = false
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
isConnected = true
flowCallbackList.forEach { it.onGattConnect(true) }
} else {
gatt.close()
isConnected = false
flowCallbackList.forEach {
it.onGattConnect(false)
it.onGattError(BlufiFlow.ERROR_GATT_CONNECT)
}
}
}
/**
@description 2.mtu是指单包蓝牙输数据大小,当mtu发生变化时回调
*/
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
logD { "onMtuChanged status=$status, mtu=$mtu" }
// 如果蓝牙包大小请求失败,则恢复默认20字节
if (status != BluetoothGatt.GATT_SUCCESS) {
mBlufiClient?.setPostPackageLengthLimit(20)
}
// 代表通信同道已完成
flowCallbackList.forEach { it.onBlufiPrepared() }
}
/**
@description 3.由于onConnectionStateChange执行了 gatt.discoverServices()方法,
* BlufiClientImpl会进行读、写蓝牙特征码校验,并开启特征码监听回调如 onCharacteristicChanged(),最后会执行此方法
*/
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
logD { "onServicesDiscovered status=$status" }
// 操作状态已经断开,则进行断连
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
flowCallbackList.forEach { it.onGattError(BlufiFlow.ERROR_GATT_DISCOVERED) }
}
}
/**
@description 4.在执行写入操作前,需要确保描述通道的连接性,之后才会执行onCharacteristicWrite()
*/
override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
logD { "onDescriptorWrite status=$status" }
// 在执行BlufiClientImpl的onServicesDiscovered,会创建读通道uuid和通道描述uuid
if (descriptor.uuid == BlufiParameter.UUID_NOTIFICATION_DESCRIPTOR &&
descriptor.characteristic.uuid == BlufiParameter.UUID_NOTIFICATION_CHARACTERISTIC
) {
logD { "Set notification enable ${status == BluetoothGatt.GATT_SUCCESS}" }
// 进行消息通知写的成功或失败
// onWriteResult(msg)
}
}
/**
@description 5.进行操作特征码的写入
*/
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
logD { "WriteChar error status=$status" }
}
}
}
BlufiCallbackMain(blufi的回调)
思路总结:
Gatt的连接成功后,在特征码通道确认后,会回调onGattPrepared
调用mBlufiClient?.negotiateSecurity()进行通道加密处理
获取设备能获取到的wifi列表,mBlufiClient?.requestDeviceWifiScan(),成功会回调onDeviceScanResult
配网信息传输mBlufiClient?.configure(params),将配网信息传输给设备,会依次执行onPostConfigureParams(代表信息提交成功) -> onDeviceStatusResponse(配网的成功回调)-> onReceiveCustomData(接收到设备传输的json结果)
@SuppressLint("MissingPermission")
private class BlufiCallbackMain : BlufiCallback() {
/**
@description 1.描述通道onDescriptorWrite/服务发现onServicesDiscovered时调用此方法,用于申明gatt已经可以通信
*/
override fun onGattPrepared(
client: BlufiClient,
gatt: BluetoothGatt,
service: BluetoothGattService?,
writeChar: BluetoothGattCharacteristic?,
notifyChar: BluetoothGattCharacteristic?
) {
if (service == null) {
logD { "Discover service failed" }
gatt.disconnect()
return
}
if (writeChar == null) {
logD { "Get write characteristic failed" }
gatt.disconnect()
return
}
if (notifyChar == null) {
logD { "Get notification characteristic failed" }
gatt.disconnect()
return
}
// 通知可以进行通信
logD { "Discover service and characteristics success" }
val mtu: Int = DEFAULT_MTU_LENGTH
val requestMtu = gatt.requestMtu(mtu)
if (!requestMtu) {
logD { "Request mtu failed" }
flowCallbackList.forEach { it.onBlufiPrepared() }
}
}
/**
@description 2.negotiateSecurity执行加密的状态回调
*/
override fun onNegotiateSecurityResult(client: BlufiClient, status: Int) {
// 加密结果回调
flowCallbackList.forEach { it.onBlufiSecurity(status == STATUS_SUCCESS) }
}
/**
@description 3.wifi列表扫描结果回调
*/
override fun onDeviceScanResult(
client: BlufiClient,
status: Int,
results: List<BlufiScanResult>
) {
if (status == STATUS_SUCCESS) {
val msg = StringBuilder()
msg.append("Receive device scan result:\n")
for (scanResult in results) {
msg.append(scanResult.toString()).append("\n")
}
logD { msg.toString() }
flowCallbackList.forEach { it.onBlufiScanWifiList(results) }
} else {
flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_WIFI_LIST_SCAN) }
}
}
/**
@description 4.配网信息传递是否成功
*/
override fun onPostConfigureParams(client: BlufiClient, status: Int) {
logD { "Post configure params status = $status" }
flowCallbackList.forEach { it.onBlufiConfigWifi(status == STATUS_SUCCESS) }
}
/**
@description 5.配网成功的回调
*/
override fun onDeviceStatusResponse(
client: BlufiClient?,
status: Int,
response: BlufiStatusResponse
) {
if (status == STATUS_SUCCESS) {
logD { "generateValidInfo : ${response.generateValidInfo()}" }
} else {
logD { "Device status response error code=$status" }
}
flowCallbackList.forEach { it.onBlufiStatusResponse(status == STATUS_SUCCESS) }
}
/**
@description 6.接收自定义消息
*/
override fun onReceiveCustomData(client: BlufiClient, status: Int, data: ByteArray) {
if (status == STATUS_SUCCESS) {
val customStr = String(data)
flowCallbackList.forEach { it.onBlufiReceive(customStr) }
logD { "Receive custom data: $customStr" }
} else {
logD { "Receive custom data error, code=$status" }
flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_RECEIVE) }
}
}
/**
@description 7.接收错误回调
*/
override fun onError(client: BlufiClient, errCode: Int) {
logD { "Receive error code $errCode" }
// gatt连接超时
when (errCode) {
CODE_GATT_WRITE_TIMEOUT -> {
logD { "Gatt write timeout" }
client.close()
flowCallbackList.forEach { it.onGattConnect(false) }
}
else -> {
flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_COMMON) }
}
}
}
}
BlufiManager(Blufi管理类)
思路整理:
通过addCallback(callback: BlufiFlow.Callback)和removeCallback(callback: BlufiFlow.Callback)进行整体的回调暴露, BlufiFlow.Callback代表期望暴露的回调
执行顺序是,startConnect(开始连接)-> blufiSecurity(通道加密) -> blufiScanWifiList(Wifi扫描) -> blufiConfigWifi(配网传输) -> BlufiFlow.Callback回调各环节结果
object BlufiManager : BlufiFlow {
private var mBlufiClient: BlufiClient? = null
/**
@description 蓝牙写超时
*/
const val GATT_WRITE_TIMEOUT = 5000L
/**
@description 默认mtu,即每包蓝牙最大长度
*/
const val DEFAULT_MTU_LENGTH = 512
/**
@description 要进行连接的蓝牙设备信息
*/
var mDevice: BluetoothDevice? = null
var flowCallbackList: MutableList<BlufiFlow.Callback> = mutableListOf()
fun addCallback(callback: BlufiFlow.Callback) {
flowCallbackList.add(callback)
}
fun removeCallback(callback: BlufiFlow.Callback){
flowCallbackList.remove(callback)
}
/**
@description 是否正在连接中
*/
private var isConnecting = false
/**
@description 是否已连接上
*/
var isConnected = false
/**
@description 获取服务
*/
private fun getBlufiClient(): BlufiClient? {
if (mBlufiClient == null) {
mBlufiClient = BlufiClient(BaseGlobalConst.app, mDevice).apply {
setGattCallback(GattCallback())
setBlufiCallback(BlufiCallbackMain())
setGattWriteTimeout(GATT_WRITE_TIMEOUT)
}
}
return mBlufiClient
}
override fun startConnect() {
if (!isConnecting) {
if (mBlufiClient != null) {
mBlufiClient?.close()
mBlufiClient = null
}
getBlufiClient()?.run {
connect()
}
}
}
override fun disConnect() {
isConnected = false
flowCallbackList.clear()
mBlufiClient?.close()
}
override fun blufiSecurity() {
mBlufiClient?.negotiateSecurity()
}
override fun blufiScanWifiList() {
mBlufiClient?.requestDeviceWifiScan()
}
override fun blufiConfigWifi(params: BlufiConfigureParams) {
mBlufiClient?.configure(params)
}
}