好厚米们,我来了!
这次分享的是经典蓝牙设备执行扫描动作时源码的执行流程。
先来了解下“经典蓝牙设备”和“低功耗蓝牙设备”的概念 。(ps:因为扫描有两种方式,分别适合不同类型的设备)
经典蓝牙设备:是指采用蓝牙标准2.0及以上版本,支持传输速率为1Mdps的传统蓝牙设备。这类设备通常需要较高的功耗,用于数据传输范围较小且需要高带宽的应用,例如音频传输,文件传输等。常见的经典蓝牙设备有:蓝牙耳机,蓝牙音箱,蓝牙打印机,蓝牙键盘,蓝牙鼠标。
低功耗蓝牙设备:是指采用蓝牙标准4.0及以上版本,支持传输速率1Mdps ~ 2Mdps的低功耗蓝牙设备。这些设备通常需要较低的功耗,用于数据传输范围比较小且需要低带宽的应用。常见的低功耗蓝牙设备有:智能手环,智能门锁
下面来说下应用层是如何使用蓝牙扫描功能的。
代码如下,这种方式更适用于“低功耗蓝牙设备”扫描状态的监听(源码我就不深究了,简单介绍下):
//声明BluetoothLeScanner
private BluetoothLeScanner scanner =BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
// 处理扫描到的设备信息
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
// 扫描失败处理
}
};
// 开始扫描
if (scanner != null) {
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
// 添加过滤条件
List filters = new ArrayList<>();
//权限检查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
scanner.startScan(filters, settings, scanCallback);
}
// 停止扫描
if (scanner != null) {
scanner.stopScan(scanCallback);
}
startDiscovery方法是由BluetoothAdapter类提供的方法,用于发现和配对其他蓝牙设备。使用该方法后,系统将会执行一个经典蓝牙设备扫描过程,这个过程耗时较长,大概需要12秒钟到25秒钟左右,具体时间取决于操作系统版本、设备型号和周围环境的干扰情况等因素。
下面我们来逐步分析下调用statrDiscovery()时,代码的执行流程。
首先是应用层,应用层通过蓝牙适配器调用startDiscovery()方法,代码如下:
private BluetoothAdapter mBluetoothAdapter;//声明适配器
mBluetoothAdapter.startDiscovery();//适配器调用扫描方法
调用后会进入BluetoothAdapter.java中执行startDiscovery(),源码如下:
public boolean startDiscovery () {
if (getState() != STATE_ON) {
return false;
}
try {
mServiceLock.readLock().lock();
if (mService != null) {
return mService.startDiscovery(getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "", e);
} finally {
mServiceLock.readLock().unlock();
}
return false;
}
最终,代码会执行到 return mService.startDiscovery(getOpPackageName());
而mService的声明如下:
此接口是一个AIDL文件 ,位置:/system/bt/binder/android/bluetooth/IBluetooth.aidl
private IBluetooth mService;
通过这个接口,可以和蓝牙服务层进行通信,代码最终会通过AIDL通信,执行进AdapterService.java中。AdapterService.startDiscovery()
@Override
public boolean startDiscovery(String callingPackage) {
if (!Utils.checkCaller()) {
Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
return false;
}
AdapterService service = getService();
if (service == null) {
return false;
}
return service.startDiscovery(callingPackage);
}
最终会执行到这个startDiscovery(String callingPackage)方法中,代码如下:
boolean startDiscovery (String callingPackage){
UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
debugLog("startDiscovery");
//权限检查
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
boolean isQApp = Utils.isQApp(this, callingPackage);
String permission = null;
if (Utils.checkCallerHasNetworkSettingsPermission(this)) {
//权限检查
permission = android.Manifest.permission.NETWORK_SETTINGS;
} else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
//权限检查
permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
} else if (isQApp) {
if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingUser)) {
return false;
}
//还是权限检查
permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
} else {
if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingUser)) {
return false;
}
permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
}
synchronized (mDiscoveringPackages) {
mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
}
//会执行协议栈的接口,通知协议栈开始扫描动作。
return startDiscoveryNative();
}
startDiscoveryNative()会调用JNI接口,将扫描的动作传递到协议栈。其具体实现的文件路径:/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
部分代码如下:
static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
ALOGV("%s", __func__);
if (!sBluetoothInterface) return JNI_FALSE;
int ret = sBluetoothInterface->start_discovery();
return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
之后协议栈就会开始通知HAL层,最终通知到蓝牙芯片的驱动执行扫描动作。
还是 /packages/apps/Bluetooth/jni/com_android_bluetooth_hbtservice_AdapterService.cpp
com_android_bluetooth_btservice_AdapterService.cpp中注册了一个callback回调,用于接收底层传递过来的扫描状态,便于传递回Java层。
部分代码如下:
//callback,调用java层的回调函数
static void discovery_state_changed_callback(bt_discovery_state_t state) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
ALOGV("%s: DiscoveryState:%d ", __func__, state);
sCallbackEnv->CallVoidMethod(
sJniCallbacksObj, method_discoveryStateChangeCallback, (jint)state);
}
//获取java层callback方法的ID
method_discoveryStateChangeCallback = env->GetMethodID(
jniCallbackClass, "discoveryStateChangeCallback", "(I)V");
继而通过 /packages/apps/Bluetooth/src/com/android/bluetooth/btservice/JniCallbacks.java中的discoveryStateChangeCallback接口将扫面获取到的状态通知到java层。
JniCallbacks.java部分代码如下:
void discoveryStateChangeCallback(int state) {
mAdapterProperties.discoveryStateChangeCallback(state);
}
这样每次回调回来的扫描状态就会通过AdapterProperties.java的void discoveryStateChangeCallback(int status)进行处理,根据状态的不同向状态机发送特定msg
部分代码如下:
void discoveryStateChangeCallback(int state) {
infoLog("Callback:discoveryStateChangeCallback with state:" + state);
synchronized (mObject) {
Intent intent;
if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {
mDiscovering = false;
mService.clearDiscoveringPackages();
mDiscoveryEndMs = System.currentTimeMillis();
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
} else if (state == AbstractionLayer.BT_DISCOVERY_STARTED) {
mDiscovering = true;
mDiscoveryEndMs = System.currentTimeMillis() + DEFAULT_DISCOVERY_TIMEOUT_MS;
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
}
}
}
至此,应用层即可通过接收对应的状态广播,进行业务逻辑的编写。
也是同样通过一个call回调,位置依旧是com_android_bluetooth_btservice_AdapterService.cpp中,部分代码如下:
//callback,调用java层的回调函数
static void device_found_callback(int num_properties,
bt_property_t* properties) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
ScopedLocalRef addr(sCallbackEnv.get(), NULL);
int addr_index;
for (int i = 0; i < num_properties; i++) {
if (properties[i].type == BT_PROPERTY_BDADDR) {
addr.reset(sCallbackEnv->NewByteArray(properties[i].len));
if (!addr.get()) {
ALOGE("Address is NULL (unable to allocate) in %s", __func__);
return;
}
//保存扫描到的蓝牙设备的MAC地址,地址信息会存入addr数组中
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,
(jbyte*)properties[i].val);
addr_index = i;//同时properties中的索引赋值给addr_index
}
}
//调用deviceFoundCallback回调,将扫描到的蓝牙设备信息传递会应用层。
sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,
addr.get());
}
//获取java层callback方法的ID
method_deviceFoundCallback =
env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
此回调也是定义在 JniCallbacks.java中,代码如下:
void deviceFoundCallback(byte[] address) {
mRemoteDevices.deviceFoundCallback(address);
}
通过此回调,最终会执行到RemoteDevices.java中的deviceFoundCallback方法。代码如下:
void deviceFoundCallback(byte[] address) {
// The device properties are already registered - we can send the intent
// now
BluetoothDevice device = getDevice(address);
debugLog("deviceFoundCallback: Remote Address is:" + device);
DeviceProperties deviceProp = getDeviceProperties(device);
if (deviceProp == null) {
errorLog("Device Properties is null for Device:" + device);
return;
}
//将扫描到的设备信息通过Intent进行包装。
Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(deviceProp.mBluetoothClass));
intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
final ArrayList packages = sAdapterService.getDiscoveringPackages();
synchronized (packages) {
for (DiscoveringPackage pkg : packages) {
intent.setPackage(pkg.getPackageName());
//最终将包装好的设备信息以广播的形式进行发送。
sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
AdapterService.BLUETOOTH_PERM, pkg.getPermission()
});
}
}
}
应用层只需要监听上述代码中的广播,即可接收扫描后取得的设备信息,进行视图的加载和相关业务逻辑的对应。
至此,经典蓝牙的扫描流程以及扫描状态/数据的回调就介绍完毕了~
ps:我也是小白,刚看蓝牙源码不久,如果有哪里解释的不对,欢迎各位大神指点!
文章会同步上传到公众号上(二两仙气儿),欢迎同好一起交流学习。