- BLE 與經(jīng)典藍(lán)牙的區(qū)別
- BLE 的 Kotlin 下實踐
- BluetoothGattCallback 不回調(diào)異常
- 一些不常見的問題和暴力解決的方法
- 經(jīng)典藍(lán)牙自動配對,關(guān)閉系統(tǒng)配對彈窗
經(jīng)典藍(lán)牙(Classic Bluetooth)& 低功耗藍(lán)牙(Bluetooth Low Energy)
經(jīng)典藍(lán)牙可以用與數(shù)據(jù)量比較大的傳輸姓惑,如語音,音樂旱眯,較高數(shù)據(jù)量傳輸?shù)取?/p>
BLE 特點(diǎn)就如其名喂链,功耗更低的同時铐姚,對數(shù)據(jù)包做出了限制策肝。所以適用于實時性要求比較高,但是數(shù)據(jù)速率比較低的產(chǎn)品隐绵,如鼠標(biāo)之众,鍵盤,傳感設(shè)備的數(shù)據(jù)發(fā)送等依许。
藍(lán)牙 4.0 支持單模和雙模兩種部署方式棺禾,其中單模即是我們說的 BLE,而雙模指的是 Classic Bluetooth + BLE 悍手。
實際上帘睦,BLE 和經(jīng)典藍(lán)牙的使用等各方面都像是沒有關(guān)聯(lián)的兩個東西袍患,甚至因為 BLE 的通訊機(jī)制不同坦康,所以是不能向下兼容的;經(jīng)典藍(lán)牙則可以兼容到藍(lán)牙 3.0 / 2.1诡延。
BLE
同樣滞欠,有條件一定要去看官方文檔,然而這一次并沒有中文版肆良,或許可以找一些國內(nèi)大佬們翻譯的版本筛璧。
還有就是大佬 JBD 寫的 Android BLE 藍(lán)牙開發(fā)入門 ,而且還用 RxJava 封裝成一個庫可以直接調(diào)用:RxBLE 惹恃,是真的厲害夭谤,不妨去學(xué)習(xí)學(xué)習(xí)。
- 概念與常用 API
UUID:每個服務(wù)和特征都會有唯一的 UUID 巫糙,由硬件決定朗儒。
服務(wù)(Service):藍(lán)牙設(shè)備中可以定義多個服務(wù),相當(dāng)于功能的集合参淹。
特征(Characteristic):一個服務(wù)可以包含多個特征醉锄,可以通過 UUID 獲取到對應(yīng)的特征的實例,通過這個實例就可以向藍(lán)牙設(shè)備發(fā)送 / 讀取數(shù)據(jù)浙值。
BluetoothDeivce
:調(diào)用 startLeScan()
獲取該實例恳不,用于連接設(shè)備。
BluetoothManager
:藍(lán)牙管理器开呐,調(diào)用 getSystemService()
獲取烟勋,用于獲取藍(lán)牙適配器和管理所有和藍(lán)牙相關(guān)的東西规求。
BluetoothAdapter
:藍(lán)牙適配器,通過 BluetoothManager
獲取卵惦,用于打開藍(lán)牙颓哮、開始掃描設(shè)備等操作。
BluetoothGatt
:通用屬性協(xié)議鸵荠, 定義了BLE通訊的基本規(guī)則冕茅,就是通過把數(shù)據(jù)包裝成服務(wù)和特征的約定過程。
BluetoothGattCallback
:一個回調(diào)類蛹找,非常重要而且會頻繁使用姨伤,用于回調(diào) GATT 通信的各種狀態(tài)和結(jié)果。
BluetoothGattService
:服務(wù)庸疾,通過 BluetoothGatt
實例調(diào)用 getService(UUID) 獲取
乍楚。
BluetoothGattCharacteristic
:特征团赁,通過 BluetoothGattService
實例調(diào)用 getCharacteristic(UUID)
獲取栗柒,是 GATT 通信中的最小數(shù)據(jù)單元。
BluetoothGattDescriptor
:特征描述符趣些,對特征的額外描述金顿,包括但不僅限于特征的單位臊泌,屬性等。
- 聲明權(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)牙沒有打開則向用戶申請
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled)
bluetoothAdapter.enable()
}
- 掃描設(shè)備與停止掃描
var mDevice : BluetoothDevice ?= null
//掃描結(jié)果的回調(diào)揍拆,開始掃描后會多次調(diào)用該方法
val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
//通過對比設(shè)備的 mac 地址獲取需要的實例
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è)備時最好調(diào)用 stopLeScan()
mBluetoothAdapter?.stopLeScan(mLeScanCallback)
mScanning = false
}
}
其實 startLeScan()
已經(jīng)被聲明為過時嫂拴,所以開始掃描可以以廣播的形式接受:
private fun startDiscover() {
//這種方法需要注冊接收廣播播揪,獲取掃描結(jié)果。
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter?.startDiscovery()
}
//注冊廣播筒狠,監(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)
// 過濾器 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è)備
此時已經(jīng)獲取到了藍(lán)牙設(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()
的幾個參數(shù):
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback)
第二個參數(shù)辩恼,autoConnect
為 true 時雇庙,如果設(shè)備斷開了連接將會不斷的嘗試連接。
第三個 BluetoothGattCallback
是一個接受回調(diào)的對象运挫,也是這一部分的重點(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)后,還要手動調(diào)用 close()谁帕,否則可能出現(xiàn)連接未斷開導(dǎo)致的重連失敗等問題;
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)
//這里的三個 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ù)成功,則會回調(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ù)成功會回調(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)) }
//這時候 stringBuilder 應(yīng)該和上面 mSendValue 是一樣的
}
//硬件返回數(shù)據(jù)的回調(diào),由于設(shè)置了回調(diào),所以當(dāng)硬件返回數(shù)據(jù)時會調(diào)用這個方法
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)
峡继,
這個方法在成功連接、斷開連接等狀態(tài)改變的時候回調(diào)匈挖,所以一開始會先進(jìn)入這個方法碾牌。
參數(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è)備的時候,這時候可以調(diào)用
gatt.discoverServices()
開始異步的查找藍(lán)牙服務(wù):
if (newState == BluetoothProfile.STATE_CONNECTED){
//發(fā)現(xiàn)服務(wù)
gatt?.discoverServices()
}
執(zhí)行了discoverServices()
后舶吗,若找到可用的服務(wù)征冷,系統(tǒng)又會回調(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){
//獲取特征的實例失敗誓琼,斷開連接
gatt?.disconnect()
return
}
//向硬件寫入數(shù)據(jù)
characteristic.value = mSendValue
if (!gatt.writeCharacteristic(characteristic)){
//當(dāng)上面的方法返回 false 時检激,寫入數(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腹侣,拼接在一起后成為一個 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è)置硬件返回數(shù)據(jù)的開關(guān):
//設(shè)置回調(diào):打開 Android 端接收通知的開關(guān);并且向 Descriptor 寫入數(shù)據(jù)來開啟通知
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ù)复濒,打開硬件返回數(shù)據(jù)的開關(guān)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
if (!gatt.writeDescriptor(descriptor)) {
//寫入數(shù)據(jù)失敗
}
- 實際上向硬件寫入數(shù)據(jù)這一段代碼有時候是可以省略的,只需要打開 Android 段的開關(guān)即可接收到返回的數(shù)據(jù)乒省,可能是和硬件有關(guān)巧颈。這樣一來,就不能繼續(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 中馋劈,通過綁定時返回的 mBinder 來調(diào)用 Service 中的方法攻锰。
connectGatt()
不觸發(fā)回調(diào) (BluetoothGattCallback
) 的異常
- 在實際的操作過程中遇到過一些藍(lán)牙設(shè)備,在調(diào)用了
connectGatt
會無響應(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 中是無法直接設(shè)置的,通過反射調(diào)用后解決了以上出現(xiàn)的無回調(diào)楷拳、無響應(yīng)問題:
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
}
連接未徹底斷開
首先遇到的問題是 gatt.disconnect
無法徹底斷開設(shè)備的連接绣夺,藍(lán)牙設(shè)備的狀態(tài)為已連接,但 gatt.getConnectionState()
的狀態(tài)缺為已斷開欢揖;
參考了一下各路方法陶耍,通過反射 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無解
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) {
//如果找不到,說明不兼容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)中不可有耗時操作,否則會影響下一個回調(diào)的執(zhí)行她混。
讀寫問題
首先 BLE 的所有操作都是通信的結(jié)果烈钞,所以基本都是異步操作泊碑,在頻繁讀 / 寫的過程中不免要等待上一次結(jié)束再繼續(xù)讀 / 寫;
Android BLE 默認(rèn)單次傳輸數(shù)據(jù)包最大為 20 字節(jié)毯欣,在實際場景中明顯不夠馒过,一般有兩種思路:
1.設(shè)置 MTU
修改單次傳輸包大小上限: gatt.requestMtu()
,但不同設(shè)備有失敗的可能酗钞;
2.分包傳輸腹忽,注意連接寫入時的間隔問題,實際上設(shè)備是每隔一定時間去讀取一次特征值來獲取寫入的數(shù)據(jù)砚作,BLE 默認(rèn)這個時間間隔為 7.5ms (與設(shè)備相關(guān))留凭,如果寫入的時間間隔小于這個讀取間隔則會導(dǎo)致丟包。
因此可以在寫入成功回調(diào)后 (onCharacteristicWrite()
) 再繼續(xù)下一個寫入偎巢,或者粗暴的加一個時間間隔蔼夜,考慮不同設(shè)備的差異 200ms 一般足夠穩(wěn)妥。
3.對于 (2) 中提到的讀 / 寫的時間間隔压昼,其實可以通過 requestConnectionPriority()
來修改求冷,參數(shù)為:
BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER
BluetoothGatt#CONNECTION_PRIORITY_BALANCED
BluetoothGatt#CONNECTION_PRIORITY_HIGH
分別對于低功耗、中等窍霞、高功耗三種模式匠题。
經(jīng)典藍(lán)牙
參考官方文檔(基礎(chǔ)的應(yīng)用基本上看這一篇文檔就可以了。
其中有些細(xì)節(jié)但金,經(jīng)典藍(lán)牙連接設(shè)備是需要配對的韭山,而很多藍(lán)牙設(shè)備采用了默認(rèn)的 pin 碼:0000 或 1234 等。
這里就存在優(yōu)化空間冷溃,我們可以通過代碼去設(shè)置并關(guān)閉系統(tǒng)彈出的配對窗口 (這里的實踐基于 Android 5.1);
fun connect(device: BluetoothDevice) {
var isBond = false
try {
//檢查是否處于未配對狀態(tài)
if (device.bondState == BluetoothDevice.BOND_NONE) {
// 監(jiān)聽配對彈窗的廣播
activity.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// 關(guān)閉配對彈窗
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") })
// 開始配對
val creMethod = device.javaClass.getMethod("createBond")
isBond = creMethod.invoke(device) as Boolean
} else {
isBond = true
}
} catch (e: Exception) {
e.printStackTrace()
// onConnectError("連接設(shè)備失敗, 請手動配對藍(lán)牙;", 272)
}
if (!isBond) {
// onConnectError("連接設(shè)備失敗, 請手動配對藍(lán)牙;", 275)
return
}
try {
tmp.connect()
// tmpIn = tmp.getInputStream()
// tmpOut = tmp.getOutputStream()
// mSocket = tmp
// 連接成功
} catch (e: IOException) {
// onConnectError("連接設(shè)備失敗: 287, 請重試;", 287)
e.printStackTrace()
tmp.close()
}
}