Ble掃描
Ble權(quán)限適配
權(quán)限問題點(diǎn)總結(jié):
如何適配不同sdk版本,以確保獲取到藍(lán)牙權(quán)限?
如何過谷歌權(quán)限隱私審核要求?即以掃描標(biāo)準(zhǔn)進(jìn)行權(quán)限申請?
清單文件配置
針對不同sdk版本權(quán)限,需要定制不同的清單文件,以確保清單文件的權(quán)限能被谷歌審核通過,因此需要按照官方要求進(jìn)行適配。官方鏈接:https://developer.android.com/guide/topics/connectivity/bluetooth#Permissions
舊版本所需權(quán)限如下:
android.permission.BLUETOOTH:用于藍(lán)牙連接
android.permission.BLUETOOTH_ADMIN: 用于藍(lán)牙掃描發(fā)現(xiàn),即找到其他藍(lán)牙設(shè)備所需權(quán)限
android.permission.ACCESS_COARSE_LOCATION:網(wǎng)絡(luò)定位權(quán)限,只兼容android9 即sdk28以下
android.permission.ACCESS_FINE_LOCATION: Gps精準(zhǔn)定位,兼容所有版本
所以需要在清單文件中指令權(quán)限最大使用的sdk版本,android11 即sdk30,需要在舊權(quán)限上申明此信息
<?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">
<!--舊版藍(lán)牙權(quán)限 BLUETOOTH藍(lán)牙連接,BLUETOOTH_ADMIN藍(lán)牙掃描發(fā)現(xiàn)-->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!--Android6-11低版本藍(lán)牙權(quán)限兼容,ACCESS_FINE_LOCATION精準(zhǔn)的GPS定位,ACCESS_COARSE_LOCATION網(wǎng)絡(luò)定位-->
<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"> <!--舊版藍(lán)牙權(quán)限 BLUETOOTH藍(lán)牙連接,BLUETOOTH_ADMIN藍(lán)牙掃描發(fā)現(xiàn)--> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <!--Android6-11低版本藍(lán)牙權(quán)限兼容,ACCESS_FINE_LOCATION精準(zhǔn)的GPS定位,ACCESS_COARSE_LOCATION網(wǎng)絡(luò)定位--> <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 藍(lán)牙設(shè)備通信和檢測藍(lán)牙是否打開
android.permission.BLUETOOTH_SCAN: 掃描權(quán)限,最好申明不獲取位置信息,即neverForLocation
android.permission.BLUETOOTH_ADVERTISE: 當(dāng)前手機(jī)藍(lán)牙能被檢測掃描權(quán)限
<?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 的藍(lán)牙權(quán)限 如果您的應(yīng)用與已配對的藍(lán)牙設(shè)備通信或者獲取當(dāng)前手機(jī)藍(lán)牙是否打開-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!--Android12 的藍(lán)牙權(quán)限 如果您的應(yīng)用查找藍(lán)牙設(shè)備(如藍(lán)牙低功耗 (BLE) 外圍設(shè)備)-->
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<!--Android12 的藍(lán)牙權(quán)限 如果您的應(yīng)用使當(dāng)前設(shè)備可被其他藍(lán)牙設(shè)備檢測到-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>
動態(tài)權(quán)限申請
總結(jié)要點(diǎn):
區(qū)分android12以上和以下權(quán)限android12PermissionList 和 android12LowerPermissionList
獲取當(dāng)前設(shè)備sdk版本,并進(jìn)行權(quán)限判斷,分別為isAndroid12()和isPermissionGrant()
進(jìn)行權(quán)限申請,使用官方的權(quán)限申請新方法ActivityResultContract
用safeRun()確保權(quán)限申請獲得后,執(zhí)行掃描代碼
object BlePermissionHelp {
/**
@description android12高版本權(quán)限
*/
@RequiresApi(Build.VERSION_CODES.S)
private val android12PermissionList = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
)
/**
@description 低版本權(quán)限
*/
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 權(quán)限安全執(zhí)行
*/
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 是權(quán)限否授權(quán)
*/
fun isPermissionGrant(): Boolean {
return if (isAndroid12()) {
android12PermissionList.hasPermission()
} else {
android12LowerPermissionList.hasPermission()
}
}
/**
@description 檢測權(quán)限列表是否授權(quán),如果未授權(quán),遍歷請求授權(quán)
*/
private fun requestPermission(
permission: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>,
permissionList: Array<String>,
block: () -> Unit
) {
permission.launch(permissionList) {
// 檢測權(quán)限完整
var hasPermission = false
for (entry in it) {
if (!entry.value) {
hasPermission = false
break
} else {
hasPermission = true
}
}
if (hasPermission) block.invoke()
}
}
}
Ble掃描代碼實現(xiàn)
藍(lán)牙通識:
通常藍(lán)牙掃描會根據(jù)藍(lán)牙芯片進(jìn)行區(qū)分,即為普通藍(lán)牙和低功耗藍(lán)牙,在4.0之前藍(lán)牙只有一種模式,在4.0時則區(qū)分了普通藍(lán)牙和低功耗藍(lán)牙,4.1/4.2/5.0的藍(lán)牙芯片即包含經(jīng)典藍(lán)牙(BT)又包含低功耗藍(lán)牙(Ble)
技術(shù)說明:
針對google的藍(lán)牙Api,區(qū)分了廣播注冊方式獲取和使用藍(lán)牙設(shè)配器BluetoothManager.BluetoothAdapter中的BluetoothLeScanner,基于api的新舊,筆者優(yōu)先考慮了后者BluetoothLeScanner,因此在此不對廣播注冊方式的藍(lán)牙掃描做過多的展開。如需廣播注冊方式,請參考官方鏈接:https://developer.android.com/guide/topics/connectivity/bluetooth#FindDevices
ble掃描所需的api分別為startScan(List <scanfilter>filters, ScanSettings settings, ScanCallback callback) 和 stopScan(ScanCallback callback), 由于需要對掃描的ble進(jìn)行條件過濾和配置,所以用了一個輔助類去管理此職責(zé)</scanfilter>
BleScanHelper (藍(lán)牙掃描輔助類)
思路整理:
掃描過程會掃到許多藍(lán)牙設(shè)備,為了確保性能需要對此進(jìn)行過濾,通常利用ScanFilter類,對service uuid進(jìn)行過濾,其中藍(lán)牙標(biāo)準(zhǔn)的uuid 就是"0000ffff-0000-1000-8000-00805F9B34FB",當(dāng)然也可以針對名稱等其他條件進(jìn)行過濾
對于不同版本sdk,由于api的差異,我們需要配置不同的ScanSettings,對于android6.0以上的藍(lán)牙版本,可以設(shè)置平衡模式和全匹配規(guī)則,以便更快捷掃描;對于低版本的智能使用低功耗模式用于節(jié)電
正式開始掃描startScan(),為了防止權(quán)限問題,對權(quán)限進(jìn)行了二次校驗,并且為了防止重復(fù)掃描,在executeScan()中打了標(biāo)記isScanning,確保只會執(zhí)行以此掃描
由于藍(lán)牙掃描是耗時行為,所以通常需要定義超時概念,來結(jié)果藍(lán)牙掃描,因此需要stopScan()停止此掃描行為
@SuppressLint("MissingPermission")
object BleScanHelper {
var SERVICE_UUID = "0000ffff-0000-1000-8000-00805F9B34FB"
/**
@description 回調(diào)
*/
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 開啟藍(lán)牙掃描
@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 執(zhí)行掃描操作
*/
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(藍(lán)牙超時回調(diào))
用于統(tǒng)一處理藍(lán)牙掃描超時的而定義出的回調(diào)
interface ScanTimeoutCallback {
/**
@description ble掃描列表回調(diào)
*/
fun onScanList(list: List<BleDeviceInfo>)
/**
@description 掃描超時回調(diào)
*/
fun onTimeout()
}
BleScanManager(藍(lán)牙掃描管理類)
思路整理:
由于該類是管理類,為了確保線程安全,使用了單例方式進(jìn)行校驗申請
藍(lán)牙掃描需要有超時時間配置和權(quán)限launch進(jìn)行權(quán)限申請,因此定義了initConfig()方法
在執(zhí)行掃描前,需要判斷藍(lán)牙是否打開,因此定義了isBleEnable(),利用BluetoothAdapter進(jìn)行判斷
開始掃描startScan(callback: ScanTimeoutCallback),主要通過ScanTimeoutCallback的封裝,對超時情況和列表掃描結(jié)果進(jìn)行回調(diào)
stopScan()為了防止內(nèi)存泄露,對此進(jìn)行超時的取消和對回調(diào)的清除,同事為防止出現(xiàn)權(quán)限問題,也進(jìn)行了權(quán)限校驗
使用總結(jié):
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 權(quán)限輔助類
*/
private var permissionHelper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>? =
null
/**
@description 藍(lán)牙列表結(jié)果
*/
private val scanCallback = BleScanDefCallback {
// 子線程回調(diào),需要切換線程
BaseGlobalConst.mainScope.launchOnUi {
scanTimeoutCallback?.onScanList(it)
// 列表不為空,移除超時
if (it.isNotEmpty()){
BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
}
}
}
/**
@description 二次封裝回調(diào),用于處理掃描超時回調(diào)
*/
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 打開藍(lán)牙
*/
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 當(dāng)前藍(lán)牙是否打開
*/
fun isBleEnable(): Boolean = bluetoothAdapter?.isEnabled ?: false
/**
@description 開啟藍(lán)牙掃描,傳遞一個回調(diào)類
*/
fun startScan(callback: ScanTimeoutCallback) {
if (!isBleEnable()) return
// 設(shè)置回調(diào)
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設(shè)備配網(wǎng)
官方參考:https://github.com/EspressifApp/EspBlufiForAndroid
Blufi是用于解決設(shè)備配網(wǎng)的一個三方工具,由于硬件使用此套第三方交互,因此使用此庫,但為了防止以后需要定制化開發(fā),在此也提供了另外的解決方案,參考博客:https://cloud.tencent.com/developer/article/1875770
總結(jié)思考:
設(shè)備配網(wǎng)本質(zhì)是一種藍(lán)牙數(shù)據(jù)通信,所以需要用到官方的BluetoothGattCallback,使用BluetoothDevice.connectGatt()進(jìn)行g(shù)att連接通信
需要對BluetoothGattCallback的回調(diào)順序有所理解,參考博客中清晰地備注了此方法
blufi主要封裝了建立完Gatt通道后,對信息的加密傳輸,對設(shè)備能訪問到的wifi列表信息穿肚、對設(shè)備配網(wǎng)信息的結(jié)果回調(diào)處理,因此有了BlufiCallback
GattCallback(低功耗藍(lán)牙回調(diào))
思路整理:
BluetoothDevice.connectGatt()進(jìn)行低功耗藍(lán)牙同道連接,連接成功會執(zhí)行onConnectionStateChange,通常在連接成功時,需要調(diào)用gatt.discoverServices(),進(jìn)行服務(wù)發(fā)現(xiàn)
mtu指的是單包藍(lán)牙傳輸數(shù)據(jù)大小,gatt.requestMtu(512),默認(rèn)是512字節(jié),如果請求失敗再設(shè)置為20字節(jié),這是由于低版本ble有的只支持20字節(jié)大小的傳輸,因此通??梢栽趏nConnectionStateChange進(jìn)行配置
當(dāng)執(zhí)行完畢gatt.discoverServices(),就會回調(diào)onServicesDiscovered,如果發(fā)現(xiàn)不了服務(wù),則需要調(diào)用gatt.disconnect()進(jìn)行斷開連接;如果發(fā)現(xiàn)服務(wù),則需要進(jìn)行服務(wù)的獲取gatt.getService(UUID_SERVICE),其中UUID_SERVICE 就是之前過濾用的" 0000ffff-0000-1000-8000-00805F9B34FB "。在獲取完service之后,需要對進(jìn)行讀、寫通道的特征碼進(jìn)行校驗service.getCharacteristic(uuid),校驗完畢就可以執(zhí)行寫入描述字符gatt.writeDescriptor(notifyDesc)
由于writeDescriptor()后,會回調(diào)onDescriptorWrite,會在此進(jìn)行讀uuid的校驗和寫描述符的校驗,來確保設(shè)備的合法性來源,校驗完畢后可以對寫入狀態(tài)結(jié)果進(jìn)行回調(diào)判斷
最終再通過gatt.writeCharacteristic(characteristic)進(jìn)行特征碼寫入,會回調(diào)onCharacteristicWrite
@SuppressLint("MissingPermission")
private class GattCallback : BluetoothGattCallback() {
/**
@description 1.檢測gatt低功耗藍(lán)牙的連接狀態(tài),連接成功后,BlufiClientImpl 執(zhí)行了gatt.discoverServices()
@param status 操作開關(guān)狀態(tài),為了區(qū)別于連接狀態(tài),用于處理斷開連接后不回調(diào)
@param newState 連接狀態(tài) 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" }
// 當(dāng)前藍(lán)牙狀態(tài)連接成功,
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是指單包藍(lán)牙輸數(shù)據(jù)大小,當(dāng)mtu發(fā)生變化時回調(diào)
*/
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
logD { "onMtuChanged status=$status, mtu=$mtu" }
// 如果藍(lán)牙包大小請求失敗,則恢復(fù)默認(rèn)20字節(jié)
if (status != BluetoothGatt.GATT_SUCCESS) {
mBlufiClient?.setPostPackageLengthLimit(20)
}
// 代表通信同道已完成
flowCallbackList.forEach { it.onBlufiPrepared() }
}
/**
@description 3.由于onConnectionStateChange執(zhí)行了 gatt.discoverServices()方法,
* BlufiClientImpl會進(jìn)行讀、寫藍(lán)牙特征碼校驗,并開啟特征碼監(jiān)聽回調(diào)如 onCharacteristicChanged(),最后會執(zhí)行此方法
*/
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
logD { "onServicesDiscovered status=$status" }
// 操作狀態(tài)已經(jīng)斷開,則進(jìn)行斷連
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
flowCallbackList.forEach { it.onGattError(BlufiFlow.ERROR_GATT_DISCOVERED) }
}
}
/**
@description 4.在執(zhí)行寫入操作前,需要確保描述通道的連接性,之后才會執(zhí)行onCharacteristicWrite()
*/
override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
logD { "onDescriptorWrite status=$status" }
// 在執(zhí)行BlufiClientImpl的onServicesDiscovered,會創(chuàng)建讀通道uuid和通道描述uuid
if (descriptor.uuid == BlufiParameter.UUID_NOTIFICATION_DESCRIPTOR &&
descriptor.characteristic.uuid == BlufiParameter.UUID_NOTIFICATION_CHARACTERISTIC
) {
logD { "Set notification enable ${status == BluetoothGatt.GATT_SUCCESS}" }
// 進(jìn)行消息通知寫的成功或失敗
// onWriteResult(msg)
}
}
/**
@description 5.進(jìn)行操作特征碼的寫入
*/
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
logD { "WriteChar error status=$status" }
}
}
}
BlufiCallbackMain(blufi的回調(diào))
思路總結(jié):
Gatt的連接成功后,在特征碼通道確認(rèn)后,會回調(diào)onGattPrepared
調(diào)用mBlufiClient?.negotiateSecurity()進(jìn)行通道加密處理
獲取設(shè)備能獲取到的wifi列表,mBlufiClient?.requestDeviceWifiScan(),成功會回調(diào)onDeviceScanResult
配網(wǎng)信息傳輸mBlufiClient?.configure(params),將配網(wǎng)信息傳輸給設(shè)備,會依次執(zhí)行onPostConfigureParams(代表信息提交成功) -> onDeviceStatusResponse(配網(wǎng)的成功回調(diào))-> onReceiveCustomData(接收到設(shè)備傳輸?shù)膉son結(jié)果)
@SuppressLint("MissingPermission")
private class BlufiCallbackMain : BlufiCallback() {
/**
@description 1.描述通道onDescriptorWrite/服務(wù)發(fā)現(xiàn)onServicesDiscovered時調(diào)用此方法,用于申明gatt已經(jīng)可以通信
*/
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
}
// 通知可以進(jìn)行通信
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執(zhí)行加密的狀態(tài)回調(diào)
*/
override fun onNegotiateSecurityResult(client: BlufiClient, status: Int) {
// 加密結(jié)果回調(diào)
flowCallbackList.forEach { it.onBlufiSecurity(status == STATUS_SUCCESS) }
}
/**
@description 3.wifi列表掃描結(jié)果回調(diào)
*/
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.配網(wǎng)信息傳遞是否成功
*/
override fun onPostConfigureParams(client: BlufiClient, status: Int) {
logD { "Post configure params status = $status" }
flowCallbackList.forEach { it.onBlufiConfigWifi(status == STATUS_SUCCESS) }
}
/**
@description 5.配網(wǎng)成功的回調(diào)
*/
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.接收錯誤回調(diào)
*/
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)進(jìn)行整體的回調(diào)暴露, BlufiFlow.Callback代表期望暴露的回調(diào)
執(zhí)行順序是,startConnect(開始連接)-> blufiSecurity(通道加密) -> blufiScanWifiList(Wifi掃描) -> blufiConfigWifi(配網(wǎng)傳輸) -> BlufiFlow.Callback回調(diào)各環(huán)節(jié)結(jié)果
object BlufiManager : BlufiFlow {
private var mBlufiClient: BlufiClient? = null
/**
@description 藍(lán)牙寫超時
*/
const val GATT_WRITE_TIMEOUT = 5000L
/**
@description 默認(rèn)mtu,即每包藍(lán)牙最大長度
*/
const val DEFAULT_MTU_LENGTH = 512
/**
@description 要進(jìn)行連接的藍(lán)牙設(shè)備信息
*/
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 獲取服務(wù)
*/
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)
}
}