日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

android蓝牙BLE 有源码 有视频

發(fā)布時間:2024/3/26 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android蓝牙BLE 有源码 有视频 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前序
? Google在android 4.3(API Level 18)的android版本中引入了低功耗藍牙BLE核心API。低功耗藍牙BLE也就是我們經(jīng)常說的藍牙4.0, 該技術(shù)擁有極低的運行和待機功耗,使用一粒紐扣電池甚至可連續(xù)工作數(shù)年之久。先不講藍牙協(xié)議與藍牙模塊一些類的作用與之間的關(guān)系,本章僅僅記錄android Ble開發(fā)中的掃描模塊及其一些細節(jié)。

一、聲明藍牙權(quán)限和定位權(quán)限

? 由于藍牙掃描需要用到模糊定位權(quán)限( Android10 后需要精準定位權(quán)限 ),所以android6.0之后,除了在 AndroidManifest.xml中 申明權(quán)限之外,還需要動態(tài)申請定位權(quán)限,才可進行藍牙掃描,否則不會掃描到任何Ble設(shè)備。

可依據(jù) PackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) 獲知該手機是否支持BLE

二、中心設(shè)備與外圍設(shè)備
Ble開發(fā)中,存在著兩個角色:中心設(shè)備角色和外圍設(shè)備角色。粗略了解下:

外圍設(shè)備:一般指非常小或者低功耗設(shè)備,更強大的中心設(shè)備可以連接外圍設(shè)備為中心設(shè)備提供數(shù)據(jù)。外設(shè)會不停的向外廣播,讓中心設(shè)備知道它的存在。 例如小米手環(huán)。
中心設(shè)備:可以掃描并連接多個外圍設(shè)備,從外設(shè)中獲取信息。
? 外圍設(shè)備會設(shè)定一個廣播間隔,每個廣播間隔中,都會發(fā)送自己的廣播數(shù)據(jù)。廣播間隔越長,越省電。一個沒有被連接的Ble外設(shè)會不斷發(fā)送廣播數(shù)據(jù),這時可以被多個中心設(shè)備發(fā)現(xiàn)。一旦外設(shè)被連接,則會馬上停止廣播。

? android 4.3 時引入的Ble核心Api只支持android手機作為中心設(shè)備角色,當android 5.0 更新Api后,android手機支持充當作為外設(shè)角色和中心角色。即 android 5.0 引入了外設(shè)角色的Api,同時也更新了部分中心角色的Api。比如:中心角色中,更新了藍牙掃描的Api。

三、打開藍牙
//初始化ble設(shè)配器
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();
//判斷藍牙是否開啟,如果關(guān)閉則請求打開藍牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//方式一:請求打開藍牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
//方式二:半靜默打開藍牙
//低版本android會靜默打開藍牙,高版本android會請求打開藍牙
//mBluetoothAdapter.enable();
}
mBluetoothAdapter.isEnabled()判斷當前藍牙是否打開,如果藍牙處于打開狀態(tài)返回true。

同時可以在activity層通過廣播監(jiān)聽藍牙的關(guān)閉與開啟,進行自己的邏輯處理:

new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//獲取藍牙廣播 本地藍牙適配器的狀態(tài)改變時觸發(fā)
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//獲取藍牙廣播中的藍牙新狀態(tài)
int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
//獲取藍牙廣播中的藍牙舊狀態(tài)
int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueNewState) {
//正在打開藍牙
case BluetoothAdapter.STATE_TURNING_ON:
break;
//藍牙已打開
case BluetoothAdapter.STATE_ON:
break;
//正在關(guān)閉藍牙
case BluetoothAdapter.STATE_TURNING_OFF:
break;
//藍牙已關(guān)閉
case BluetoothAdapter.STATE_OFF:
break;
}
}
}
};
四、掃描
android 4.3 掃描
在android 4.3 和 android 4.4進行藍牙掃描,可使用BluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback)進行藍牙掃描。

//開始掃描
mBluetoothAdapter.startLeScan(mLeScanCallback);
//停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
android 5.0以上 掃描
在 android 5.0之后的版本(包括 5.0)建議使用新的Api進行藍牙掃描:

BluetoothLeScanner.startScan(ScanCallback)
BluetoothLeScanner.startScan(List, ScanSettings, ScanCallback)。
//獲取 5.0 的掃描類實例
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
//開始掃描
//可設(shè)置過濾條件,在第一個參數(shù)傳入,但一般不設(shè)置過濾。
mBLEScanner.startScan(null,mScanSettings,mScanCallback);
//停止掃描
mBLEScanner.stopScan(mScanCallback);
藍牙掃描示例
//如果沒打開藍牙,不進行掃描操作,或請求打開藍牙。
if(!mBluetoothAdapter.isEnabled()) {
return;
}
//處于未掃描的狀態(tài)
if (!mScanning){
//android 5.0后
if(android.os.Build.VERSION.SDK_INT >= 21) {
//標記當前的為掃描狀態(tài)
mScanning = true;
//獲取5.0新添的掃描類
if (mBLEScanner == null){
//mBLEScanner是5.0新添加的掃描類,通過BluetoothAdapter實例獲取。
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
//開始掃描
//mScanSettings是ScanSettings實例,mScanCallback是ScanCallback實例,后面進行講解。
mBLEScanner.startScan(null,mScanSettings,mScanCallback);
} else {
//標記當前的為掃描狀態(tài)
mScanning = true;
//5.0以下 開始掃描
//mLeScanCallback是BluetoothAdapter.LeScanCallback實例
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
//設(shè)置結(jié)束掃描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//停止掃描設(shè)備
if(android.os.Build.VERSION.SDK_INT >= 21) {
//標記當前的為未掃描狀態(tài)
mScanning = false;
mBLEScanner.stopScan(mScanCallback);
} else {
//標記當前的為未掃描狀態(tài)
mScanning = false;
//5.0以下 停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
},SCAN_TIME);
}
掃描代碼如上述所示,當掃描到所需要的設(shè)備的時候,就要手動馬上停止藍牙掃描,因為藍牙掃描是耗電操作。

注意事項
android 6.0 以上需要獲取到定位權(quán)限。否則會報如下運行時異常:
image
android 7.0 后不能在30秒內(nèi)掃描+停止超過5次。(官網(wǎng)沒特意說明,可自行測試,設(shè)置掃描時長為3秒,連續(xù)掃描10次,穩(wěn)定復(fù)現(xiàn)5次后不能掃描到任何設(shè)備。android 藍牙模塊會打印當前應(yīng)用掃描太頻繁的log日志,并在android 5.0 的ScanCallback回調(diào)中觸發(fā)onScanFailed(int),返回錯誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描)。
image
五、掃描回調(diào)
android 4.3 掃描回調(diào):LeScanCallback
android 4.3 的掃描回調(diào)接口BluetoothAdapter.LeScanCallback:
image

回調(diào)接口中只有一個回調(diào)函數(shù)onLeScan,掃描到的設(shè)備會通過該方法返回。
參數(shù):
BluetoothDevice 掃描到的設(shè)備實例,可從實例中獲取到相應(yīng)的信息。如:名稱,mac地址
rssi 可理解成設(shè)備的信號值。該數(shù)值是一個負數(shù),越大則信號越強。
scanRecord 遠程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容。
//5.0以下
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//對掃描到的設(shè)備進行操作。如:獲取設(shè)備信息。

}

};
獲取BluetoothDevice中的信息
image
可以從中獲取到設(shè)備的mac地址,設(shè)備名稱,綁定狀態(tài)和設(shè)備類型等信息,并作相應(yīng)的保存。

mac可用于再創(chuàng)建BluetoothDevice對象進行g(shù)att連接。

綁定狀態(tài):

BOND_NONE:數(shù)值 10
表示遠程設(shè)備未綁定,沒有共享鏈接密鑰,因此通信(如果允許的話)將是未經(jīng)身份驗證和未加密的。(掃描到未綁定的小米手環(huán))
BOND_BONDING:數(shù)值 11 表示正在與遠程設(shè)備進行綁定;
BOND_BONDED:數(shù)值 12 表示遠程設(shè)備已綁定,遠程設(shè)備本地存儲共享連接的密鑰,因此可以對通信進行身份驗證和加密。(掃描到已綁定的小米手環(huán))
設(shè)備類型:一般是2,表示LE設(shè)備

android 5.0 掃描回調(diào):ScanCallback
mScanCallback = new ScanCallback() {
//當一個藍牙ble廣播被發(fā)現(xiàn)時回調(diào)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//掃描類型有開始掃描時傳入的ScanSettings相關(guān)
//對掃描到的設(shè)備進行操作。如:獲取設(shè)備信息。

}//批量返回掃描結(jié)果 //@param results 以前掃描到的掃描結(jié)果列表。 @Override public void onBatchScanResults(List<ScanResult> results) {super.onBatchScanResults(results);}//當掃描不能開啟時回調(diào) @Override public void onScanFailed(int errorCode) {super.onScanFailed(errorCode);//掃描太頻繁會返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描。}

};
ScanCallback掃描回調(diào)存在三個回調(diào)函數(shù):

onScanResult(int,ScanResult):類似于BluetoothAdapter.LeScanCallback中的onLeScan(),可在ScanResult實例中獲取到BluetoothDevice藍牙設(shè)備對象,rssi信號值等信息。一般都是在該函數(shù)中回調(diào)獲取掃描到藍牙設(shè)備和信號值,在本函數(shù)中執(zhí)行和onLeScan()中相同的邏輯處理即可。
onBatchScanResults(List) 批量返回掃描結(jié)果。
onScanFailed(int) 掃描失敗返回錯誤碼。
注意事項
回調(diào)函數(shù)中盡量不要做耗時操作!
一般藍牙設(shè)備對象都是通過onScanResult(int,ScanResult)返回,而不會在onBatchScanResults(List)方法中返回,除非手機支持批量掃描模式并且開啟了批量掃描模式。批處理的開啟請查看ScanSettings。
六、掃描設(shè)置
? ScanSettings實例對象是通過ScanSettings.Builder構(gòu)建的。通過Builder對象為ScanSettings實例設(shè)置掃描模式、回調(diào)類型、匹配模式等參數(shù),用于配置android 5.0 的掃描參數(shù)。

//創(chuàng)建ScanSettings的build對象用于設(shè)置參數(shù)
ScanSettings.Builder builder = new ScanSettings.Builder()
//設(shè)置高功耗模式
.setScanMode(SCAN_MODE_LOW_LATENCY);
//android 6.0添加設(shè)置回調(diào)類型、匹配模式等
if(android.os.Build.VERSION.SDK_INT >= 23) {
//定義回調(diào)類型
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
//設(shè)置藍牙LE掃描濾波器硬件匹配的匹配模式
builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
}
//芯片組支持批處理芯片上的掃描
if (bluetoothadapter.isOffloadedScanBatchingSupported()) {
//設(shè)置藍牙LE掃描的報告延遲的時間(以毫秒為單位)
//設(shè)置為0以立即通知結(jié)果
builder.setReportDelay(0L);
}
builder.build();
配置描述:

setScanMode() 設(shè)置掃描模式??蛇x擇模式主要三種( 從上到下,會越來越耗電,但掃描間隔越來越短,即掃描速度會越來越快。):
ScanSettings.SCAN_MODE_LOW_POWER 低功耗模式(默認掃描模式,如果掃描應(yīng)用程序不在前臺,則強制使用此模式。)
ScanSettings.SCAN_MODE_BALANCED 平衡模式
ScanSettings.SCAN_MODE_LOW_LATENCY 高功耗模式(建議僅在應(yīng)用程序在前臺運行時才使用此模式。)
setCallbackType() 設(shè)置回調(diào)類型??蛇x擇模式主要三種:
ScanSettings.CALLBACK_TYPE_ALL_MATCHES 數(shù)值: 1。

尋找符合過濾條件的藍牙廣播,如果沒有設(shè)置過濾條件,則返回全部廣播包

ScanSettings.CALLBACK_TYPE_FIRST_MATCH 數(shù)值: 2

僅針對與篩選條件匹配的第一個廣播包觸發(fā)結(jié)果回調(diào)。

ScanSettings.CALLBACK_TYPE_MATCH_LOST 數(shù)值: 4

回調(diào)類型一般設(shè)置ScanSettings.CALLBACK_TYPE_ALL_MATCHES,有過濾條件時過濾,返回符合過濾條件的藍牙廣播。無過濾條件時,返回全部藍牙廣播。

setMatchMode() 設(shè)置藍牙LE掃描濾波器硬件匹配的匹配模式
ScanSettings.MATCH_MODE_STICKY 粘性模式,在通過硬件報告之前,需要更高的信號強度和目擊閾值
MATCH_MODE_AGGRESSIVE 激進模式,即使信號強度微弱且持續(xù)時間內(nèi)瞄準/匹配的次數(shù)很少,hw也會更快地確定匹配。
Bluetoothadapter.isOffloadedScanBatchingSupported() 判斷當前手機藍牙芯片是否支持批處理掃描。
如果支持掃描則使用批處理掃描,可通過ScanSettings.Builder對象調(diào)用setReportDelay(Long)方法來設(shè)置藍牙LE掃描的報告延遲的時間(以毫秒為單位)來啟動批處理掃描模式。
ScanSettings.Builder.setReportDelay(Long);
當設(shè)備藍牙芯片支持批處理掃描時,用來設(shè)置藍牙LE掃描的報告延遲的時間(以毫秒為單位)。
該參數(shù)默認為 0,如果不修改它的值,則默認只會在onScanResult(int,ScanResult)中返回掃描到的藍牙設(shè)備,不會觸發(fā)不會觸發(fā)onBatchScanResults(List)方法。(onScanResult(int,ScanResult) 和 onBatchScanResults(List) 是互斥的。 )
設(shè)置為0以立即通知結(jié)果,不開啟批處理掃描模式。即ScanCallback藍牙回調(diào)中,不會觸發(fā)onBatchScanResults(List)方法,但會觸發(fā)onScanResult(int,ScanResult)方法,返回掃描到的藍牙設(shè)備。
當設(shè)置的時間大于0L時,則會開啟批處理掃描模式。即觸發(fā)onBatchScanResults(List)方法,返回掃描到的藍牙設(shè)備列表。但不會觸發(fā)onScanResult(int,ScanResult)方法。
提示
Android 10 進行BLE掃描時需要打開GPS。

具體源碼可參考Ble實戰(zhàn)BleScanHelper.kt

Android 8更新了一個掃描API,系統(tǒng)層為你提供后臺持續(xù)掃描的能力。(即便APP已被殺死,掃描仍會繼續(xù)。但如果用戶重啟或關(guān)閉藍牙后,該掃描停止)。 具體可查看官網(wǎng): BluetoothLeScanner#startScan。

public int startScan (List filters,
ScanSettings settings,
PendingIntent callbackIntent)

作者:大棋17
鏈接:https://www.jianshu.com/p/2dba7f067372
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

一、藍牙基礎(chǔ)協(xié)議
想了解藍牙通信之前,需要先了解藍牙兩個最基本的協(xié)議:GAP 和 GATT。

GAP(Generic Access Profile)簡介
? GAP是通用訪問配置文件的首字母縮寫,主要控制藍牙連接和廣播。GAP使藍牙設(shè)備對外界可見,并決定設(shè)備是否可以或者怎樣與其他設(shè)備進行交互。

GAP定義了多種角色,但主要的兩個是:中心設(shè)備 和 外圍設(shè)備。

中心設(shè)備:可以掃描并連接多個外圍設(shè)備,從外設(shè)中獲取信息。

外圍設(shè)備:小型,低功耗,資源有限的設(shè)備。可以連接到功能更強大的中心設(shè)備,并為其提供數(shù)據(jù)。

GAP廣播數(shù)據(jù)
GAP 中外圍設(shè)備通過兩種方式向外廣播數(shù)據(jù):廣播數(shù)據(jù) 和 掃描回復(fù)( 每種數(shù)據(jù)最長可以包含 31 byte。)。

? 廣播數(shù)據(jù)是必需的,因為外設(shè)必需不停的向外廣播,讓中心設(shè)備知道它的存在。而掃描回復(fù)是可選的,中心設(shè)備可以向外設(shè)請求掃描回復(fù),這里包含一些設(shè)備額外的信息。

image

外圍設(shè)備會設(shè)定一個廣播間隔。每個廣播間隔中,它會重新發(fā)送自己的廣播數(shù)據(jù)。廣播間隔越長,越省電,同時也不太容易掃描到。

廣播的網(wǎng)絡(luò)拓撲結(jié)構(gòu)
? 外設(shè)通過廣播自己讓中心設(shè)備發(fā)現(xiàn)自己,并建立 GATT 連接,從而進行更多的數(shù)據(jù)交換。但有些情況是不需要連接的,只要外設(shè)廣播自己的數(shù)據(jù)即可。目的是讓外圍設(shè)備,把自己的信息發(fā)送給多個中心設(shè)備。因為基于 GATT 連接的方式的,只能是一個外設(shè)連接一個中心設(shè)備。

image
GATT(Generic Attribute Profile)簡介
? GATT配置文件是一個通用規(guī)范,用于在BLE鏈路上發(fā)送和接收被稱為“屬性”的數(shù)據(jù)塊。目前所有的BLE應(yīng)用都基于GATT。

BLE設(shè)備通過叫做 Service 和 Characteristic 的東西進行通信

? GATT使用了 ATT(Attribute Protocol)協(xié)議,ATT 協(xié)議把 Service, Characteristic對應(yīng)的數(shù)據(jù)保存在一個查詢表中,次查找表使用 16 bit ID 作為每一項的索引。

? GATT 連接是獨占的。也就是一個 BLE 外設(shè)同時只能被一個中心設(shè)備連接。一旦外設(shè)被連接,它就會馬上停止廣播,這樣它就對其他設(shè)備不可見了。當外設(shè)與中心設(shè)備斷開,外設(shè)又開始廣播,讓其他中心設(shè)備感知該外設(shè)的存在。而中心設(shè)備可同時與多個外設(shè)進行連接。

image
GATT 通信
中心設(shè)備和外設(shè)需要雙向通信的話,唯一的方式就是建立 GATT 連接。

? GATT 通信的雙方是 C/S 關(guān)系。外設(shè)作為 GATT 服務(wù)端(Server),它維持了 ATT 的查找表以及 service 和 characteristic 的定義。中心設(shè)備是 GATT 客戶端(Client),它向 外設(shè)(Server) 發(fā)起請求來獲取數(shù)據(jù)。

GATT 結(jié)構(gòu)
image
Profile:并不是實際存在于 BLE 外設(shè)上的,它只是一個被 Bluetooth SIG 或者外設(shè)設(shè)計者預(yù)先定義的 Service 的集合。例如心率Profile(Heart Rate Profile)就是結(jié)合了 Heart Rate Service 和 Device Information Service。

Service:包含一個或者多個 Characteristic。每個 Service 有一個 UUID 唯一標識。

Characteristic: 是最小的邏輯數(shù)據(jù)單元。一個Characteristic包括一個單一value變量和0-n個用來描述characteristic變量的Descriptor。與 Service 類似,每個 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一標識。

實際開發(fā)中,和 BLE 外設(shè)打交道,主要是通過 Characteristic??梢詮?Characteristic 讀取數(shù)據(jù),也可以往 Characteristic 寫數(shù)據(jù),從而實現(xiàn)雙向的通信。

UUID 有 16 bit 、32bit 和 128 bit 的。16 bit 的 UUID 是官方通過認證的,需要花錢購買。

Bluetooth_Base_UUID定義為 00000000-0000-1000-8000-00805F9B34FB

若16 bit UUID為xxxx,轉(zhuǎn)換為128 bit UUID為0000xxxx-0000-1000-8000-00805F9B34FB
若32 bit UUID為xxxxxxxx,轉(zhuǎn)換為128 bit UUID為xxxxxxxx-0000-1000-8000-00805F9B34FB
二、中心設(shè)備與外設(shè)通訊
簡單介紹BLE開發(fā)當中各種主要類和其作用:

BluetoothDeivce:藍牙設(shè)備,代表一個具體的藍牙外設(shè)。
BluetoothGatt:通用屬性協(xié)議,定義了BLE通訊的基本規(guī)則和操作
BluetoothGattCallback:GATT通信回調(diào)類,用于回調(diào)的各種狀態(tài)和結(jié)果。
BluetoothGattService:服務(wù),由零或多個特征組構(gòu)成。
BluetoothGattCharacteristic:特征,里面包含了一組或多組數(shù)據(jù),是GATT通信中的最小數(shù)據(jù)單元。
BluetoothGattDescriptor:特征描述符,對特征的額外描述,包括但不僅限于特征的單位,屬性等。
獲取藍牙設(shè)備對象
對掃描到的藍牙可以用集合形式進行緩存,也可只保存其mac地址,存儲到字符集合中,用于后續(xù)的連接。

根據(jù)mac地址獲取到BluetoothDeivce用于連接

BluetoothManager bluetoothmanager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothmanager.getAdapter();
//獲取藍牙設(shè)備對象進行連接
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddressStr)
藍牙gatt回調(diào)
實現(xiàn)BluetoothGattCallBack類,監(jiān)聽藍牙連接過程中各種回調(diào)的監(jiān)聽。藍牙Gatt回調(diào)方法中都不應(yīng)該進行耗時操作,需要將其方法內(nèi)進行的操作丟進另一個線程,盡快返回。

//定義子線程handle,用于在BluetoothGattCallback中回調(diào)方法中的操作拋到該線程工作。
private Handler mHandler;
//定義handler工作的子線程
private HandlerThread mHandlerThread;

初始化handler
mHandlerThread = new HandlerThread(“daqi”);
mHandlerThread.start();
//將handler綁定到子線程中
mHandler = new Handler(mHandlerThread.getLooper());

//定義藍牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//連接狀態(tài)回調(diào)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用于返回操作是否成功,會返回異常碼。
// newState 返回連接狀態(tài),如BluetoothProfile#STATE_DISCONNECTED、BluetoothProfile#STATE_CONNECTED

//操作成功的情況下if (status == BluetoothGatt.GATT_SUCCESS){//判斷是否連接碼if (newState == BluetoothProfile.STATE_CONNECTED) {}else if(newState == BluetoothProfile.STATE_DISCONNECTED){//判斷是否斷開連接碼}}else{//異常碼}}//服務(wù)發(fā)現(xiàn)回調(diào)@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);}//特征寫入回調(diào)@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);}//外設(shè)特征值改變回調(diào)@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);}//描述寫入回調(diào)@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);} }

連接設(shè)備
? 調(diào)用BluetoothDevice#connectGatt()進行ble連接,第二個參數(shù)默認選擇false,不自動連接。并定義BluetoothGatt變量,存儲BluetoothDevice#connectGatt()返回的對象。
image
//定義Gatt實現(xiàn)類
private BluetoothGatt mBluetoothGatt;

//創(chuàng)建Gatt回調(diào)
private BluetoothGattCallback mGattCallback = new daqiBluetoothGattCallback();
//連接設(shè)備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);
}
連接異常處理
? 藍牙連接時,不一定百分百連接成功。連接出錯時,會返回異常碼進行錯誤描述。對于大多數(shù)異常碼,可以通過重連來達到連接成功的目的。

錯誤代碼:

133 :連接超時或未找到設(shè)備。
8 : 設(shè)備超出范圍
22 :表示本地設(shè)備終止了連接
//定義重連次數(shù)
private int reConnectionNum = 0;
//最多重連次數(shù)
private int maxConnectionNum = 3;

public class daqiBluetoothGattCallback extends BluetoothGattCallback{

//連接狀態(tài)回調(diào) @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);// status 用于返回操作是否成功,會返回異常碼。//操作成功的情況下if (status == BluetoothGatt.GATT_SUCCESS){}else{//重連次數(shù)不大于最大重連次數(shù)if(reConnectionNum < maxConnectionNum){//重連次數(shù)自增reConnectionNum++//連接設(shè)備if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,false, mGattCallback, BluetoothDevice.TRANSPORT_LE);} else {mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);}}else{//斷開連接,返回連接失敗回調(diào)}} }//其他回調(diào)方法


發(fā)現(xiàn)服務(wù)
連接成功后,觸發(fā)BluetoothGattCallback#onConnectionStateChange()方法。

public class daqiBluetoothGattCallback extends BluetoothGattCallback{

//連接狀態(tài)回調(diào) @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);// status 用于返回操作是否成功,會返回異常碼。//操作成功的情況下if (status == BluetoothGatt.GATT_SUCCESS){//判斷是否連接碼if (newState == BluetoothProfile.STATE_CONNECTED) {//可延遲發(fā)現(xiàn)服務(wù),也可不延遲mHandler.post(() ->//發(fā)現(xiàn)服務(wù)mBluetoothGatt.discoverServices(););}else if(newState == BluetoothProfile.STATE_DISCONNECTED){//判斷是否斷開連接碼}} }//其他回調(diào)方法


當發(fā)現(xiàn)服務(wù)成功后,會觸發(fā)BluetoothGattCallback#onServicesDiscovered()回調(diào):

//定義需要進行通信的ServiceUUID
private UUID mServiceUUID = UUID.fromString(“0000xxxx-0000-1000-8000-00805f9b34fb”);

//定義藍牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{

//服務(wù)發(fā)現(xiàn)回調(diào) @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if (status == BluetoothGatt.GATT_SUCCESS) {mHandler.post(() ->//獲取指定uuid的serviceBluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);//獲取到特定的服務(wù)不為空if(gattService != null){}else{//獲取特定服務(wù)失敗});} }


發(fā)現(xiàn)服務(wù)失敗
? 發(fā)現(xiàn)服務(wù)時,會存在發(fā)現(xiàn)不了特定服務(wù)的情況。或者說,整個BluetoothGatt對象中的服務(wù)列表為空。
BluetoothGatt類中存在一個隱藏的方法refresh(),用于刷新Gatt的服務(wù)列表。當發(fā)現(xiàn)不了服務(wù)時,可以通過反射去調(diào)用該方法,再發(fā)現(xiàn)一遍服務(wù)。

讀取和修改特征值
//定義需要進行通信的ServiceUUID
private UUID mServiceUUID = UUID.fromString(“0000xxxx-0000-1000-8000-00805f9b34fb”);
//定義需要進行通信的CharacteristicUUID
private UUID mCharacteristicUUID = UUID.fromString(“0000yyyy-0000-1000-8000-00805f9b34fb”);

//定義藍牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{

//服務(wù)發(fā)現(xiàn)回調(diào) @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if (status == BluetoothGatt.GATT_SUCCESS) {mHandler.post(() ->//獲取指定uuid的serviceBluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);//獲取到特定的服務(wù)不為空if(gattService != null){//獲取指定uuid的CharacteristicBluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(mCharacteristicUUID);//獲取特定特征成功if(gattCharacteristic != null){//寫入你需要傳遞給外設(shè)的特征值(即傳遞給外設(shè)的信息)gattCharacteristic.setValue(bytes);//通過GATt實體類將,特征值寫入到外設(shè)中。mBluetoothGatt.writeCharacteristic(gattCharacteristic);//如果只是需要讀取外設(shè)的特征值://通過Gatt對象讀取特定特征(Characteristic)的特征值mBluetoothGatt.readCharacteristic(gattCharacteristic);}}else{//獲取特定服務(wù)失敗});} }


當成功讀取特征值時,會觸發(fā)BluetoothGattCallback#onCharacteristicRead()回調(diào)。

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取讀取到的特征值
characteristic.getValue()

}
當成功寫入特征值到外設(shè)時,會觸發(fā)BluetoothGattCallback#onCharacteristicWrite()回調(diào)。

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取寫入到外設(shè)的特征值
characteristic.getValue()

}
監(jiān)聽外設(shè)特征值改變
? 無論是對外設(shè)寫入新值,還是讀取外設(shè)特定Characteristic的值,其實都只是單方通信。如果需要雙向通信,可以在BluetoothGattCallback#onServicesDiscovered中對某個特征值設(shè)置監(jiān)聽(前提是該Characteristic具有NOTIFY屬性):

//設(shè)置訂閱notificationGattCharacteristic值改變的通知
mBluetoothGatt.setCharacteristicNotification(notificationGattCharacteristic, true);
//獲取其對應(yīng)的通知Descriptor
BluetoothGattDescriptor descriptor = notificationGattCharacteristic.getDescriptor(UUID.fromString(“00002902-0000-1000-8000-00805f9b34fb”));
if (descriptor != null){
//設(shè)置通知值
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
boolean descriptorResult = mBluetoothGatt.writeDescriptor(descriptor);
}
? 當寫入完特征值后,外設(shè)修改自己的特征值進行回復(fù)時,手機端會觸發(fā)BluetoothGattCallback#onCharacteristicChanged()方法,獲取到外設(shè)回復(fù)的值,從而實現(xiàn)雙向通信。

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取外設(shè)修改的特征值
String value = characteristic.getValue()
//對特征值進行解析


斷開連接
斷開連接的操作分為兩步:

mBluetoothGatt.disconnect();
mBluetoothGatt.close();
? 調(diào)用disconnect()后,會觸發(fā)手機會觸發(fā)BluetoothGattCallback#onConnectionStateChange()的回調(diào),回調(diào)斷開連接信息,newState = BluetoothProfile.STATE_DISCONNECTED。但調(diào)用完disconnect()緊接著馬上調(diào)用close(),會終止BluetoothGattCallback#onConnectionStateChange()的回調(diào)??梢钥辞闆r將兩個進行拆分調(diào)用,來實現(xiàn)斷開連接,但必須兩個方法都調(diào)用。

例如:
需要在外設(shè)修改特征值觸發(fā)BluetoothGattCallback#onCharacteristicChanged()時,斷開連接??梢韵仍贐luetoothGattCallback#onCharacteristicChanged()中調(diào)用disconnect(),并等調(diào)用BluetoothGattCallback#onConnectionStateChange()回調(diào),返回斷開連接信息后,再調(diào)用close()對Gatt資源進行關(guān)閉。

當和外設(shè)進行ble通信時,如出現(xiàn)任何意外情況,馬上調(diào)用斷開連接操作。

作者:大棋17
鏈接:https://www.jianshu.com/p/d273e46f47b1
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

android藍牙BLE(三) —— 廣播

大棋17
1
2019.05.22 16:20:54
字數(shù) 2,699
閱讀 9,561
? 在藍牙開發(fā)中,有些情況是不需要連接的,只要外設(shè)廣播自己的數(shù)據(jù)即可,例如蘋果的ibeacon。自Android 5.0更新藍牙API后,手機可以作為外設(shè)廣播數(shù)據(jù)。

廣播包有兩種:

廣播包(Advertising Data)
響應(yīng)包(Scan Response)
其中廣播包是每個外設(shè)都必須廣播的,而響應(yīng)包是可選的。每個廣播包的長度必須是31個字節(jié),如果不到31個字節(jié) ,則剩下的全用0填充 補全,這部分的數(shù)據(jù)是無效的
image
廣播數(shù)據(jù)單元
廣播包中包含若干個廣播數(shù)據(jù)單元,廣播數(shù)據(jù)單元也稱為 AD Structure。

廣播數(shù)據(jù)單元 = 長度值Length + AD type + AD Data。

長度值Length只占一個字節(jié),并且位于廣播數(shù)據(jù)單元的第一個字節(jié)。

概念的東西有些抽象,先看看下面的廣播報文:

image
? 0x代表這串字符串是十六進制的字符串。兩位十六進制數(shù)代表一個字節(jié)。因為兩個字符組成的十六進制字符串最大為FF,即255,而Java中byte類型的取值范圍是-128到127,剛好可以表示一個255的大小。所以兩個十六進制的字符串表示一個字節(jié)。

? 繼續(xù)查看報文內(nèi)容,開始讀取第一個廣播數(shù)據(jù)單元。讀取第一個字節(jié):0x07,轉(zhuǎn)換為十進制就是7,即表示后面的7個字節(jié)是這個廣播數(shù)據(jù)單元的數(shù)據(jù)內(nèi)容。超過這7個字節(jié)的數(shù)據(jù)內(nèi)容后,表示是一個新的廣播數(shù)據(jù)單元。

? 而第二個廣播數(shù)據(jù)單元,第一個字節(jié)的值是0x16,轉(zhuǎn)換為十進制就是22,表示后面22個字節(jié)為第二個廣播數(shù)據(jù)單元。

? 在廣播數(shù)據(jù)單元的數(shù)據(jù)部分中,第一個字節(jié)代表數(shù)據(jù)類型(AD type),決定數(shù)據(jù)部分表示的是什么數(shù)據(jù)。(即廣播數(shù)據(jù)單元第二個字節(jié)為AD type)

image
AD Type的類型如下:

Flags:TYPE = 0x01。用來標識設(shè)備LE物理連接。
bit 0: LE 有限發(fā)現(xiàn)模式
bit 1: LE 普通發(fā)現(xiàn)模式
bit 2: 不支持 BR/EDR
bit 3: 對 Same Device Capable(Controller) 同時支持 BLE 和 BR/EDR
bit 4: 對 Same Device Capable(Host) 同時支持 BLE 和 BR/EDR
bit 5…7: 預(yù)留
? 這bit 1~7分別代表著發(fā)送該廣播的藍牙芯片的物理連接狀態(tài)。當bit的值為1時,表示支持該功能。
例:

image
Service UUID。廣播數(shù)據(jù)中可以將設(shè)備支持的GATT Service的UUID廣播出來,來告知中心設(shè)備其支持的Service。對于不同bit的UUID,其對應(yīng)的類型也有不同:

非完整的16bit UUID: TYPE = 0x02;
完整的16bit UUID 列表: TYPE = 0x03;
非完整的32bit UUID 列表: TYPE = 0x04;
完整的32bit UUID 列表: TYPE = 0x05;
非完整的128bit UUID 列表: TYPE = 0x06;
完整的128bit UUID: TYPE = 0x07;
TX Power Level: TYPE = 0x0A,表示設(shè)備發(fā)送廣播包的信號強度。 數(shù)值范圍:±127 dBm。

設(shè)備名字,DATA 是名字的字符串,可以是設(shè)備的全名,也可以是設(shè)備名字的縮寫。

縮寫的設(shè)備名稱: TYPE = 0x08
完整的設(shè)備名稱: TYPE = 0x09
Service Data: Service 對應(yīng)的數(shù)據(jù)。

16 bit UUID Service: TYPE = 0x16, 前 2 字節(jié)是 UUID,后面是 Service 的數(shù)據(jù);
32 bit UUID Service: TYPE = 0x20, 前 4 字節(jié)是 UUID,后面是 Service 的數(shù)據(jù);
128 bit UUID Service: TYPE = 0x21, 前 16 字節(jié)是 UUID,后面是 Service 的數(shù)據(jù);
廠商自定義數(shù)據(jù): TYPE = 0xFF。廠商數(shù)據(jù)中,前兩個字節(jié)表示廠商ID,剩下的是廠商自定義的數(shù)據(jù)。

BLE廣播
藍牙廣播的數(shù)據(jù)格式大致講了一下,有助于下面的廣播操作的理解。

自定義UUID:
//UUID
public static UUID UUID_SERVICE = UUID.fromString(“0000fff7-0000-1000-8000-00805f9b34fb”);
開啟廣播一般需要3~4對象:廣播設(shè)置(AdvertiseSettings)、廣播包(AdvertiseData)、掃描包(可選)、廣播回調(diào)(AdvertiseCallback)。

廣播設(shè)置
先看看廣播設(shè)置(AdvertiseSettings)如何定義:

//初始化廣播設(shè)置
mAdvertiseSettings = new AdvertiseSettings.Builder()
//設(shè)置廣播模式,以控制廣播的功率和延遲。
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
//發(fā)射功率級別
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
//不得超過180000毫秒。值為0將禁用時間限制。
.setTimeout(3000)
//設(shè)置是否可以連接
.setConnectable(false)
.build();
(1)、通過AdvertiseSettings.Builder#setAdvertiseMode() 設(shè)置廣播模式。其中有3種模式:

在均衡電源模式下執(zhí)行藍牙LE廣播:AdvertiseSettings#ADVERTISE_MODE_BALANCED
在低延遲,高功率模式下執(zhí)行藍牙LE廣播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY
在低功耗模式下執(zhí)行藍牙LE廣播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER
(2)、通過AdvertiseSettings.Builder#setAdvertiseMode() 設(shè)置廣播發(fā)射功率。共有4種功率模式:

使用高TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH
使用低TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW
使用中等TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM
使用最低傳輸(TX)功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW
(3)、通過AdvertiseSettings.Builder#setTimeout()設(shè)置持續(xù)廣播的時間,單位為毫秒。最多180000毫秒。當值為0則無時間限制,持續(xù)廣播,除非調(diào)用BluetoothLeAdvertiser#stopAdvertising()停止廣播。

(4)、通過AdvertiseSettings.Builder#setConnectable()設(shè)置該廣播是否可以連接的。

廣播包
之前說過,外設(shè)必須廣播廣播包,掃描包是可選。但添加掃描包也意味著廣播更多得數(shù)據(jù),即可廣播62個字節(jié)。

//初始化廣播包
mAdvertiseData = new AdvertiseData.Builder()
//設(shè)置廣播設(shè)備名稱
.setIncludeDeviceName(true)
//設(shè)置發(fā)射功率級別
.setIncludeDeviceName(true)
.build();

//初始化掃描響應(yīng)包
mScanResponseData = new AdvertiseData.Builder()
//隱藏廣播設(shè)備名稱
.setIncludeDeviceName(false)
//隱藏發(fā)射功率級別
.setIncludeDeviceName(false)
//設(shè)置廣播的服務(wù)UUID
.addServiceUUID(new ParcelUUID(UUID_SERVICE))
//設(shè)置廠商數(shù)據(jù)
.addManufacturerData(0x11,hexStrToByte(mData))
.build();
可見無論是廣播包還是掃描包,其廣播的內(nèi)容都是用AdvertiseData類封裝的。

(1)、AdvertiseData.Builder#setIncludeDeviceName()方法,可以設(shè)置廣播包中是否包含藍牙的名稱。

(2)、AdvertiseData.Builder#setIncludeTxPowerLevel()方法,可以設(shè)置廣播包中是否包含藍牙的發(fā)射功率。

(3)、AdvertiseData.Builder#addServiceUUID(ParcelUUID)方法,可以設(shè)置特定的UUID在廣播包中。

(4)、AdvertiseData.Builder#addServiceData(ParcelUUID,byte[])方法,可以設(shè)置特定的UUID和其數(shù)據(jù)在廣播包中。

(5)、AdvertiseData.Builder#addManufacturerData(int,byte[])方法,可以設(shè)置特定廠商Id和其數(shù)據(jù)在廣播包中。

? 從AdvertiseData.Builder的設(shè)置中可以看出,如果一個外設(shè)需要在不連接的情況下對外廣播數(shù)據(jù),其數(shù)據(jù)可以存儲在UUID對應(yīng)的數(shù)據(jù)中,也可以存儲在廠商數(shù)據(jù)中。但由于廠商ID是需要由Bluetooth SIG進行分配的,廠商間一般都將數(shù)據(jù)設(shè)置在廠商數(shù)據(jù)。

廣播名稱
另外可以通過BluetoothAdapter#setName()設(shè)置廣播的名稱

//獲取藍牙設(shè)配器
BluetoothManager bluetoothManager = (BluetoothManager)
getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//設(shè)置設(shè)備藍牙名稱
mBluetoothAdapter.setName(“daqi”);
發(fā)送BLE廣播
先看一個例子,我們分別在廣播包和掃描包中設(shè)置AdvertiseData.Builder的每一種廣播報文參數(shù),得到一下報文內(nèi)容:
image
(1)、Type = 0x01 表示設(shè)備LE物理連接。

(2)、Type = 0x09 表示設(shè)備的全名

(3)、Type = 0x03 表示完整的16bit UUID。其值為0xFFF7。

(4)、Type = 0xFF 表示廠商數(shù)據(jù)。前兩個字節(jié)表示廠商ID,即廠商ID為0x11。后面的為廠商數(shù)據(jù),具體由用戶自行定義。

(5)、Type = 0x16 表示16 bit UUID的數(shù)據(jù),所以前兩個字節(jié)為UUID,即UUID為0xF117,后續(xù)為UUID對應(yīng)的數(shù)據(jù),具體由用戶自行定義。

最后繼承AdvertiseCallback自定義廣播回調(diào)。

private class daqiAdvertiseCallback extends AdvertiseCallback {
//開啟廣播成功回調(diào)
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect){
super.onStartSuccess(settingsInEffect);
Log.d(“daqi”,“開啟服務(wù)成功”);
}

//無法啟動廣播回調(diào)。 @Override public void onStartFailure(int errorCode) {super.onStartFailure(errorCode);Log.d("daqi","開啟服務(wù)失敗,失敗碼 = " + errorCode); }

}
初始化完畢上面的對象后,就可以進行廣播:

//獲取BLE廣播的操作對象。
//如果藍牙關(guān)閉或此設(shè)備不支持藍牙LE廣播,則返回null。
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
//mBluetoothLeAdvertiser不為空,且藍牙已開打
if(mBluetoothAdapter.isEnabled()){
if (mBluetoothLeAdvertiser != null){
//開啟廣播
mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,
mAdvertiseData, mScanResponseData, mAdvertiseCallback);
}else {
Log.d(“daqi”,“該手機不支持ble廣播”);
}
}else{
Log.d(“daqi”,“手機藍牙未開啟”);
}
? 廣播主要是通過BluetoothLeAdvertiser#startAdvertising()方法實現(xiàn),但在之前需要先獲取BluetoothLeAdvertiser對象。

BluetoothLeAdvertiser對象存在兩個情況獲取為Null:

手機藍牙模塊不支持BLE廣播
藍牙未開啟
所以在調(diào)用BluetoothAdapter#getBluetoothLeAdvertiser()前,需要先調(diào)用判斷藍牙已開啟,并判斷在BluetoothAdapter中獲取的BluetoothLeAdvertiser是否為空(測試過某些華為手機mBluetoothAdapter.isMultipleAdvertisementSupported()為 false, 但是能發(fā)送ble廣播)。

? 與廣播成對出現(xiàn)就是BluetoothLeAdvertiser.stopAdvertising()停止廣播了,傳入開啟廣播時傳遞的廣播回調(diào)對象,即可關(guān)閉廣播:

mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)
啟動GATT Service 和 Characteristic
? 雖然通過廣播告知外邊自身擁有這些Service,但手機自身并沒有初始化Gattd的Service。導(dǎo)致外部的中心設(shè)備連接手機后,并不能找到對應(yīng)的GATT Service 和 獲取對應(yīng)的數(shù)據(jù)。

創(chuàng)建Gatt Service
Service類型有兩個級別:

BluetoothGattService#SERVICE_TYPE_PRIMARY 主服務(wù)
BluetoothGattService#SERVICE_TYPE_SECONDARY次要服務(wù)(存在于主服務(wù)中的服務(wù))
創(chuàng)建BluetoothGattService時,傳入兩個參數(shù):UUID和Service類型:

BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
創(chuàng)建Gatt Characteristic
? 我們都知道Gatt中,Service的下一級是Characteristic,Characteristic是最小的通信單元,通過對Characteristic進行讀寫操作來進行通信。

//初始化特征值
mGattCharacteristic = new BluetoothGattCharacteristic(UUID_CHARACTERISTIC,
BluetoothGattCharacteristic.PROPERTY_WRITE|
BluetoothGattCharacteristic.PROPERTY_NOTIFY|
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE|
BluetoothGattCharacteristic.PERMISSION_READ);
創(chuàng)建BluetoothGattCharacteristic時,傳入三個參數(shù):UUID、特征屬性 和 權(quán)限屬性。

? 特征屬性表示該BluetoothGattCharacteristic擁有什么功能,即能對BluetoothGattCharacteristic進行什么操作。其中主要有3種:

BluetoothGattCharacteristic#PROPERTY_WRITE 表示特征支持寫
BluetoothGattCharacteristic#PROPERTY_READ 表示特征支持讀
BluetoothGattCharacteristic#PROPERTY_NOTIFY 表示特征支持通知
權(quán)限屬性用于配置該特征值所具有的功能。主要兩種:

BluetoothGattCharacteristic#PERMISSION_WRITE 特征寫權(quán)限
BluetoothGattCharacteristic#PERMISSION_READ 特征讀權(quán)限
注意事項
當特征值只有讀權(quán)限時,調(diào)用BluetoothGatt#writeCharacteristic()對特征值進行修改時,將返回false,無法寫入。并不會觸發(fā)BluetoothGattCallback#onCharacteristicWrite()回調(diào)。
當特征值只有寫權(quán)限時,調(diào)用BluetoothGatt#readCharacteristic()對特征值進行讀取時,將返回false,無法寫入。并不會觸發(fā)BluetoothGattCallback#onCharacteristicRead()回調(diào)。
創(chuàng)建Gatt Descriptor
Characteristic下還有Descriptor,初始化BluetoothGattDescriptor時傳入:Descriptor UUID 和 權(quán)限屬性

//初始化描述
mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattDescriptor.PERMISSION_WRITE);
添加 Characteristic 和 Descriptor
為Service添加Characteristic,為Characteristic添加Descriptor:

//Service添加特征值
mGattService.addCharacteristic(mGattCharacteristic);
mGattService.addCharacteristic(mGattReadCharacteristic);
//特征值添加描述
mGattCharacteristic.addDescriptor(mGattDescriptor);
? 通過藍牙管理器mBluetoothManager獲取Gatt Server,用來添加Gatt Service。添加完Gatt Service后,外部中心設(shè)備連接手機時,將能獲取到對應(yīng)的GATT Service 和 獲取對應(yīng)的數(shù)據(jù)

//初始化GattServer回調(diào)
mBluetoothGattServerCallback = new daqiBluetoothGattServerCallback();

if (mBluetoothManager != null)
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
boolean result = mBluetoothGattServer.addService(mGattService);
if (result){
Toast.makeText(daqiActivity.this,“添加服務(wù)成功”,Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(daqiActivity.this,“添加服務(wù)失敗”,Toast.LENGTH_SHORT).show();
}
? 定義Gatt Server回調(diào)。當中心設(shè)備連接該手機外設(shè)、修改特征值、讀取特征值等情況時,會得到相應(yīng)情況的回調(diào)。

private class daqiBluetoothGattServerCallback extends BluetoothGattServerCallback{

//設(shè)備連接/斷開連接回調(diào) @Override public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {super.onConnectionStateChange(device, status, newState); }//添加本地服務(wù)回調(diào) @Override public void onServiceAdded(int status, BluetoothGattService service) {super.onServiceAdded(status, service); }//特征值讀取回調(diào) @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {super.onCharacteristicReadRequest(device, requestId, offset, characteristic); }//特征值寫入回調(diào) @Override public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); }//描述讀取回調(diào) @Override public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {super.onDescriptorReadRequest(device, requestId, offset, descriptor); }//描述寫入回調(diào) @Override public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); }

}
最后開啟廣播后,用nRF連接后看到的特征值信息如下圖所示:(加多了一個只能都的特征值)

前序
????????android ble系列將以本章結(jié)尾,前三章都是自己個人一遍一遍翻閱官網(wǎng)和博客,自己動手實踐的歸納總結(jié),最后以demo的形式展示和進一步鞏固掌握的ble知識。該demo仿android版 nRF進行編寫,功能簡單但也相對齊全,主要涉及:掃描、連接通信、廣播、Gatt Service和藍牙報文解讀。

該demo涉及到的知識全在前三章進行歸納描述,本章不再敘述。

一、掃描
????????掃描附近的藍牙設(shè)備,點擊展示其廣播附帶的基本信息。

image
????????點擊RAW,查看完整的廣播報文和報文詳情。

image
二、廣播
????????對廣播以及連接后進行的操作進行打印,具體的廣播內(nèi)容可以在代碼進行調(diào)整。當然也可以使用前面的掃描對廣播的內(nèi)容進行查看!

想搞清楚廣播的通信最好也學(xué)學(xué)藍牙報文,那就很好掌握模仿ibeacon發(fā)送beacon數(shù)據(jù)。

image
三、連接
????????對進行外設(shè)進行連接。連接成功后,對外設(shè)的特征和描述進行讀寫操作來進行ble通信。

image
四、最后
????????最后,可以給兩臺手機安裝該demo,一臺進行ble廣播,一臺對廣播的手機進行連接,并進行讀取操作。

源碼:https://pan.baidu.com/s/167I3wCDbVHTCtakKmQ59Dg 提取碼:5a0a

作者:大棋17
鏈接:https://www.jianshu.com/p/a8adcee1f02e
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

總結(jié)

以上是生活随笔為你收集整理的android蓝牙BLE 有源码 有视频的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。