nRF Connect 官方有開源BLE基礎框架,具體查看 這里
前序
? Google
在android 4.3(API Level 18)
的android
版本中引入了低功耗藍牙BLE核心API。低功耗藍牙BLE
也就是我們經常說的藍牙4.0, 該技術擁有極低的運行和待機功耗,使用一粒紐扣電池甚至可連續工作數年之久。先不講藍牙協議與藍牙模塊一些類的作用與之間的關系,本章僅僅記錄android Ble
開發中的掃描模塊及其一些細節。
一、聲明藍牙權限和定位權限
<!--藍牙權限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--讓應用啟動設備發現或操縱藍牙設置-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- LE Beacons位置相關權限-->
<!-- 如果設配Android9及更低版本,可以申請 ACCESS_COARSE_LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--ble模塊 設置為true表示只有支持ble的手機才能安裝-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
? 由于藍牙掃描需要用到模糊定位權限( Android10
后需要精準定位權限 ),所以android6.0
之后,除了在 AndroidManifest.xml
中 申明權限之外,還需要動態申請定位權限,才可進行藍牙掃描,否則不會掃描到任何Ble設備。
可依據
PackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
獲知該手機是否支持BLE
二、中心設備與外圍設備
Ble
開發中,存在著兩個角色:中心設備角色和外圍設備角色。粗略了解下:
- 外圍設備:一般指非常小或者低功耗設備,更強大的中心設備可以連接外圍設備為中心設備提供數據。外設會不停的向外廣播,讓中心設備知道它的存在。 例如小米手環。
- 中心設備:可以掃描并連接多個外圍設備,從外設中獲取信息。
? 外圍設備會設定一個廣播間隔,每個廣播間隔中,都會發送自己的廣播數據。廣播間隔越長,越省電。一個沒有被連接的Ble
外設會不斷發送廣播數據,這時可以被多個中心設備發現。一旦外設被連接,則會馬上停止廣播。
? android 4.3
時引入的Ble
核心Api
只支持android手機作為中心設備角色,當android 5.0
更新Api
后,android手機支持充當作為外設角色和中心角色。即 android 5.0
引入了外設角色的Api
,同時也更新了部分中心角色的Api
。比如:中心角色中,更新了藍牙掃描的Api
。
三、打開藍牙
//初始化ble設配器
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();
//判斷藍牙是否開啟,如果關閉則請求打開藍牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//方式一:請求打開藍牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
//方式二:半靜默打開藍牙
//低版本android會靜默打開藍牙,高版本android會請求打開藍牙
//mBluetoothAdapter.enable();
}
mBluetoothAdapter.isEnabled()
判斷當前藍牙是否打開,如果藍牙處于打開狀態返回true。
同時可以在activity
層通過廣播監聽藍牙的關閉與開啟,進行自己的邏輯處理:
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//獲取藍牙廣播 本地藍牙適配器的狀態改變時觸發
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//獲取藍牙廣播中的藍牙新狀態
int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
//獲取藍牙廣播中的藍牙舊狀態
int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueNewState) {
//正在打開藍牙
case BluetoothAdapter.STATE_TURNING_ON:
break;
//藍牙已打開
case BluetoothAdapter.STATE_ON:
break;
//正在關閉藍牙
case BluetoothAdapter.STATE_TURNING_OFF:
break;
//藍牙已關閉
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<ScanFilter>, ScanSettings, ScanCallback)。
//獲取 5.0 的掃描類實例
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
//開始掃描
//可設置過濾條件,在第一個參數傳入,但一般不設置過濾。
mBLEScanner.startScan(null,mScanSettings,mScanCallback);
//停止掃描
mBLEScanner.stopScan(mScanCallback);
藍牙掃描示例
//如果沒打開藍牙,不進行掃描操作,或請求打開藍牙。
if(!mBluetoothAdapter.isEnabled()) {
return;
}
//處于未掃描的狀態
if (!mScanning){
//android 5.0后
if(android.os.Build.VERSION.SDK_INT >= 21) {
//標記當前的為掃描狀態
mScanning = true;
//獲取5.0新添的掃描類
if (mBLEScanner == null){
//mBLEScanner是5.0新添加的掃描類,通過BluetoothAdapter實例獲取。
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
//開始掃描
//mScanSettings是ScanSettings實例,mScanCallback是ScanCallback實例,后面進行講解。
mBLEScanner.startScan(null,mScanSettings,mScanCallback);
} else {
//標記當前的為掃描狀態
mScanning = true;
//5.0以下 開始掃描
//mLeScanCallback是BluetoothAdapter.LeScanCallback實例
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
//設置結束掃描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//停止掃描設備
if(android.os.Build.VERSION.SDK_INT >= 21) {
//標記當前的為未掃描狀態
mScanning = false;
mBLEScanner.stopScan(mScanCallback);
} else {
//標記當前的為未掃描狀態
mScanning = false;
//5.0以下 停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
},SCAN_TIME);
}
掃描代碼如上述所示,當掃描到所需要的設備的時候,就要手動馬上停止藍牙掃描,因為藍牙掃描是耗電操作。
注意事項
-
android 6.0
以上需要獲取到定位權限。否則會報如下運行時異常:
-
android 7.0
后不能在30秒內掃描+停止超過5次。(官網沒特意說明,可自行測試,設置掃描時長為3秒,連續掃描10次,穩定復現5次后不能掃描到任何設備。android 藍牙模塊會打印當前應用掃描太頻繁的log日志,并在android 5.0
的ScanCallback
回調中觸發onScanFailed(int)
,返回錯誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
,表示app無法注冊,無法開始掃描)。
五、掃描回調
android 4.3 掃描回調:LeScanCallback
android 4.3
的掃描回調接口BluetoothAdapter.LeScanCallback
:
回調接口中只有一個回調函數
onLeScan
,掃描到的設備會通過該方法返回。參數:
-
BluetoothDevice
掃描到的設備實例,可從實例中獲取到相應的信息。如:名稱,mac地址 -
rssi
可理解成設備的信號值。該數值是一個負數,越大則信號越強。 -
scanRecord
遠程設備提供的廣播數據的內容。
//5.0以下
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//對掃描到的設備進行操作。如:獲取設備信息。
}
};
獲取BluetoothDevice
中的信息
可以從中獲取到設備的mac地址,設備名稱,綁定狀態和設備類型等信息,并作相應的保存。
mac可用于再創建
BluetoothDevice
對象進行gatt
連接。-
綁定狀態:
-
BOND_NONE
:數值 10
表示遠程設備未綁定,沒有共享鏈接密鑰,因此通信(如果允許的話)將是未經身份驗證和未加密的。(掃描到未綁定的小米手環) -
BOND_BONDING
:數值 11 表示正在與遠程設備進行綁定; -
BOND_BONDED
:數值 12 表示遠程設備已綁定,遠程設備本地存儲共享連接的密鑰,因此可以對通信進行身份驗證和加密。(掃描到已綁定的小米手環)
-
設備類型:一般是2,表示LE設備
android 5.0 掃描回調:ScanCallback
mScanCallback = new ScanCallback() {
//當一個藍牙ble廣播被發現時回調
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//掃描類型有開始掃描時傳入的ScanSettings相關
//對掃描到的設備進行操作。如:獲取設備信息。
}
//批量返回掃描結果
//@param results 以前掃描到的掃描結果列表。
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
//當掃描不能開啟時回調
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
//掃描太頻繁會返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描。
}
};
ScanCallback
掃描回調存在三個回調函數:
-
onScanResult(int,ScanResult)
:類似于BluetoothAdapter.LeScanCallback
中的onLeScan()
,可在ScanResult
實例中獲取到BluetoothDevice
藍牙設備對象,rssi
信號值等信息。一般都是在該函數中回調獲取掃描到藍牙設備和信號值,在本函數中執行和onLeScan()
中相同的邏輯處理即可。 -
onBatchScanResults(List)
批量返回掃描結果。 -
onScanFailed(int)
掃描失敗返回錯誤碼。
注意事項
- 回調函數中盡量不要做耗時操作!
- 一般藍牙設備對象都是通過
onScanResult(int,ScanResult)
返回,而不會在onBatchScanResults(List)
方法中返回,除非手機支持批量掃描模式并且開啟了批量掃描模式。批處理的開啟請查看ScanSettings
。
六、掃描設置
? ScanSettings
實例對象是通過ScanSettings.Builder
構建的。通過Builder
對象為ScanSettings
實例設置掃描模式、回調類型、匹配模式等參數,用于配置android 5.0
的掃描參數。
//創建ScanSettings的build對象用于設置參數
ScanSettings.Builder builder = new ScanSettings.Builder()
//設置高功耗模式
.setScanMode(SCAN_MODE_LOW_LATENCY);
//android 6.0添加設置回調類型、匹配模式等
if(android.os.Build.VERSION.SDK_INT >= 23) {
//定義回調類型
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
//設置藍牙LE掃描濾波器硬件匹配的匹配模式
builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
}
//芯片組支持批處理芯片上的掃描
if (bluetoothadapter.isOffloadedScanBatchingSupported()) {
//設置藍牙LE掃描的報告延遲的時間(以毫秒為單位)
//設置為0以立即通知結果
builder.setReportDelay(0L);
}
builder.build();
配置描述:
-
setScanMode()
設置掃描模式。可選擇模式主要三種( 從上到下,會越來越耗電,但掃描間隔越來越短,即掃描速度會越來越快。):-
ScanSettings.SCAN_MODE_LOW_POWER
低功耗模式(默認掃描模式,如果掃描應用程序不在前臺,則強制使用此模式。) -
ScanSettings.SCAN_MODE_BALANCED
平衡模式 -
ScanSettings.SCAN_MODE_LOW_LATENCY
高功耗模式(建議僅在應用程序在前臺運行時才使用此模式。)
-
-
setCallbackType()
設置回調類型。可選擇模式主要三種:-
ScanSettings.CALLBACK_TYPE_ALL_MATCHES
數值: 1。尋找符合過濾條件的藍牙廣播,如果沒有設置過濾條件,則返回全部廣播包
-
ScanSettings.CALLBACK_TYPE_FIRST_MATCH
數值: 2僅針對與篩選條件匹配的第一個廣播包觸發結果回調。
-
ScanSettings.CALLBACK_TYPE_MATCH_LOST
數值: 4回調類型一般設置
ScanSettings.CALLBACK_TYPE_ALL_MATCHES
,有過濾條件時過濾,返回符合過濾條件的藍牙廣播。無過濾條件時,返回全部藍牙廣播。
-
-
setMatchMode()
設置藍牙LE掃描濾波器硬件匹配的匹配模式-
ScanSettings.MATCH_MODE_STICKY
粘性模式,在通過硬件報告之前,需要更高的信號強度和目擊閾值 -
MATCH_MODE_AGGRESSIVE
激進模式,即使信號強度微弱且持續時間內瞄準/匹配的次數很少,hw也會更快地確定匹配。
-
-
Bluetoothadapter.isOffloadedScanBatchingSupported()
判斷當前手機藍牙芯片是否支持批處理掃描。- 如果支持掃描則使用批處理掃描,可通過
ScanSettings.Builder
對象調用setReportDelay(Long)
方法來設置藍牙LE掃描的報告延遲的時間(以毫秒為單位)來啟動批處理掃描模式。
- 如果支持掃描則使用批處理掃描,可通過
-
ScanSettings.Builder.setReportDelay(Long)
;- 當設備藍牙芯片支持批處理掃描時,用來設置藍牙LE掃描的報告延遲的時間(以毫秒為單位)。
- 該參數默認為 0,如果不修改它的值,則默認只會在
onScanResult(int,ScanResult)
中返回掃描到的藍牙設備,不會觸發不會觸發onBatchScanResults(List)
方法。(onScanResult(int,ScanResult)
和onBatchScanResults(List)
是互斥的。 ) - 設置為0以立即通知結果,不開啟批處理掃描模式。即
ScanCallback
藍牙回調中,不會觸發onBatchScanResults(List)
方法,但會觸發onScanResult(int,ScanResult)
方法,返回掃描到的藍牙設備。 - 當設置的時間大于0L時,則會開啟批處理掃描模式。即觸發
onBatchScanResults(List)
方法,返回掃描到的藍牙設備列表。但不會觸發onScanResult(int,ScanResult)
方法。
提示
Android 10 進行BLE掃描時需要打開GPS。
具體源碼可參考Ble實戰BleScanHelper.kt
Android 8
更新了一個掃描API,系統層為你提供后臺持續掃描的能力。(即便APP已被殺死,掃描仍會繼續。但如果用戶重啟或關閉藍牙后,該掃描停止)。 具體可查看官網: BluetoothLeScanner#startScan。
public int startScan (List<ScanFilter> filters,
ScanSettings settings,
PendingIntent callbackIntent)