對於低功耗藍牙而言,設備的發現的一種重要的手段就是經過設備的掃描,固然這不是惟一的手段。關於設備的掃描咱們在Bluetooth LE實戰篇中的低功耗藍牙之設備掃描中闡述過,因此在這裏就再也不進行過多的解釋。
android
言歸正傳,咱們來分析BluetoothLeScanner的源碼。咱們先來看看這個類的註釋:bash
這段文字告訴咱們:app
該類提供了一些低功耗藍牙設備掃描相關的方法。若是須要特定類型的掃描方式請使用「ScanFilter」。同時告訴咱們能夠經過「BluetoothAdapter#getBuletoothLeScanner()」方法獲取該類的實例。還有就是提示你們使用以前須要在在註冊清單裏添加「android.Manifest.permission#BLUETOOTH_ADMIN」權限。ide
首先咱們看一下,設備掃描相關方法:oop
前兩個方法傳遞的參數有所不一樣,先說一下它們的共同點:源碼分析
1.都是經過「ScanCallback」將掃描結果回調回來;post
2.開啓掃描程序必須須要定位權限:ACCESS_COARSE_LOCATION 或者 ACCESS_FINE_LOCATIONui
須要注意的是,當沒有過濾條件的時候,當屏幕熄滅,掃描就會終止。當屏幕從新喚醒時掃描會從新開始。this
關於該方法相關的使用和參數的詳解,請閱讀:低功耗藍牙之設備掃描中的內容。spa
還有一個比較特殊的方法:
startScan(@NullableList<ScanFilter>filters,@NullableScanSettingssettings,
@NonNullPendingIntentcallbackIntent)複製代碼
它是經過PendingIntent進行信息的傳遞。固然掃描的結果是經過Intent進行傳遞,那樣的話你就能夠在廣播接受者或者Activity中進行處理。
咱們繼續閱讀三個方法內部調用的方法:
startScan(List<ScanFilter>filters,ScanSettingssettings,
finalWorkSourceworkSource,finalScanCallbackcallback,
finalPendingIntentcallbackIntent,
List<List<ResultStorageDescriptor>>resultStorages)複製代碼
咱們分析一下它的邏輯:
private int startScan(List<ScanFilter> filters, ScanSettings settings,
final WorkSource workSource, final ScanCallback callback,
final PendingIntent callbackIntent,
List<List<ResultStorageDescriptor>> resultStorages) {
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
//承接了上面的三種掃描方法
if (callback == null && callbackIntent == null) {
throw new IllegalArgumentException("callback is null");
}
if (settings == null) {
throw new IllegalArgumentException("settings is null");
}
synchronized (mLeScanClients) {
//重複開啓同一個callback的掃描就會提示「掃描已經開啓」
if (callback != null && mLeScanClients.containsKey(callback)) {
return postCallbackErrorOrReturn(callback,
ScanCallback.SCAN_FAILED_ALREADY_STARTED);
}
... ...
//檢查ScanSetting是否容許進行掃描
if (!isSettingsConfigAllowedForScan(settings)) {
return postCallbackErrorOrReturn(callback,
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
}
//檢查硬件是否支持
if (!isHardwareResourcesAvailableForScan(settings)) {
return postCallbackErrorOrReturn(callback,
ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
}
//onlost/onfound,須要非空的過濾條件
if (!isSettingsAndFilterComboAllowed(settings, filters)) {
return postCallbackErrorOrReturn(callback,
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
}
//這些條件都符合纔會進行設備的掃描
if (callback != null) {
BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
settings, workSource, callback, resultStorages);
wrapper.startRegistration();
} else {
try {
gatt.startScanForIntent(callbackIntent, settings, filters,
ActivityThread.currentOpPackageName());
} catch (RemoteException e) {
return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
}
}
}
return ScanCallback.NO_ERROR;
}
複製代碼
在這個方法裏當使用前兩種方法時,使用BleScanCallbackWarpper進行開始掃描邏輯的處理,使用PendingIntent則使用IBluetoothGatt的startScanForIntent進行掃描開始的邏輯。
1.使用Scancallback做爲參數的中止掃描的方法,使用BleScanCallbackWrapper的stopLeScan進行掃描的中止。
2.使用PendingIntent做爲參數的中止掃描方法,使用的是IBluetoothGatt的stopScanForIntent進行掃描的中止。
使用該方法會屬性藍牙控制器,而後經過ScanCallback回調批量掃描結果。固然也是經過BleScanCallbackWrapper操做的。
咱們先看一下它的主要成員變量和構造器:
咱們看到ScanCallback應該用於掃描結果和掃描狀態的回調;ScanFilter的集合和ScanSetting應該用於掃描相關的邏輯;IBluetoothGatt應該就是藍牙相關的具體邏輯的處理,可是咱們找不到。有點遺憾~
還有一個更重要的變量「mScannerId」它是用來表示掃描成功與否的狀態變量,由於沒法知曉IBluetoothGatt的具體邏輯,因此該變量的值就是尤其重要的。總結起來:當mScannerId =0,是掃描過程沒有註冊;當mScannerId =-1,是掃描中止了或者註冊失敗;當mScannerId =-2,是由於掃描太頻繁致使註冊失敗;當mScannerId >0,已經註冊而且掃描成功了。
1.掃描註冊
咱們在上面提到了掃描方法中的關鍵就是該類中的startRegistretion()方法,咱們梳理一下邏輯:
public void startRegistration() {
synchronized (this) {
// Scan stopped.
if (mScannerId == -1 || mScannerId == -2) return;
try {
//當非異常狀況下,進行註冊,同時註冊等待時間是2秒;
mBluetoothGatt.registerScanner(this, mWorkSource);
wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "application registeration exception", e);
postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
}
//當註冊成功後,存入回調集合;
if (mScannerId > 0) {
mLeScanClients.put(mScanCallback, this);
} else {
// Registration timed out or got exception, reset RscannerId to -1 so no
// subsequent operations can proceed.
//當註冊超時或者發生異常,會把狀態變量至成-1,掃描就不會繼續進行了;
if (mScannerId == 0) mScannerId = -1;
// If scanning too frequently, don't report anything to the app. //當狀態變量爲-2時,也就是掃描太過頻繁,會直接返回,可是並不將狀態反饋至App。 if (mScannerId == -2) return; //注意:這些狀況只是返回註冊失敗,並不返回具體緣由; postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); } } }複製代碼
因此說有時候在應用端並不知道爲何會致使掃描很久可是並無返回任何異常狀態。這是其餘文章須要討論的問題~
2.中止掃描
在前面咱們分析了中止掃描中止掃描的方法,最終調用的是該類的stopLeScan()方法。
public void stopLeScan() {
synchronized (this) {
//不是正常掃描的狀況
if (mScannerId <= 0) {
Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
return;
}
try {
mBluetoothGatt.stopScan(mScannerId);
mBluetoothGatt.unregisterScanner(mScannerId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to stop scan and unregister", e);
}
mScannerId = -1;
}
}複製代碼
3.mScannerId是怎麼賦值的呢?
咱們看一下onScannerRegistered()方法:
@Override
public void onScannerRegistered(int status, int scannerId) {
Log.d(TAG, "onScannerRegistered() - status=" + status
+ " scannerId=" + scannerId + " mScannerId=" + mScannerId);
synchronized (this) {
if (status == BluetoothGatt.GATT_SUCCESS) {
try {
if (mScannerId == -1) {
// Registration succeeds after timeout, unregister client.
//註冊超出2秒,解除註冊
mBluetoothGatt.unregisterClient(scannerId);
} else {
mScannerId = scannerId;
//正式開始掃描設備
mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
mResultStorages,
ActivityThread.currentOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "fail to start le scan: " + e);
mScannerId = -1;
}
} else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) {
// applicaiton was scanning too frequently
//掃描太頻繁:Android7.0以上版本規定30秒內重複調用start/stop方法5次
mScannerId = -2;
} else {
// registration failed
mScannerId = -1;
}
notifyAll();
}
}複製代碼
4.掃描結果的回調:
咱們看一下怎麼將掃描數據返回給ScanCallback的:
@Override
public void onScanResult(final ScanResult scanResult) {
if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
// Check null in case the scan has been stopped
synchronized (this) {
if (mScannerId <= 0) return;
}
//經過handler將數據返回值頁面:主線程哦~
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
}
});
}複製代碼
當掃描程序正常啓動時,經過Handler將掃描結果返回給頁面。onBatchScanResults()也是類似的邏輯,因此就再也不解釋了。
到這裏就把BluetoothLeScanner的源碼分析完了,好累的一天~