- BLE 與經(jīng)典藍(lán)牙的區(qū)別
- BLE 的 Kotlin 下實(shí)踐
- BluetoothGattCallback 不回調(diào)異常
- 一些不常見的問(wèn)題和暴力解決的方法
- 經(jīng)典藍(lán)牙自動(dòng)配對(duì),關(guān)閉系統(tǒng)配對(duì)彈窗
經(jīng)典藍(lán)牙(Classic Bluetooth)& 低功耗藍(lán)牙(Bluetooth Low Energy)
經(jīng)典藍(lán)牙可以用與數(shù)據(jù)量比較大的傳輸,如語(yǔ)音,音樂,較高數(shù)據(jù)量傳輸?shù)取?/p>
BLE 特點(diǎn)就如其名,功耗更低的同時(shí),對(duì)數(shù)據(jù)包做出了限制。所以適用于實(shí)時(shí)性要求比較高,但是數(shù)據(jù)速率比較低的產(chǎn)品,如鼠標(biāo),鍵盤,傳感設(shè)備的數(shù)據(jù)發(fā)送等。
藍(lán)牙 4.0 支持單模和雙模兩種部署方式,其中單模即是我們說(shuō)的 BLE,而雙模指的是 Classic Bluetooth + BLE 。
實(shí)際上,BLE 和經(jīng)典藍(lán)牙的使用等各方面都像是沒有關(guān)聯(lián)的兩個(gè)東西,甚至因?yàn)?BLE 的通訊機(jī)制不同,所以是不能向下兼容的;經(jīng)典藍(lán)牙則可以兼容到藍(lán)牙 3.0 / 2.1。
BLE
同樣,有條件一定要去看官方文檔,然而這一次并沒有中文版,或許可以找一些國(guó)內(nèi)大佬們翻譯的版本。
還有就是大佬 JBD 寫的 Android BLE 藍(lán)牙開發(fā)入門 ,而且還用 RxJava 封裝成一個(gè)庫(kù)可以直接調(diào)用:RxBLE ,是真的厲害,不妨去學(xué)習(xí)學(xué)習(xí)。
- 概念與常用 API
UUID:每個(gè)服務(wù)和特征都會(huì)有唯一的 UUID ,由硬件決定。
服務(wù)(Service):藍(lán)牙設(shè)備中可以定義多個(gè)服務(wù),相當(dāng)于功能的集合。
特征(Characteristic):一個(gè)服務(wù)可以包含多個(gè)特征,可以通過(guò) UUID 獲取到對(duì)應(yīng)的特征的實(shí)例,通過(guò)這個(gè)實(shí)例就可以向藍(lán)牙設(shè)備發(fā)送 / 讀取數(shù)據(jù)。
BluetoothDeivce
:調(diào)用 startLeScan()
獲取該實(shí)例,用于連接設(shè)備。
BluetoothManager
:藍(lán)牙管理器,調(diào)用 getSystemService()
獲取,用于獲取藍(lán)牙適配器和管理所有和藍(lán)牙相關(guān)的東西。
BluetoothAdapter
:藍(lán)牙適配器,通過(guò) BluetoothManager
獲取,用于打開藍(lán)牙、開始掃描設(shè)備等操作。
BluetoothGatt
:通用屬性協(xié)議, 定義了BLE通訊的基本規(guī)則,就是通過(guò)把數(shù)據(jù)包裝成服務(wù)和特征的約定過(guò)程。
BluetoothGattCallback
:一個(gè)回調(diào)類,非常重要而且會(huì)頻繁使用,用于回調(diào) GATT 通信的各種狀態(tài)和結(jié)果。
BluetoothGattService
:服務(wù),通過(guò) BluetoothGatt
實(shí)例調(diào)用 getService(UUID) 獲取
。
BluetoothGattCharacteristic
:特征,通過(guò) BluetoothGattService
實(shí)例調(diào)用 getCharacteristic(UUID)
獲取,是 GATT 通信中的最小數(shù)據(jù)單元。
BluetoothGattDescriptor
:特征描述符,對(duì)特征的額外描述,包括但不僅限于特征的單位,屬性等。
- 聲明權(quán)限
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- Android 5.0 及以上需要添加 GPS 權(quán)限 -->
<uses-feature android:name="android.hardware.location.gps" />
<!-- Android 6.0 及以上需要添加定位權(quán)限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- 初始化
fun initBluetoothAdapter(){
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager.adapter
//如果藍(lán)牙沒有打開則向用戶申請(qǐng)
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled)
bluetoothAdapter.enable()
}
- 掃描設(shè)備與停止掃描
var mDevice : BluetoothDevice ?= null
//掃描結(jié)果的回調(diào),開始掃描后會(huì)多次調(diào)用該方法
val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
//通過(guò)對(duì)比設(shè)備的 mac 地址獲取需要的實(shí)例
if(device.address == "50:F1:4A:A1:77:00"){
mDevice = device
}
}
//開始掃描之前判斷是否開啟了藍(lán)牙,enable 為 false 可以停止掃描
fun scanLeDeviceWithBLE(enable:Boolean = true){
if (mBluetoothAdapter == null)
initBluetoothAdapter()
if (mBluetoothAdapter?.isEnabled as Boolean){
mBluetoothAdapter?.enable()
}
if (enable){
mScanning = true
mBluetoothAdapter?.startLeScan(mLeScanCallback)
TimeUtilWithoutKotlin.Delay(8,TimeUnit.SECONDS).setTodo {
mBluetoothAdapter?.stopLeScan(mLeScanCallback)
mScanning = false
}
}else {
//停止掃描,在連接設(shè)備時(shí)最好調(diào)用 stopLeScan()
mBluetoothAdapter?.stopLeScan(mLeScanCallback)
mScanning = false
}
}
其實(shí) startLeScan()
已經(jīng)被聲明為過(guò)時(shí),所以開始掃描可以以廣播的形式接受:
private fun startDiscover() {
//這種方法需要注冊(cè)接收廣播,獲取掃描結(jié)果。
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter?.startDiscovery()
}
//注冊(cè)廣播,監(jiān)聽 BluetoothDevice.ACTION_FOUND 獲取掃描結(jié)果
private inner class DeviceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
val device = intent
.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.e("Service","device: ${device.address}")
}
}
}
或者使用新的 API:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback)
// 過(guò)濾器 filters: new ScanFilter.Builder().setDeviceName(deviceName).build();
// 掃描設(shè)置 settings: new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
mBluetoothAdapter.bluetoothLeScanner.startScan(object : ScanCallback() {
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
// 啟用了批量掃描模式后的回調(diào)
super.onBatchScanResults(results)
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
// startScan() 失敗的回調(diào)
}
override fun onScanResult(callbackType: Int, result: ScanResult?) {
super.onScanResult(callbackType, result)
// 默認(rèn)的掃描方式的回調(diào)
}
} )
}
-
連接藍(lán)牙設(shè)備
此時(shí)已經(jīng)獲取到了藍(lán)牙設(shè)備的實(shí)例:mDevice
,開始連接
fun connectWithBluetoothDevice(){
if (null == mDevice){
toast("can not find device")
return
}
if(mScanning){
//如果正在掃描設(shè)備,則停止掃描
scanLeDeviceWithBLE(false)
}
mDevice?.connectGatt(this,false,mBluetoothGattCallback)
}
關(guān)于 connectGatt()
的幾個(gè)參數(shù):
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback)
第二個(gè)參數(shù),autoConnect
為 true 時(shí),如果設(shè)備斷開了連接將會(huì)不斷的嘗試連接。
第三個(gè) BluetoothGattCallback
是一個(gè)接受回調(diào)的對(duì)象,也是這一部分的重點(diǎn)。
先看一下完整的 BluetoothGattCallback
:
val mBluetoothGattCallback = object :BluetoothGattCallback(){
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
if (newState == BluetoothProfile.STATE_CONNECTED){
//開始搜索服務(wù)
gatt?.discoverServices()
}
// 接受到設(shè)備斷開的狀態(tài)后,還要手動(dòng)調(diào)用 close(),否則可能出現(xiàn)連接未斷開導(dǎo)致的重連失敗等問(wèn)題;
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt?.close()
}
if(newState == BluetoothProfile.STATE_CONNECTING){
//設(shè)備在連接中
}
}
//成功發(fā)現(xiàn)服務(wù)的回調(diào)
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
if (gatt == null) {
return
}
//設(shè)置回調(diào),打開 Android 端接收通知的開關(guān),用 Descriptor 開啟通知的數(shù)據(jù)開關(guān)
//這里的三個(gè) UUID 都是由硬件決定的
val bluetoothGattService = gatt.getService(UUID_0)
val characteristic = bluetoothGattService.getCharacteristic(UUID_1)
val descriptor = characteristic.getDescriptor(UUID_2)
if (descriptor == null) {
gatt.disconnect()
return
}
//打開 Android 端開關(guān)
if (!gatt.setCharacteristicNotification(characteristic, true)) {
//打開失敗
}
//假如寫入數(shù)據(jù)成功,則會(huì)回調(diào)下面的 onDescriptorWrite() 方法
//所以在 onDescriptorWrite() 方法中向硬件寫入數(shù)據(jù)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
if (!gatt.writeDescriptor(descriptor)) {
//寫入失敗
}
}
//調(diào)用 writeDescriptor 的回調(diào)
override fun onDescriptorWrite(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
val bluetoothGattService = gatt?.getService(UUID_SERVICE_CHANNEL)
val characteristic = bluetoothGattService?.getCharacteristic(UUID_CHARACTERISTIC_CHANNEL)
if (characteristic == null){
//獲取特征失敗,直接斷開連接
gatt?.disconnect()
return
}
//mSendValue 即要往硬件發(fā)送的數(shù)據(jù)
//如果這里寫入數(shù)據(jù)成功會(huì)回調(diào)下面的 onCharacteristicWrite() 方法
characteristic.value = mSendValue
if (!gatt.writeCharacteristic(characteristic)){
//寫入數(shù)據(jù)失敗,斷開連接
gatt.disconnect()
}
}
//調(diào)用 writeCharacteristic 的回調(diào)
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
val stringBuilder = StringBuilder()
characteristic?.value
?.filter { it > 0 }
?.forEach { stringBuilder.append(String.format("%c", it)) }
//這時(shí)候 stringBuilder 應(yīng)該和上面 mSendValue 是一樣的
}
//硬件返回?cái)?shù)據(jù)的回調(diào),由于設(shè)置了回調(diào),所以當(dāng)硬件返回?cái)?shù)據(jù)時(shí)會(huì)調(diào)用這個(gè)方法
override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
super.onCharacteristicChanged(gatt, characteristic)
val stringBuilder = StringBuilder()
characteristic?.value?.forEach {
val b = it
hexStringBuilder.append(Integer.toHexString(b.toInt()))
stringBuilder.append(String.format("%c",b))
}
runOnUiThread { toast("$stringBuilder") }
//接受到數(shù)據(jù)之后就可以斷開連接了
gatt?.disconnect()
}
}
首先是 onConnectionStateChange(gatt,status,newState)
,
這個(gè)方法在成功連接、斷開連接等狀態(tài)改變的時(shí)候回調(diào),所以一開始會(huì)先進(jìn)入這個(gè)方法。
參數(shù)中, newState
代表當(dāng)前設(shè)備的連接的狀態(tài):
/** The profile is in disconnected state */
public static final int STATE_DISCONNECTED = 0;
/** The profile is in connecting state */
public static final int STATE_CONNECTING = 1;
/** The profile is in connected state */
public static final int STATE_CONNECTED = 2;
/** The profile is in disconnecting state */
public static final int STATE_DISCONNECTING = 3;
所以當(dāng) newState
為 2 的時(shí)候就是剛連上設(shè)備的時(shí)候,這時(shí)候可以調(diào)用
gatt.discoverServices()
開始異步的查找藍(lán)牙服務(wù):
if (newState == BluetoothProfile.STATE_CONNECTED){
//發(fā)現(xiàn)服務(wù)
gatt?.discoverServices()
}
執(zhí)行了discoverServices()
后,若找到可用的服務(wù),系統(tǒng)又會(huì)回調(diào) mBluetoothGattCallback
里的onServicesDiscovered()
方法,所以添加:
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
val bluetoothGattService = gatt?.getService(UUID_0)
val characteristic = bluetoothGattService?.getCharacteristic(UUID_1)
if (characteristic == null){
//獲取特征的實(shí)例失敗,斷開連接
gatt?.disconnect()
return
}
//向硬件寫入數(shù)據(jù)
characteristic.value = mSendValue
if (!gatt.writeCharacteristic(characteristic)){
//當(dāng)上面的方法返回 false 時(shí),寫入數(shù)據(jù)失敗
gatt.disconnect()
}
}
如果成功寫入數(shù)據(jù),系統(tǒng)回調(diào)mBluetoothGattCallback
的onCharacteristicWrite()
方法:
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
//這里遍歷 characteristic 中的 value,拼接在一起后成為一個(gè) stringBuilder
//stringBuilder 應(yīng)該和發(fā)送給硬件的數(shù)據(jù)是一樣的
val stringBuilder = StringBuilder()
characteristic?.value
?.filter { it > 0 }
?.forEach { stringBuilder.append(String.format("%c", it)) }
//斷開連接,這一句最好延遲幾秒后執(zhí)行
gatt?.disconnect()
}
上面的代碼可以成功往硬件發(fā)送數(shù)據(jù),但是不能接受硬件返回的數(shù)據(jù)。
如果想要接受硬件返回的數(shù)據(jù),需要在 onServicesDiscovered()
,也就是連上服務(wù)后,先不發(fā)送數(shù)據(jù)而是設(shè)置硬件返回?cái)?shù)據(jù)的開關(guān):
//設(shè)置回調(diào):打開 Android 端接收通知的開關(guān);并且向 Descriptor 寫入數(shù)據(jù)來(lái)開啟通知
val bluetoothGattService = gatt.getService(UUID_SERVICE_CHANNEL)
val characteristic = bluetoothGattService.getCharacteristic(UUID_CHARACTERISTIC_CHANNEL)
val descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID)
val descriptors = characteristic.descriptors
if (descriptors == null) {
//獲取特征描述符失敗,斷開連接
gatt.disconnect()
return
}
//打開 Android 端開關(guān)
if (!gatt.setCharacteristicNotification(characteristic, true)) {
//失敗的處理
}
//向硬件寫入一些數(shù)據(jù),打開硬件返回?cái)?shù)據(jù)的開關(guān)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
if (!gatt.writeDescriptor(descriptor)) {
//寫入數(shù)據(jù)失敗
}
- 實(shí)際上向硬件寫入數(shù)據(jù)這一段代碼有時(shí)候是可以省略的,只需要打開 Android 段的開關(guān)即可接收到返回的數(shù)據(jù),可能是和硬件有關(guān)。這樣一來(lái),就不能繼續(xù)在
onServicesDiscovered()
執(zhí)行寫入數(shù)據(jù)的代碼,改為在onDescriptorWrite()
中執(zhí)行。
還有就是用 Kotlin 寫的 MainActivity 部分:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
verticalLayout {
gravity = CENTER
linearLayout {
orientation = LinearLayout.VERTICAL
button("搜索設(shè)備"){
setOnClickListener {
mBinder?.startScanLeDevice()
}
}.lparams(width = matchParent,height = wrapContent){
padding = dip(5)
margin = dip(10)
}
button("發(fā)送開鎖指令"){
padding = dip(10)
setOnClickListener{
mBinder?.connect()
}
}.lparams(width = matchParent,height = wrapContent){
padding = dip(5)
margin = dip(10)
}
}
}
val intent = Intent(this, BluetoothService::class.java)
bindService(intent,mConnect,Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
mDisposable?.dispose()
}
var mBinder : BluetoothService.MBinder ?= null
var mDisposable : Disposable ?= null
val mConnect = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
mBinder = service as BluetoothService.MBinder
}
override fun onServiceDisconnected(name: ComponentName) {
}
}
}
BLE 相關(guān)的代碼是寫在了 Service 中,通過(guò)綁定時(shí)返回的 mBinder 來(lái)調(diào)用 Service 中的方法。
connectGatt()
不觸發(fā)回調(diào) (BluetoothGattCallback
) 的異常
- 在實(shí)際的操作過(guò)程中遇到過(guò)一些藍(lán)牙設(shè)備,在調(diào)用了
connectGatt
會(huì)無(wú)響應(yīng),既不回調(diào) callback,也不拋出異常;
后續(xù)的 debug 中偶然發(fā)現(xiàn) 6.0 及以上的connectGatt()
新增了可選參數(shù)transport
,支持設(shè)置連接設(shè)備的傳輸模式的;
@param transport preferred transport for GATT connections to remote dual-mode devices
* {@link BluetoothDevice#TRANSPORT_AUTO} or
* {@link BluetoothDevice#TRANSPORT_BREDR} or {@link BluetoothDevice#TRANSPORT_LE}
這一參數(shù)在 5.0 及 5.1 中是無(wú)法直接設(shè)置的,通過(guò)反射調(diào)用后解決了以上出現(xiàn)的無(wú)回調(diào)、無(wú)響應(yīng)問(wèn)題:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
device.connectGatt(mActivity, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE)
} else {
val d = mBluetoothAdapter.getRemoteDevice(device.address)
val creMethod = d.javaClass.getDeclaredMethod("connectGatt",
Context::class.java, Boolean::class.javaPrimitiveType,
BluetoothGattCallback::class.java, Int::class.javaPrimitiveType)
creMethod.isAccessible = true
val transport = d.javaClass.getDeclaredField("TRANSPORT_LE").getInt(null)
mBluetoothAdapter.getRemoteDevice(d.address)
val res = creMethod.invoke(d, mActivity, true, bluetoothGattCallback, transport) as BluetoothGatt
}
連接未徹底斷開
首先遇到的問(wèn)題是 gatt.disconnect
無(wú)法徹底斷開設(shè)備的連接,藍(lán)牙設(shè)備的狀態(tài)為已連接,但 gatt.getConnectionState()
的狀態(tài)缺為已斷開;
參考了一下各路方法,通過(guò)反射 BluetoothDevice
的內(nèi)部類判斷是否連接
public static final int CONNECTION_STATE_DISCONNECTED = 0;
public static final int CONNECTION_STATE_CONNECTED = 1;
public static final int CONNECTION_STATE_UN_SUPPORT = -1;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("PrivateApi")
public static int getInternalConnectionState(String mac) {
//該功能是在21 (5.1.0)以上才支持, 5.0 以及以下 都 不支持
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return CONNECTION_STATE_UN_SUPPORT;
}
if(Build.MANUFACTURER.equalsIgnoreCase("OPPO")){//OPPO勿使用這種辦法判斷, OPPO無(wú)解
return CONNECTION_STATE_UN_SUPPORT;
}
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice remoteDevice = adapter.getRemoteDevice(mac);
Object mIBluetooth = null;
try {
Field sService = BluetoothDevice.class.getDeclaredField("sService");
sService.setAccessible(true);
mIBluetooth = sService.get(null);
} catch (Exception e) {
return CONNECTION_STATE_UN_SUPPORT;
}
if (mIBluetooth == null) return CONNECTION_STATE_UN_SUPPORT;
boolean isConnected;
try {
Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected");
isConnectedMethod.setAccessible(true);
isConnected = (Boolean) isConnectedMethod.invoke(remoteDevice);
isConnectedMethod.setAccessible(false);
} catch (Exception e) {
//如果找不到,說(shuō)明不兼容isConnected, 嘗試去使用getConnectionState 判斷
try {
Method getConnectionState = mIBluetooth.getClass().getDeclaredMethod("getConnectionState", BluetoothDevice.class);
getConnectionState.setAccessible(true);
int state = (Integer) getConnectionState.invoke(mIBluetooth, remoteDevice);
getConnectionState.setAccessible(false);
isConnected = state == CONNECTION_STATE_CONNECTED;
} catch (Exception e1) {
return CONNECTION_STATE_UN_SUPPORT;
}
}
return isConnected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
}
BluetoothGattCallback
各回調(diào)中不可有耗時(shí)操作,否則會(huì)影響下一個(gè)回調(diào)的執(zhí)行。
讀寫問(wèn)題
首先 BLE 的所有操作都是通信的結(jié)果,所以基本都是異步操作,在頻繁讀 / 寫的過(guò)程中不免要等待上一次結(jié)束再繼續(xù)讀 / 寫;
Android BLE 默認(rèn)單次傳輸數(shù)據(jù)包最大為 20 字節(jié),在實(shí)際場(chǎng)景中明顯不夠,一般有兩種思路:
1.設(shè)置 MTU
修改單次傳輸包大小上限: gatt.requestMtu()
,但不同設(shè)備有失敗的可能;
2.分包傳輸,注意連接寫入時(shí)的間隔問(wèn)題,實(shí)際上設(shè)備是每隔一定時(shí)間去讀取一次特征值來(lái)獲取寫入的數(shù)據(jù),BLE 默認(rèn)這個(gè)時(shí)間間隔為 7.5ms (與設(shè)備相關(guān)),如果寫入的時(shí)間間隔小于這個(gè)讀取間隔則會(huì)導(dǎo)致丟包。
因此可以在寫入成功回調(diào)后 (onCharacteristicWrite()
) 再繼續(xù)下一個(gè)寫入,或者粗暴的加一個(gè)時(shí)間間隔,考慮不同設(shè)備的差異 200ms 一般足夠穩(wěn)妥。
3.對(duì)于 (2) 中提到的讀 / 寫的時(shí)間間隔,其實(shí)可以通過(guò) requestConnectionPriority()
來(lái)修改,參數(shù)為:
BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER
BluetoothGatt#CONNECTION_PRIORITY_BALANCED
BluetoothGatt#CONNECTION_PRIORITY_HIGH
分別對(duì)于低功耗、中等、高功耗三種模式。
經(jīng)典藍(lán)牙
參考官方文檔(基礎(chǔ)的應(yīng)用基本上看這一篇文檔就可以了。
其中有些細(xì)節(jié),經(jīng)典藍(lán)牙連接設(shè)備是需要配對(duì)的,而很多藍(lán)牙設(shè)備采用了默認(rèn)的 pin 碼:0000 或 1234 等。
這里就存在優(yōu)化空間,我們可以通過(guò)代碼去設(shè)置并關(guān)閉系統(tǒng)彈出的配對(duì)窗口 (這里的實(shí)踐基于 Android 5.1);
fun connect(device: BluetoothDevice) {
var isBond = false
try {
//檢查是否處于未配對(duì)狀態(tài)
if (device.bondState == BluetoothDevice.BOND_NONE) {
// 監(jiān)聽配對(duì)彈窗的廣播
activity.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// 關(guān)閉配對(duì)彈窗
abortBroadcast()
// 設(shè)置 Pin 碼, 默認(rèn)為 0000
val removeBondMethod = device.javaClass.getDeclaredMethod("setPin", ByteArray::class.java)
removeBondMethod.invoke(device, "0000".toByteArray())
activity.unregisterReceiver(this)
}
}, IntentFilter().apply { addAction("android.bluetooth.device.action.PAIRING_REQUEST") })
// 開始配對(duì)
val creMethod = device.javaClass.getMethod("createBond")
isBond = creMethod.invoke(device) as Boolean
} else {
isBond = true
}
} catch (e: Exception) {
e.printStackTrace()
// onConnectError("連接設(shè)備失敗, 請(qǐng)手動(dòng)配對(duì)藍(lán)牙;", 272)
}
if (!isBond) {
// onConnectError("連接設(shè)備失敗, 請(qǐng)手動(dòng)配對(duì)藍(lán)牙;", 275)
return
}
try {
tmp.connect()
// tmpIn = tmp.getInputStream()
// tmpOut = tmp.getOutputStream()
// mSocket = tmp
// 連接成功
} catch (e: IOException) {
// onConnectError("連接設(shè)備失敗: 287, 請(qǐng)重試;", 287)
e.printStackTrace()
tmp.close()
}
}