前言
萬物互聯(lián)的物聯(lián)網(wǎng)時代的已經(jīng)來臨,ble藍(lán)牙開發(fā)在其中扮演著舉重若輕的角色盅蝗。最近剛好閑一點(diǎn),抽時間梳理下這塊的知識點(diǎn)姆蘸。
涉及ble藍(lán)牙通訊的客戶端(開啟墩莫、掃描、連接乞旦、發(fā)送和接收數(shù)據(jù)贼穆、分包解包)和服務(wù)端(初始化廣播數(shù)據(jù)、開始廣播兰粉、配置Services故痊、Server回調(diào)操作)整個環(huán)節(jié)以及一些常見的問題即踩過的一些坑。
比如
1玖姑、在Android不同版本或不同手機(jī)的適配問題愕秫,掃描不到藍(lán)牙設(shè)備
2、如何避免ble藍(lán)牙連接出現(xiàn)133錯誤焰络?
3戴甩、單次寫的數(shù)據(jù)大小有20字節(jié)限制,如何發(fā)送長數(shù)據(jù)
藍(lán)牙有傳統(tǒng)(經(jīng)典)藍(lán)牙和低功耗藍(lán)牙BLE(Bluetooth Low Energy)之分闪彼,兩者的開發(fā)的API不一樣甜孤,本文主講Ble藍(lán)牙開發(fā),傳統(tǒng)藍(lán)牙不展開畏腕,有需要的可以自行了解缴川。
相對傳統(tǒng)藍(lán)牙,BLE低功耗藍(lán)牙描馅,主要特點(diǎn)是快速搜索把夸,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸铭污。
Demo效果展示
客戶端
服務(wù)端
API
Android4.3(API Level 18)開始引入BLE的核心功能并提供了相應(yīng)的 API恋日。應(yīng)用程序通過這些 API 掃描藍(lán)牙設(shè)備、查詢 services嘹狞、讀寫設(shè)備的 characteristics(屬性特征)等操作岂膳。
BLE藍(lán)牙協(xié)議是GATT協(xié)議, BLE相關(guān)類不多, 全都位于android.bluetooth包和android.bluetooth.le包的幾個類:
android.bluetooth.
.BluetoothGattService 包含多個Characteristic(屬性特征值), 含有唯一的UUID作為標(biāo)識
.BluetoothGattCharacteristic 包含單個值和多個Descriptor, 含有唯一的UUID作為標(biāo)識
.BluetoothGattDescriptor 對Characteristic進(jìn)行描述, 含有唯一的UUID作為標(biāo)識
.BluetoothGatt 客戶端相關(guān)
.BluetoothGattCallback 客戶端連接回調(diào)
.BluetoothGattServer 服務(wù)端相關(guān)
.BluetoothGattServerCallback 服務(wù)端連接回調(diào)
android.bluetooth.le.
.AdvertiseCallback 服務(wù)端的廣播回調(diào)
.AdvertiseData 服務(wù)端的廣播數(shù)據(jù)
.AdvertiseSettings 服務(wù)端的廣播設(shè)置
.BluetoothLeAdvertiser 服務(wù)端的廣播
.BluetoothLeScanner 客戶端掃描相關(guān)(Android5.0新增)
.ScanCallback 客戶端掃描回調(diào)
.ScanFilter 客戶端掃描過濾
.ScanRecord 客戶端掃描結(jié)果的廣播數(shù)據(jù)
.ScanResult 客戶端掃描結(jié)果
.ScanSettings 客戶端掃描設(shè)置
BLE設(shè)備分為兩種設(shè)備: 客戶端(也叫主機(jī)/中心設(shè)備/Central), 服務(wù)端(也叫從機(jī)/外圍設(shè)備/peripheral)
客戶端的核心類是 BluetoothGatt
服務(wù)端的核心類是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE數(shù)據(jù)的核心類是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
下面詳細(xì)講解下客戶端和服務(wù)端的開發(fā)步驟流程
一、BLE客戶端開發(fā)流程
1磅网、申請權(quán)限
安卓手機(jī)涉及藍(lán)牙權(quán)限問題谈截,藍(lán)牙開發(fā)需要在AndroidManifest.xml文件中添加權(quán)限聲明:
<!-- 藍(lán)牙權(quán)限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
為適配安卓6.0以及以上版本需要添加一個模糊定位的權(quán)限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
手機(jī)權(quán)限管理中允許此權(quán)限,否則會出現(xiàn)無法搜索到設(shè)備的情況;
BLE權(quán)限增加了BEL支持檢查
(1).在manifest中添加權(quán)限
<!-- true 表示手機(jī)必須支持BLE傻盟,否則無法安裝!
這里設(shè)為false, 運(yùn)行后在Activity中檢查-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
(2).在Activity中設(shè)置藍(lán)牙
// 檢查是否支持BLE藍(lán)牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Util.toast(this, "本機(jī)不支持低功耗藍(lán)牙嫂丙!");
finish();
return;
}
2娘赴、打開藍(lán)牙
在搜索設(shè)備之前需要詢問打開手機(jī)藍(lán)牙:
//獲取系統(tǒng)藍(lán)牙適配器管理類
private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 詢問打開藍(lán)牙
if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);
}
// 申請打開藍(lán)牙請求的回調(diào)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
Toast.makeText(this, "藍(lán)牙已經(jīng)開啟", Toast.LENGTH_SHORT).show();
} else if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "沒有藍(lán)牙權(quán)限", Toast.LENGTH_SHORT).show();
finish();
}
}
}
3、搜索設(shè)備
注意: BLE設(shè)備地址是動態(tài)變化(每隔一段時間都會變化),而經(jīng)典藍(lán)牙設(shè)備是出廠就固定不變了跟啤!
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 下面使用Android5.0新增的掃描API诽表,掃描返回的結(jié)果更友好,比如BLE廣播數(shù)據(jù)以前是byte[] scanRecord隅肥,而新API幫我們解析成ScanRecord類\
final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(mScanCallback);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bluetoothLeScanner.stopScan(mScanCallback); //停止掃描
isScanning = false;
}
}, 3000);
// 掃描結(jié)果Callback
private final ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {竿奏、
BluetoothDevice dev = result.getDevice() 獲取BLE設(shè)備信息
// result.getScanRecord() 獲取BLE廣播數(shù)據(jù)
}
};
// 舊API是BluetoothAdapter.startLeScan(LeScanCallback callback)方式掃描BLE藍(lán)牙設(shè)備,如下:
mBluetoothAdapter.startLeScan(callback);
private LeScanCallback callback = new LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) {
//device為掃描到的BLE設(shè)備
if(device.getName() == "目標(biāo)設(shè)備名稱"){
//獲取目標(biāo)設(shè)備
targetDevice = device;
}
}
};
4腥放、連接設(shè)備
通過掃描BLE設(shè)備泛啸,根據(jù)設(shè)備名稱區(qū)分出目標(biāo)設(shè)備targetDevice,下一步實(shí)現(xiàn)與目標(biāo)設(shè)備的連接秃症,在連接設(shè)備之前要停止搜索藍(lán)牙候址;停止搜索一般需要一定的時間來完成,最好調(diào)用停止搜索函數(shù)之后加以100ms的延時种柑,保證系統(tǒng)能夠完全停止搜索藍(lán)牙設(shè)備岗仑。停止搜索之后啟動連接過程;
BLE藍(lán)牙的連接方法相對簡單只需調(diào)用connectGatt方法聚请;
public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)荠雕;
參數(shù)說明
- 返回值 BluetoothGatt: BLE藍(lán)牙連接管理類,主要負(fù)責(zé)與設(shè)備進(jìn)行通信驶赏;
- boolean autoConnect:建議置為false炸卑,能夠提升連接速度;
- BluetoothGattCallback callback 連接回調(diào)母市,重要參數(shù)矾兜,BLE通信的核心部分;
5患久、設(shè)備通信
與設(shè)備建立連接之后與設(shè)備通信椅寺,整個通信過程都是在BluetoothGattCallback的異步回調(diào)函數(shù)中完成;
BluetoothGattCallback中主要回調(diào)函數(shù)如下:
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
//連接狀態(tài)改變的Callback
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//服務(wù)發(fā)現(xiàn)成功的Callback
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {
//寫入Characteristic
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//讀取Characteristic
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {
//通知Characteristic
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//寫入Descriptor
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//讀取Descriptor
}
};
上述幾個回調(diào)函數(shù)是BLE開發(fā)中不可缺少的蒋失;
6返帕、等待設(shè)備連接成功
當(dāng)調(diào)用targetdDevice.connectGatt(context, false, gattCallback)后系統(tǒng)會主動發(fā)起與BLE藍(lán)牙設(shè)備的連接,若成功連接到設(shè)備將回調(diào)onConnectionStateChange方法篙挽,其處理過程如下:
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "設(shè)備連接上 開始掃描服務(wù)");
// 連接成功后荆萤,開始掃描服務(wù)
mBluetoothGatt.discoverServices();
}
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 連接斷開
/*連接斷開后的相應(yīng)處理*/
}
};
判斷newState == BluetoothGatt.STATE_CONNECTED表明此時已經(jīng)成功連接到設(shè)備;
7、開啟掃描服務(wù)
mBluetoothGatt.discoverServices();
掃描BLE設(shè)備服務(wù)是安卓系統(tǒng)中關(guān)于BLE藍(lán)牙開發(fā)的重要一步链韭,一般在設(shè)備連接成功后調(diào)用偏竟,掃描到設(shè)備服務(wù)后回調(diào)onServicesDiscovered()函數(shù),函數(shù)原型如下:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
private List<BluetoothGattService> servicesList;
//獲取服務(wù)列表
servicesList = mBluetoothGatt.getServices();
}
- BLE藍(lán)牙協(xié)議下數(shù)據(jù)的通信方式采用BluetoothGattService敞峭、BluetoothGattCharacteristic和BluetoothGattDescriptor三個主要的類實(shí)現(xiàn)通信踊谋;
- BluetoothGattService 簡稱服務(wù),是構(gòu)成BLE設(shè)備協(xié)議棧的組成單位旋讹,一個藍(lán)牙設(shè)備協(xié)議棧一般由一個或者多個BluetoothGattService組成殖蚕;
- BluetoothGattCharacteristic 簡稱特征,一個服務(wù)包含一個或者多個特征沉迹,特征作為數(shù)據(jù)的基本單元睦疫;
- 一個BluetoothGattCharacteristic特征包含一個數(shù)據(jù)值和附加的關(guān)于特征的描述;
- BluetoothGattDescriptor:用于描述特征的類鞭呕,其同樣包含一個value值蛤育;
8、獲取負(fù)責(zé)通信的BluetoothGattCharacteristic
BLE藍(lán)牙開發(fā)主要有負(fù)責(zé)通信的BluetoothGattService完成的葫松。當(dāng)且稱為通信服務(wù)缨伊。通信服務(wù)通過硬件工程師提供的UUID獲取。獲取方式如下:
- BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("藍(lán)牙模塊提供的負(fù)責(zé)通信UUID字符串"));
- 通信服務(wù)中包含負(fù)責(zé)讀寫的BluetoothGattCharacteristic进宝,且分別稱為notifyCharacteristic和writeCharacteristic刻坊。其中notifyCharacteristic負(fù)責(zé)開啟監(jiān)聽,也就是啟動收數(shù)據(jù)的通道党晋,writeCharacteristic負(fù)責(zé)寫入數(shù)據(jù)谭胚;
具體操作方式如下:
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("藍(lán)牙模塊提供的負(fù)責(zé)通信服務(wù)UUID字符串"));
// 例如形式如:49535343-fe7d-4ae5-8fa9-9fafd205e455
notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));
writeCharacteristic = service.getCharacteristic(UUID.fromString("write uuid"));
9、開啟監(jiān)聽
開啟監(jiān)聽未玻,即建立與設(shè)備的通信的首發(fā)數(shù)據(jù)通道灾而,BLE開發(fā)中只有當(dāng)客戶端成功開啟監(jiān)聽后才能與服務(wù)端收發(fā)數(shù)據(jù)。開啟監(jiān)聽的方式如下:
mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
BluetoothGattDescriptor descriptor = characteristic .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//若開啟監(jiān)聽成功則會回調(diào)BluetoothGattCallback中的onDescriptorWrite()方法扳剿,處理方式如下:
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//開啟監(jiān)聽成功旁趟,可以向設(shè)備寫入命令了
Log.e(TAG, "開啟監(jiān)聽成功");
}
};
10、寫入數(shù)據(jù)
BLE單次寫的數(shù)據(jù)量大小是有限制的庇绽,通常是20字節(jié)锡搜,可以嘗試通過requestMTU增大,但不保證能成功瞧掺。分包寫是一種解決方案耕餐,需要定義分包協(xié)議,假設(shè)每個包大小20字節(jié)辟狈,分兩種包肠缔,數(shù)據(jù)包和非數(shù)據(jù)包夏跷。對于數(shù)據(jù)包,頭兩個字節(jié)表示包的序號明未,剩下的都填充數(shù)據(jù)槽华。對于非數(shù)據(jù)包,主要是發(fā)送一些控制信息趟妥。
監(jiān)聽成功后通過向 writeCharacteristic寫入數(shù)據(jù)實(shí)現(xiàn)與服務(wù)端的通信硼莽。寫入方式如下:
//value為客戶端向服務(wù)端發(fā)送的指令
writeCharacteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(writeCharacteristic)
其中:value一般為Hex格式指令,其內(nèi)容由設(shè)備通信的藍(lán)牙通信協(xié)議規(guī)定煮纵;
11、接收數(shù)據(jù)
若寫入指令成功則回調(diào)BluetoothGattCallback中的onCharacteristicWrite()方法偏螺,說明將數(shù)據(jù)已經(jīng)發(fā)送給下位機(jī)行疏;
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "發(fā)送成功");
}
}
若發(fā)送的數(shù)據(jù)符合通信協(xié)議,則服務(wù)端會向客戶端回復(fù)相應(yīng)的數(shù)據(jù)套像。發(fā)送的數(shù)據(jù)通過回調(diào)onCharacteristicChanged()方法獲取酿联,其處理方式如下:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// value為設(shè)備發(fā)送的數(shù)據(jù),根據(jù)數(shù)據(jù)協(xié)議進(jìn)行解析
byte[] value = characteristic.getValue();
}
通過向服務(wù)端發(fā)送指令獲取服務(wù)端的回復(fù)數(shù)據(jù)夺巩,即可完成與設(shè)備的通信過程贞让;
12、斷開連接
當(dāng)與設(shè)備完成通信之后之后一定要斷開與設(shè)備的連接柳譬。調(diào)用以下方法斷開與設(shè)備的連接:
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
二喳张、BLE服務(wù)端開發(fā)流程
1、設(shè)置廣播以及初始化廣播數(shù)據(jù)
//廣播設(shè)置(必須)
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //廣播模式: 低功耗,平衡,低延遲
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //發(fā)射功率級別: 極低,低,中,高
.setTimeout(0)
.setConnectable(true) //能否連接,廣播分為可連接廣播和不可連接廣播
.build();
//廣播數(shù)據(jù)(必須美澳,廣播啟動就會發(fā)送)
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true) //包含藍(lán)牙名稱
.setIncludeTxPowerLevel(true) //包含發(fā)射功率級別
.addManufacturerData(1, new byte[]{23, 33}) //設(shè)備廠商數(shù)據(jù)销部,自定義
.build();
//掃描響應(yīng)數(shù)據(jù)(可選,當(dāng)客戶端掃描時才發(fā)送)
AdvertiseData scanResponse = new AdvertiseData.Builder()
.addManufacturerData(2, new byte[]{66, 66}) //設(shè)備廠商數(shù)據(jù)制跟,自定義
.addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服務(wù)UUID
// .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服務(wù)數(shù)據(jù)舅桩,自定義
.build();
2、開始廣播
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
//BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// ============啟動BLE藍(lán)牙廣播(廣告) ===============
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);
// BLE廣播Callback
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
logTv("BLE廣播開啟成功");
}
@Override
public void onStartFailure(int errorCode) {
logTv("BLE廣播開啟失敗,錯誤碼:" + errorCode);
}
};
3雨膨、配置Services以及Characteristic
// 注意:必須要開啟可連接的BLE廣播擂涛,其它設(shè)備才能發(fā)現(xiàn)并連接BLE服務(wù)端!
// =============啟動BLE藍(lán)牙服務(wù)端======================================
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
//添加可讀+通知characteristic
BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE));
service.addCharacteristic(characteristicRead);
//添加可寫characteristic
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(characteristicWrite);
if (bluetoothManager != null){
mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
}
mBluetoothGattServer.addService(service);
4、Server回調(diào)以及操作
/**
* 服務(wù)事件的回調(diào)
*/
private BluetoothGattServerCallback mBluetoothGattServerCallback= new BluetoothGattServerCallback() {
/**
* 1.連接狀態(tài)發(fā)生變化時
*/
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
Log.e(TAG, String.format("1.onConnectionStateChange:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("1.onConnectionStateChange:status = %s, newState =%s ", status, newState));
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
Log.e(TAG, String.format("onServiceAdded:status = %s", status));
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("onCharacteristicReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onCharacteristicReadRequest:requestId = %s, offset = %s", requestId, offset));
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
}
/**
* 3. onCharacteristicWriteRequest,接收具體的字節(jié)
*/
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:requestId = %s, preparedWrite=%s, responseNeeded=%s, offset=%s, value=%s", requestId, preparedWrite, responseNeeded, offset, requestBytes.toString()));
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);
//4.處理響應(yīng)內(nèi)容
onResponseToClient(requestBytes, device, requestId, characteristic);
}
/**
* 2.描述被寫入時聊记,在這里執(zhí)行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS... 收撒妈,觸發(fā) onCharacteristicWriteRequest
*/
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.e(TAG, String.format("2.onDescriptorWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("2.onDescriptorWriteRequest:requestId = %s, preparedWrite = %s, responseNeeded = %s, offset = %s, value = %s,", requestId, preparedWrite, responseNeeded, offset, value.toString()));
// now tell the connected device that this was all successfull
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
/**
* 5.特征被讀取。當(dāng)回復(fù)響應(yīng)成功后排监,客戶端會讀取然后觸發(fā)本方法
*/
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
Log.e(TAG, String.format("onDescriptorReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onDescriptorReadRequest:requestId = %s", requestId));
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.e(TAG, String.format("5.onNotificationSent:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("5.onNotificationSent:status = %s", status));
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.e(TAG, String.format("onMtuChanged:mtu = %s", mtu));
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.e(TAG, String.format("onExecuteWrite:requestId = %s", requestId));
}
};
/**
* 4.處理響應(yīng)內(nèi)容
*
* @param reqeustBytes
* @param device
* @param requestId
* @param characteristic
*/
private void onResponseToClient(byte[] reqeustBytes, BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("4.onResponseToClient:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("4.onResponseToClient:requestId = %s", requestId));
Log.e(TAG, "4.收到:");
String str = new String(reqeustBytes) + " hello>";
characteristicRead.setValue(str.getBytes());
mGattServer.notifyCharacteristicChanged(device, characteristicRead, false);
Log.i(TAG, "4.響應(yīng):" + str);
MainActivity.handler.obtainMessage(MainActivity.DEVICE, new String(reqeustBytes)).sendToTarget();
}
三踩身、源碼下載
源碼上傳在CSDN上了,有需要的可以借鑒社露。
=====> Android藍(lán)牙Ble通訊Demo示例源碼–掃描,連接,發(fā)送和接收數(shù)據(jù),分包解包
四挟阻、藍(lán)牙操作的注意事項(xiàng)
1、如何避免ble藍(lán)牙連接出現(xiàn)133錯誤?
- Android 連接外圍設(shè)備的數(shù)量有限附鸽,當(dāng)不需要連接藍(lán)牙設(shè)備的時候脱拼,必須調(diào)用 BluetoothGatt#close 方法釋放資源;
- 藍(lán)牙 API 連接藍(lán)牙設(shè)備的超時時間大概在 20s 左右坷备,具體時間看系統(tǒng)實(shí)現(xiàn)熄浓。有時候某些設(shè)備進(jìn)行藍(lán)牙連接的時間會很長,大概十多秒省撑。如果自己手動設(shè)置了連接超時時間在某些設(shè)備上可能會導(dǎo)致接下來幾次的連接嘗試都會在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133赌蔑;
-
能否避免android設(shè)備與ble設(shè)備連接/斷開時上報的133這類錯誤?
1、在連接失敗或者斷開連接之后竟秫,調(diào)用 close 并刷新緩存
2娃惯、盡量不要在startLeScan的時候嘗試連接,先stopLeScan后再去連
3肥败、對同一設(shè)備斷開后再次連接(連接失敗重連)趾浅,哪怕調(diào)用完close,需要等待一段時間(400毫秒試了1次馒稍,結(jié)果不 行皿哨;1000毫秒則再沒出現(xiàn)過問題)后再去connectGatt
4、可以在連接前都startLeScan一下纽谒,成功率要高一點(diǎn)
2证膨、單次寫的數(shù)據(jù)大小有20字節(jié)限制,如何發(fā)送長數(shù)據(jù)鼓黔?
BLE單次寫的數(shù)據(jù)量大小是有限制的椎例,通常是20字節(jié),可以嘗試通過requestMTU增大请祖,但不保證能成功订歪。分包寫是一種解決方案,需要定義分包協(xié)議肆捕,假設(shè)每個包大小20字節(jié)刷晋,分兩種包,數(shù)據(jù)包和非數(shù)據(jù)包慎陵。對于數(shù)據(jù)包眼虱,頭兩個字節(jié)表示包的序號,剩下的都填充數(shù)據(jù)席纽。對于非數(shù)據(jù)包捏悬,主要是發(fā)送一些控制信息。
總體流程如下:
1润梯、定義通訊協(xié)議过牙,如下(這里只是個舉例甥厦,可以根據(jù)項(xiàng)目需求擴(kuò)展)
消息號(1個字節(jié)) | 功能(1個字節(jié)) | 子功能(1個字節(jié)) | 數(shù)據(jù)長度(2個字節(jié)) | 數(shù)據(jù)內(nèi)容(N個字節(jié)) | CRC校驗(yàn)(1個字節(jié)) |
---|---|---|---|---|---|
01 | 01 | 01 | 0000 | -- | 2D |
2、封裝通用發(fā)送數(shù)據(jù)接口(拆包)
該接口根據(jù)會發(fā)送數(shù)據(jù)內(nèi)容按最大字節(jié)數(shù)拆分(一般20字節(jié))放入隊(duì)列寇钉,拆分完后刀疙,依次從隊(duì)列里取出發(fā)送
3、封裝通用接收數(shù)據(jù)接口(組包)
該接口根據(jù)從接收的數(shù)據(jù)按協(xié)議里的定義解析數(shù)據(jù)長度判讀是否完整包扫倡,不是的話把每條消息累加起來
4谦秧、解析完整的數(shù)據(jù)包,進(jìn)行業(yè)務(wù)邏輯處理
5撵溃、協(xié)議還可以引入加密解密疚鲤,需要注意的選算法參數(shù)的時候,加密后的長度最好跟原數(shù)據(jù)長度一致缘挑,這樣不會影響拆包組包
3集歇、在Android不同版本或不同的手機(jī)掃描不到藍(lán)牙設(shè)備
一般都是Android版本適配以及不同ROM機(jī)型(小米/紅米、華為/榮耀等)(EMUI卖哎、MIUI、ColorOS等)的權(quán)限問題
4删性、其它
- 藍(lán)牙的寫入操作, 讀取操作必須序列化進(jìn)行. 寫入數(shù)據(jù)和讀取數(shù)據(jù)是不能同時進(jìn)行的, 如果調(diào)用了寫入數(shù)據(jù)的方法, 馬上調(diào)用又調(diào)用寫入數(shù)據(jù)或者讀取數(shù)據(jù)的方法,第二次調(diào)用的方法會立即返回 false, 代表當(dāng)前無法進(jìn)行操作亏娜;
總結(jié)
藍(lán)牙開發(fā)中有很多問題,要靜下心分析問題蹬挺,肯定可以解決的维贺,一起加油;