Android Bluetooth | 经典蓝牙设备扫描源码分析

        好厚米们,我来了!

        这次分享的是经典蓝牙设备执行扫描动作时源码的执行流程。

        先来了解下“经典蓝牙设备”和“低功耗蓝牙设备”的概念 。(ps:因为扫描有两种方式,分别适合不同类型的设备)

        经典蓝牙设备:是指采用蓝牙标准2.0及以上版本,支持传输速率为1Mdps的传统蓝牙设备。这类设备通常需要较高的功耗,用于数据传输范围较小且需要高带宽的应用,例如音频传输,文件传输等。常见的经典蓝牙设备有:蓝牙耳机蓝牙音箱蓝牙打印机蓝牙键盘蓝牙鼠标

        低功耗蓝牙设备:是指采用蓝牙标准4.0及以上版本,支持传输速率1Mdps ~ 2Mdps的低功耗蓝牙设备。这些设备通常需要较低的功耗,用于数据传输范围比较小且需要低带宽的应用。常见的低功耗蓝牙设备有:智能手环智能门锁

         下面来说下应用层是如何使用蓝牙扫描功能的。

        第一种就是使用ScanCallback的回调:

        代码如下,这种方式更适用于“低功耗蓝牙设备”扫描状态的监听(源码我就不深究了,简单介绍下):

//声明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);
        }

         第二种使用蓝牙扫描功能的方法就是通过BluetoothAdapter.startDiscovery()来完成的:

        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:我也是小白,刚看蓝牙源码不久,如果有哪里解释的不对,欢迎各位大神指点!

        文章会同步上传到公众号上(二两仙气儿),欢迎同好一起交流学习。

        

Android Bluetooth | 经典蓝牙设备扫描源码分析_第1张图片

你可能感兴趣的:(Android,Bluetooth开发,android)