蓝牙广播扫描机制

Android源码相关解析

1.蓝牙扫描

  1. 调用 BluetoothAdapter.java 的startLeScan()
// BluetoothAdapter.java 
public boolean startLeScan(LeScanCallback callback) {
    // 1.开始扫描
    return startLeScan(null, callback);
}
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
    ...
    synchronized (mLeScanClients) {
        if (mLeScanClients.containsKey(callback)) {
            if (DBG) {
                Log.e(TAG, "LE Scan has already started");
            }
            return false;
        }

        IBluetoothGatt iGatt = getBluetoothGatt();
        if (iGatt == null) {
            // BLE is not supported
            return false;
        }

        @SuppressLint("AndroidFrameworkBluetoothPermission")
        ScanCallback scanCallback =
                new ScanCallback() {
                    @Override
                    public void onScanResult(int callbackType, ScanResult result) {
                        if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
                            // Should not happen.
                            Log.e(TAG, "LE Scan has already started");
                            return;
                        }
                        ScanRecord scanRecord = result.getScanRecord();
                        if (scanRecord == null) {
                            return;
                        }
                        if (serviceUuids != null) {
                            List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
                            for (UUID uuid : serviceUuids) {
                                uuids.add(new ParcelUuid(uuid));
                            }
                            List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids();
                            if (scanServiceUuids == null
                                    || !scanServiceUuids.containsAll(uuids)) {
                                if (DBG) {
                                    Log.d(TAG, "uuids does not match");
                                }
                                return;
                            }
                        }
                        callback.onLeScan(
                                result.getDevice(), result.getRssi(), scanRecord.getBytes());
                    }
                };
        ScanSettings settings =
                new ScanSettings.Builder()
                        .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();

        List<ScanFilter> filters = new ArrayList<ScanFilter>();
        if (serviceUuids != null && serviceUuids.length > 0) {
            // Note scan filter does not support matching an UUID array so we put one
            // UUID to hardware and match the whole array in callback.
            ScanFilter filter =
                    new ScanFilter.Builder()
                            .setServiceUuid(new ParcelUuid(serviceUuids[0]))
                            .build();
            filters.add(filter);
        }
        // 2.调用BluetoothLeScanner的扫描,此处scanner是跟随adapter创建的,加了锁
        scanner.startScan(filters, settings, scanCallback);

        mLeScanClients.put(callback, scanCallback);
        return true;
    }
}
  1. 调用BluetoothLeScanner.java 的startScan()
// BluetoothLeScanner.java
private int startScan(
        List<ScanFilter> filters,
        ScanSettings settings,
        final WorkSource workSource,
        final ScanCallback callback,
        final PendingIntent callbackIntent) {
    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) {
        if (callback != null && mLeScanClients.containsKey(callback)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
        }
        IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
        if (gatt == null) {
            return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
        }
        if (!isSettingsConfigAllowedForScan(settings)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
        }
        if (!isHardwareResourcesAvailableForScan(settings)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
        }
        if (!isSettingsAndFilterComboAllowed(settings, filters)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
        }
        if (callback != null) {
            BleScanCallbackWrapper wrapper =
                    new BleScanCallbackWrapper(gatt, filters, settings, workSource, callback);
            wrapper.startRegistration();
        } else {
            try {
                final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
                // 3.调用IBluetoothGatt的startScanForIntent()
                gatt.startScanForIntent(
                        callbackIntent, settings, filters, mAttributionSource, recv);
                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
            } catch (TimeoutException | RemoteException e) {
                return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
            }
        }
    }
    return ScanCallback.NO_ERROR;
}

  1. 调用IBluetoothGatt的startScanForIntent(),其中IBluetoothGatt是aidl接口

  2. 调用GattService的startScanForIntent(),此处会进行TransitionalScanHelper对象的初始化,此处是关键

  3. 调用ScanMananger的registerScanner()进行蓝牙扫码注册,其中ScanManger对象在GattService 服务启动时,start()会进行初始化

// IBluetoothGatt.aidl 接口方法的实现类,GattService.java
 @Override
public void startScanForIntent(
        PendingIntent intent,
        ScanSettings settings,
        List<ScanFilter> filters,
        AttributionSource attributionSource) {
    GattService service = getService();
    if (service == null) {
        return;
    }
    service.getTransitionalScanHelper()
            .registerPiAndStartScan(intent, settings, filters, attributionSource);
}
// TransitionalScanHelper的初始化
public final TransitionalScanHelper mTransitionalScanHelper =
    new TransitionalScanHelper(this, this::isTestModeEnabled);

@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
        List<ScanFilter> filters, AttributionSource attributionSource) {
    if (DBG) {
        Log.d(TAG, "start scan with filters, for PendingIntent");
    }

    if (!Utils.checkScanPermissionForDataDelivery(
            this, attributionSource, "Starting GATT scan.")) {
        return;
    }
    enforcePrivilegedPermissionIfNeeded(settings);
    settings = enforceReportDelayFloor(settings);
    enforcePrivilegedPermissionIfNeeded(filters);
    UUID uuid = UUID.randomUUID();
    String callingPackage = attributionSource.getPackageName();
    int callingUid = attributionSource.getUid();
    PendingIntentInfo piInfo = new PendingIntentInfo();
    piInfo.intent = pendingIntent;
    piInfo.settings = settings;
    piInfo.filters = filters;
    piInfo.callingPackage = callingPackage;
    piInfo.callingUid = callingUid;
    if (DBG) {
        Log.d(
                TAG,
                "startScan(PI) -"
                        + (" UUID=" + uuid)
                        + (" Package=" + callingPackage)
                        + (" UID=" + callingUid));
    }

    // Don't start scan if the Pi scan already in mScannerMap.
    if (mTransitionalScanHelper.getScannerMap().getByContextInfo(piInfo) != null) {
        Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap.");
        return;
    }

    ContextMap.App app =
            mTransitionalScanHelper.getScannerMap().add(uuid, null, null, piInfo, this);

    app.mUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
    mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
    app.mEligibleForSanitizedExposureNotification =
            callingPackage.equals(mExposureNotificationPackage);

    app.mHasDisavowedLocation =
            Utils.hasDisavowedLocationForScan(this, attributionSource, isTestModeEnabled());

    if (!app.mHasDisavowedLocation) {
        try {
            if (checkCallerTargetSdk(this, callingPackage, Build.VERSION_CODES.Q)) {
                app.hasLocationPermission = Utils.checkCallerHasFineLocation(
                        this, attributionSource, app.mUserHandle);
            } else {
                app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
                        this, attributionSource, app.mUserHandle);
            }
        } catch (SecurityException se) {
            // No need to throw here. Just mark as not granted.
            app.hasLocationPermission = false;
        }
    }
    app.mHasNetworkSettingsPermission =
            Utils.checkCallerHasNetworkSettingsPermission(this);
    app.mHasNetworkSetupWizardPermission =
            Utils.checkCallerHasNetworkSetupWizardPermission(this);
    app.mHasScanWithoutLocationPermission =
            Utils.checkCallerHasScanWithoutLocationPermission(this);
    app.mAssociatedDevices = getAssociatedDevices(callingPackage);
    // 5.调用ScanMananger的registerScanner()进行扫描注册
    mScanManager.registerScanner(uuid);

    // If this fails, we should stop the scan immediately.
    if (!pendingIntent.addCancelListener(Runnable::run, mScanIntentCancelListener)) {
        Log.d(TAG, "scanning PendingIntent is already cancelled, stopping scan.");
        stopScan(pendingIntent, attributionSource);
    }
}
@Override
public void start() {
    if (DBG) {
        Log.d(TAG, "start()");
    }
    mExposureNotificationPackage = getString(R.string.exposure_notification_package);
    Settings.Global.putInt(
            getContentResolver(), "bluetooth_sanitized_exposure_notification_supported", 1);

    mNativeInterface = GattObjectsFactory.getInstance().getNativeInterface();
    mNativeInterface.init(this);
    mAdapterService = AdapterService.getAdapterService();
    mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance();
    mCompanionManager = getSystemService(CompanionDeviceManager.class);
    mAppOps = getSystemService(AppOpsManager.class);
    mAdvertiseManager =
            new AdvertiseManager(
                    this,
                    AdvertiseManagerNativeInterface.getInstance(),
                    mAdapterService,
                    mAdvertiserMap);

    HandlerThread thread = new HandlerThread("BluetoothScanManager");
    thread.start();
    // 此处是Manager初始化的地方
    mScanManager =
            GattObjectsFactory.getInstance()
                    .createScanManager(
                            this, mAdapterService, mBluetoothAdapterProxy, thread.getLooper());

    mPeriodicScanManager = GattObjectsFactory.getInstance()
            .createPeriodicScanManager(mAdapterService);

    mDistanceMeasurementManager = GattObjectsFactory.getInstance()
            .createDistanceMeasurementManager(mAdapterService);

    mActivityManager = getSystemService(ActivityManager.class);
    mPackageManager = mAdapterService.getPackageManager();
}
  1. 调用ScanNative的registerScanner(),其中NativeInterface在构造函数时初始化
// ScanMananger.java
public void registerScanner(UUID uuid) {
    mScanNative.registerScanner(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
}
// ScanMananger.ScanNative.java
private void registerScanner(long appUuidLsb, long appUuidMsb) {
    mNativeInterface.registerScanner(appUuidLsb, appUuidMsb);
}
ScanNative(TransitionalScanHelper scanHelper) {
    // 此处调用工厂模式,获取到Navtive接口实例对象
    mNativeInterface = ScanObjectsFactory.getInstance().getScanNativeInterface();
    mNativeInterface.init(scanHelper);
    mFilterIndexStack = new ArrayDeque<Integer>();
    mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>();

    mAlarmManager = mContext.getSystemService(AlarmManager.class);
    Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
    mBatchScanIntervalIntent =
            PendingIntent.getBroadcast(
                    mContext, 0, batchIntent, PendingIntent.FLAG_IMMUTABLE);
    IntentFilter filter = new IntentFilter();
    filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
    filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
    mBatchAlarmReceiver.set(
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime());
                    String action = intent.getAction();

                    if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
                        if (mBatchClients.isEmpty()) {
                            return;
                        }
                        // Note this actually flushes all pending batch data.
                        if (mBatchClients.iterator().hasNext()) {
                            flushBatchScanResults(mBatchClients.iterator().next());
                        }
                    }
                }
            });
    mContext.registerReceiver(mBatchAlarmReceiver.get(), filter);
}
  1. ScanObjectsFactory.getInstance(),本质上是ScanNativeInterface.getInstance(),registerScanner()背后调用的是registerScannerNative()方法,到此Framwork层就无法继续往下分析了
 // ScanNativeInterface.java
 public ScanNativeInterface getScanNativeInterface() {
        return ScanNativeInterface.getInstance();
 }
 // ScanNativeInterface.java
public void registerScanner(long appUuidLsb, long appUuidMsb) {
    registerScannerNative(appUuidLsb, appUuidMsb);
}
// Native方法
private native void registerScannerNative(long appUuidLsb, long appUuidMsb);
  1. 注册成功以后,会开启蓝牙扫描,回调是通过ScanNativeInterface的Nactive回调,在通知到TransitionalScanHelper的onScannerRegistered(),最终调用TransitionalScanHelper.continuePiStartScan()
  // ScanNativeInterface.java
  void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
        throws RemoteException {
    if (mScanHelper == null) {
        Log.e(TAG, "Scan helper is null!");
        return;
    }
    mScanHelper.onScannerRegistered(status, scannerId, uuidLsb, uuidMsb);
 }

 // TransitionalScanHelper.java
 public void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
        throws RemoteException {
    UUID uuid = new UUID(uuidMsb, uuidLsb);
    Log.d(
            TAG,
            "onScannerRegistered() - UUID="
                    + uuid
                    + ", scannerId="
                    + scannerId
                    + ", status="
                    + status);

    // First check the callback map
    ScannerMap.ScannerApp cbApp = mScannerMap.getByUuid(uuid);
    if (cbApp != null) {
        if (status == 0) {
            cbApp.mId = scannerId;
            // If app is callback based, setup a death recipient. App will initiate the start.
            // Otherwise, if PendingIntent based, start the scan directly.
            if (cbApp.mCallback != null) {
                cbApp.linkToDeath(new ScannerDeathRecipient(scannerId, cbApp.mName));
            } else {
                // 8.调用扫描方法
                continuePiStartScan(scannerId, cbApp);
            }
        } else {
            mScannerMap.remove(scannerId);
        }
        if (cbApp.mCallback != null) {
            cbApp.mCallback.onScannerRegistered(status, scannerId);
        }
    }
}
//
void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
        throws RemoteException {
    if (mScanHelper == null) {
        Log.e(TAG, "Scan helper is null!");
        return;
    }
    mScanHelper.onScannerRegistered(status, scannerId, uuidLsb, uuidMsb);
}
  1. 开始扫描,最终调用ScanManager.startScan(),本质是发送MSG_START_BLE_SCAN标识消息,最终由ScanManager.ClientHandler的handleStartScan()方法处理
// TransitionalScanHelper.java
 public void continuePiStartScan(int scannerId, ScannerMap.ScannerApp app) {
    final PendingIntentInfo piInfo = app.mInfo;
    final ScanClient scanClient =
            new ScanClient(scannerId, piInfo.settings, piInfo.filters, piInfo.callingUid);
    scanClient.hasLocationPermission = app.mHasLocationPermission;
    scanClient.userHandle = app.mUserHandle;
    scanClient.isQApp = checkCallerTargetSdk(mContext, app.mName, Build.VERSION_CODES.Q);
    scanClient.eligibleForSanitizedExposureNotification =
            app.mEligibleForSanitizedExposureNotification;
    scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission;
    scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission;
    scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission;
    scanClient.associatedDevices = app.mAssociatedDevices;
    scanClient.hasDisavowedLocation = app.mHasDisavowedLocation;

    AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId);
    if (scanStats != null) {
        scanClient.stats = scanStats;
        boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty();
        scanStats.recordScanStart(
                piInfo.settings, piInfo.filters, isFilteredScan, false, scannerId);
    }

    mScanManager.startScan(scanClient);
}
// ScanManager.java
public void startScan(ScanClient client) {
    Log.d(TAG, "startScan() " + client);
    sendMessage(MSG_START_BLE_SCAN, client);
}
private void sendMessage(int what, ScanClient client) {
    final ClientHandler handler = mHandler;
    if (handler == null) {
        Log.d(TAG, "sendMessage: mHandler is null.");
        return;
    }
    Message message = new Message();
    message.what = what;
    message.obj = client;
    handler.sendMessage(message);
}
// ScanManager.ClientHandler.java
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_START_BLE_SCAN:
            //此处通知进行扫描
            handleStartScan((ScanClient) msg.obj);
            break;
        case MSG_STOP_BLE_SCAN:
            handleStopScan((ScanClient) msg.obj);
            break;
        case MSG_FLUSH_BATCH_RESULTS:
            handleFlushBatchResults((ScanClient) msg.obj);
            break;
        case MSG_SCAN_TIMEOUT:
            mScanNative.regularScanTimeout((ScanClient) msg.obj);
            break;
        case MSG_SUSPEND_SCANS:
            handleSuspendScans();
            break;
        case MSG_RESUME_SCANS:
            handleResumeScans();
            break;
        case MSG_SCREEN_OFF:
            handleScreenOff();
            break;
        case MSG_SCREEN_ON:
            handleScreenOn();
            break;
        case MSG_REVERT_SCAN_MODE_UPGRADE:
            revertScanModeUpgrade((ScanClient) msg.obj);
            break;
        case MSG_IMPORTANCE_CHANGE:
            handleImportanceChange((UidImportance) msg.obj);
            break;
        case MSG_START_CONNECTING:
            handleConnectingState();
            break;
        case MSG_STOP_CONNECTING:
            handleClearConnectingState();
            break;
        case MSG_BT_PROFILE_CONN_STATE_CHANGED:
            handleProfileConnectionStateChanged(msg);
            break;
        default:
            // Shouldn't happen.
            Log.e(TAG, "received an unknown message : " + msg.what);
    }
}
  1. 进入扫描处理后,由于扫描通常设置ScanSettings.CALLBACK_TYPE_ALL_MATCHES 设置蓝牙扫描滤波器硬件匹配的匹配模式,所以只会进入isBatchClient()和isAutoBatchScanClientEnabled(),前者是只要不设置ReportDelayMillis(延迟多少秒,统一接收蓝牙广播)参数,就会执行;后者则是沿用上次扫描设置参数,不继续往下分析。最终会调用ScanNative.startBatchScan()方法
//  ScanManager.java
 void handleStartScan(ScanClient client) {
    Log.d(TAG, "handling starting scan");
    fetchAppForegroundState(client);

    if (!isScanSupported(client)) {
        Log.e(TAG, "Scan settings not supported");
        return;
    }

    if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) {
        Log.e(TAG, "Scan already started");
        return;
    }

    if (requiresScreenOn(client) && !mScreenOn) {
        Log.w(
                TAG,
                "Cannot start unfiltered scan in screen-off. This scan will be resumed "
                        + "later: "
                        + client.scannerId);
        mSuspendedScanClients.add(client);
        if (client.stats != null) {
            client.stats.recordScanSuspend(client.scannerId);
        }
        return;
    }

    final boolean locationEnabled = mLocationManager.isLocationEnabled();
    if (requiresLocationOn(client) && !locationEnabled) {
        Log.i(
                TAG,
                "Cannot start unfiltered scan in location-off. This scan will be"
                        + " resumed when location is on: "
                        + client.scannerId);
        mSuspendedScanClients.add(client);
        if (client.stats != null) {
            client.stats.recordScanSuspend(client.scannerId);
        }
        return;
    }

    if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) {
        if (mScreenOn) {
            clearAutoBatchScanClient(client);
        } else {
            setAutoBatchScanClient(client);
        }
    }

    // Begin scan operations.
    if (isBatchClient(client) || isAutoBatchScanClientEnabled(client)) {
        mBatchClients.add(client);
        // 10.此处进行扫描
        mScanNative.startBatchScan(client);
    } else {
        updateScanModeBeforeStart(client);
        updateScanModeConcurrency(client);
        mRegularScanClients.add(client);
        mScanNative.startRegularScan(client);
        if (!mScanNative.isOpportunisticScanClient(client)) {
            mScanNative.configureRegularScanParams();

            if (!mScanNative.isExemptFromScanTimeout(client)) {
                Message msg = obtainMessage(MSG_SCAN_TIMEOUT);
                msg.obj = client;
                // Only one timeout message should exist at any time
                removeMessages(MSG_SCAN_TIMEOUT, client);
                sendMessageDelayed(msg, mAdapterService.getScanTimeoutMillis());
                Log.d(
                        TAG,
                        "apply scan timeout ("
                                + mAdapterService.getScanTimeoutMillis()
                                + ")"
                                + "to scannerId "
                                + client.scannerId);
            }
        }
    }
    client.started = true;
}

private boolean isBatchClient(ScanClient client) {
    if (client == null || client.settings == null) {
        return false;
    }
    ScanSettings settings = client.settings;
    return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
            && settings.getReportDelayMillis() != 0;
}

private boolean isAutoBatchScanClientEnabled(ScanClient client) {
    return client.stats != null && client.stats.isAutoBatchScan(client.scannerId);
}

  1. 进入startBatchScan()方法后,此处关键就是configureScanFilters(),后续的isOpportunisticScanClient()则受Mode配置影响,当为mode不是SCAN_MODE_OPPORTUNISTIC会触发

  2. SCAN_MODE_LOW_POWER

  这个是Android默认的扫描模式,耗电量最小。如果扫描不再前台,则强制执行此模式。

  在这种模式下, Android会扫喵0.5s,暂停4.5s.

4.  SCAN_MODE_BALANCED

  平衡模式, 平衡扫描频率和耗电量的关系。

  在这种模式下,Android会扫描2s, 暂停3s。 这是一种妥协模式。

7.  SCAN_MODE_LOW_LATENCY

  连续不断的扫描, 建议应用在前台时使用。但会消耗比较多的电量。 扫描结果也会比较快一些。

9.  SCAN_MODE_OPPORTUNISTIC

  这种模式下, 只会监听其他APP的扫描结果回调。它无法发现你想发现的设备。
//  ScanManager.java
void startBatchScan(ScanClient client) {
    if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
        initFilterIndexStack();
    }
    configureScanFilters(client);
    // 此处只有Mode为
    if (!isOpportunisticScanClient(client)) {
        // Reset batch scan. May need to stop the existing batch scan and update scan
        // params.
        resetBatchScan(client);
    }
}
  1. 最终会调用ScanNativeInterface.gattClientStartBatchScan()方法,剩下的就是Navtive提供回来的回调了
//  ScanManager.java
private void resetBatchScan(ScanClient client) {
    int scannerId = client.scannerId;
    BatchScanParams batchScanParams = getBatchScanParams();
    // Stop batch if batch scan params changed and previous params is not null.
    if (mBatchScanParams != null && (!mBatchScanParams.equals(batchScanParams))) {
        Log.d(TAG, "stopping BLe Batch");
        resetCountDownLatch();
        mNativeInterface.gattClientStopBatchScan(scannerId);
        waitForCallback();
        // Clear pending results as it's illegal to config storage if there are still
        // pending results.
        flushBatchResults(scannerId);
    }
    // Start batch if batchScanParams changed and current params is not null.
    if (batchScanParams != null && (!batchScanParams.equals(mBatchScanParams))) {
        int notifyThreshold = 95;
        Log.d(TAG, "Starting BLE batch scan");
        int resultType = getResultType(batchScanParams);
        int fullScanPercent = getFullScanStoragePercent(resultType);
        resetCountDownLatch();
        Log.d(TAG, "configuring batch scan storage, appIf " + client.scannerId);
        mNativeInterface.gattClientConfigBatchScanStorage(
                client.scannerId, fullScanPercent, 100 - fullScanPercent, notifyThreshold);
        waitForCallback();
        resetCountDownLatch();
        int scanInterval =
                Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.scanMode));
        int scanWindow =
                Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.scanMode));
        // 12.此处进行native调用
        mNativeInterface.gattClientStartBatchScan(
                scannerId,
                resultType,
                scanInterval,
                scanWindow,
                0,
                DISCARD_OLDEST_WHEN_BUFFER_FULL);
        waitForCallback();
    }
    mBatchScanParams = batchScanParams;
    setBatchAlarm();
}

2.蓝牙扫描限制

  1. BluetoothLeScanner.java 的startScan()中,会调用BleScanCallbackWrapper.startRegistration() ,而BleScanCallbackWrapper本质上是去调用GattService.registerScanner()

  2. 在GattService.registerScanner()方法中,AppScanStats.isScanningTooFrequently()用于判断是否过于频繁扫描

// BluetoothLeScanner.java
private int startScan(
    List<ScanFilter> filters,
    ScanSettings settings,
    final WorkSource workSource,
    final ScanCallback callback,
    final PendingIntent callbackIntent) {
    ...
    // 1.此处就是注册
    BleScanCallbackWrapper wrapper =
        new BleScanCallbackWrapper(gatt, filters, settings, workSource, callback);
    wrapper.startRegistration();
    ...
    // 此处就是之前分析开始扫描的地方
    gatt.startScanForIntent(
                        callbackIntent, settings, filters, mAttributionSource, recv);
}
// BluetoothLeScanner.BleScanCallbackWrapper.java
public void startRegistration() {
    synchronized (this) {
        // Scan stopped.
        if (mScannerId == -1 || mScannerId == -2) return;
        try {
            // 此处调用GattService.registerScanner()
            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.
            if (mScannerId == 0) mScannerId = -1;
            // If scanning too frequently, don't report anything to the app.
            if (mScannerId == -2) return;
            postCallbackError(mScanCallback,
                ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
        }
    }
}

// GattService.BluetoothGattBinder.java
@Override
public void registerScanner(IScannerCallback callback, WorkSource workSource)
   throws RemoteException {
   GattService service = getService();
   if (service == null) {
          return;
      }
   service.registerScanner(callback, workSource);
}
// GattService.java
void registerScanner(IScannerCallback callback, WorkSource workSource) throws RemoteException {
    enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");

    UUID uuid = UUID.randomUUID();
    if (DBG) {
            Log.d(TAG, "registerScanner() - UUID=" + uuid);
        }

    if (workSource != null) {
            enforceImpersonatationPermission();
        }

    AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid());
    if (app != null && app.isScanningTooFrequently()
                && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) {
            Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
            callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1);
            return;
        }

    mScannerMap.add(uuid, workSource, callback, null, this);
    mScanManager.registerScanner(uuid);
}
 @Override
 public void registerScanner(IScannerCallback callback, WorkSource workSource)
       throws RemoteException {
   GattService service = getService();
   if (service == null) {
       return;
   }
   service.registerScanner(callback, workSource);
}
  1. AppScanStats.isScanningTooFrequently() android 14版本 与Andoid10 源码只有标识名称不同,上面为14,下面为10,其中有俩个条件会触发扫描频率过快问题。

    1. AdapterService.getScanQuotaCount()返回最大次数为5,即扫描次数累计小于5

    2. 当前时间戳 - 上次扫描结束时间戳(mLastScans最先加入的一组,即第一次扫描结束时间,mLastScans大小为4) < 30s

// AdapterService.DeviceConfigListener.java
private static final int DEFAULT_SCAN_QUOTA_COUNT = 5; //扫描的最大次数
private static final long DEFAULT_SCAN_QUOTA_WINDOW_MILLIS = 30 * SECOND_IN_MILLIS; // 扫描周期
// AppScanStats.java
public synchronized boolean isScanningTooFrequently() {
    if (mLastScans.size() < mAdapterService.getScanQuotaCount()) {
        return false;
    }

    return (SystemClock.elapsedRealtime() - mLastScans.get(0).timestamp)
            < mAdapterService.getScanQuotaWindowMillis();
}

public synchronized void recordScanStop(int scannerId) {
        LastScan scan = getScanFromScannerId(scannerId);
        if (scan == null) {
            return;
        }
        this.mScansStopped++;
        stopTime = SystemClock.elapsedRealtime();
        long scanDuration = stopTime - scan.timestamp;
        scan.duration = scanDuration;
        if (scan.isSuspended) {
            long suspendDuration = stopTime - scan.suspendStartTime;
            scan.suspendDuration += suspendDuration;
            mTotalSuspendTime += suspendDuration;
        }
        mOngoingScans.remove(scannerId);
        if (mLastScans.size() >= mAdapterService.getScanQuotaCount()) {
            mLastScans.remove(0);
        }
        // 此处在扫描结束时会记录第一次扫描结束时间
        mLastScans.add(scan);
        ...
    }
// andorid 10版本,标记名称会不一样
static final int NUM_SCAN_DURATIONS_KEPT = 5;
static final long EXCESSIVE_SCANNING_PERIOD_MS = 30 * 1000;

蓝牙广播

  1. 广播状态

设备每次进行广播时,通过三个广播通道发送相同数据包。此三个数据包序列称为一个“广播事件”。两个广播事件之间的时间被称为广播间隔,长度从 20 毫秒到 10.24 秒不等。

image.png
  1. 广播时序

2个广播的间隔时间 T_advEvent= 广播间隔 + advDelay(0 - 10ms)

其中广播会在不同的广播通道(37/38/39)中进行广播,每跳一个通道,其广播时长 <= 10.24s

  1. 扫描时序

设备扫描会在 (37/38/39)广播通道索引高中扫描,扫描窗口时间和扫描间隔时间 < 10.24s,每个通道索引的扫描时间独立。

  1. 安卓蓝牙连接

进行连接前,需要扫描到蓝牙广播,否则连接时会报GATT_ERROR = 133代码,且安卓蓝牙扫描所返回的列表,是由底层Native控制的,其扫描列表有大小限制,具体参数未能查询到相关参数

参考资料

意法半导体公开的蓝牙文档
https://www.st.com/resource/zh/programming_manual/pm0269-bluetooth-low-energy-stack-v3x-programming-guidelines-stmicroelectronics.pdf

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容