对于低功耗蓝牙而言,设备的发现的一种重要的手段就是通过设备的扫描,当然这不是唯一的手段。关于设备的扫描我们在Bluetooth LE实战篇中的低功耗蓝牙之设备扫描中阐述过,所以在这里就不再进行过多的解释。
言归正传,我们来分析BluetoothLeScanner的源码。我们先来看看这个类的注释:
这段文字告诉我们:
该类提供了一些低功耗蓝牙设备扫描相关的方法。如果需要特定类型的扫描方式请使用“ScanFilter”。同时告诉我们可以通过“BluetoothAdapter#getBuletoothLeScanner()”方法获取该类的实例。还有就是提示大家使用之前需要在在注册清单里添加“android.Manifest.permission#BLUETOOTH_ADMIN”权限。
首先我们看一下,设备扫描相关方法:
前两个方法传递的参数有所不同,先说一下它们的共同点:
1.都是通过“ScanCallback”将扫描结果回调回来;
2.开启扫描程序必须需要定位权限:ACCESS_COARSE_LOCATION 或者 ACCESS_FINE_LOCATION
需要注意的是,当没有过滤条件的时候,当屏幕熄灭,扫描就会终止。当屏幕重新唤醒时扫描会重新开始。
关于该方法相关的使用和参数的详解,请阅读:低功耗蓝牙之设备扫描中的内容。
还有一个比较特殊的方法:
startScan(@NullableListfilters,@NullableScanSettingssettings,
@NonNullPendingIntentcallbackIntent)
它是通过PendingIntent进行信息的传递。当然扫描的结果是通过Intent进行传递,那样的话你就可以在广播接受者或者Activity中进行处理。
我们继续阅读三个方法内部调用的方法:
startScan(Listfilters,ScanSettingssettings,
finalWorkSourceworkSource,finalScanCallbackcallback,
finalPendingIntentcallbackIntent,
List>resultStorages)
我们分析一下它的逻辑:
private int startScan(List filters, ScanSettings settings,
final WorkSource workSource, final ScanCallback callback,
final PendingIntent callbackIntent,
List> 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的源码分析完了,好累的一天~