安卓BLE被动扫描

14

我想在Android应用程序中 passively 扫描BLE广告商。

但是我找不到如何扫描的方法。

但是我无法确定是否可以使用被动扫描模式。

问题:在Android上是否可以使用“PASSIVE SCAN”?如果可以,如何使用此功能?

2个回答

17
active扫描和passive扫描的区别在于,active扫描从广告者请求一个SCAN_RESPONSE数据包。这是在检测到广告后发送一个SCAN_REQUEST数据包来完成的。两者的信息(有效负载)都将在发现设备回调的scanRecord参数中。
根据核心规范
“设备可以使用主动扫描来获取有关可能对填充用户界面有用的设备的更多信息。主动扫描涉及更多链路层广告消息。”
因此,在任何用例中,不需要区分这两种扫描类型。
但是,如果您想在后台侦听广告,则需要通过创建Service自行执行此操作-没有内置功能(截至Android 4.4)。
进行后台扫描,请参考此示例。但是,扫描会在应用程序被系统杀死(或被用户停止)时结束。
通过AlarmManager在任何地方启动PendingIntent(必须至少运行一次以启动服务的任何应用程序...)
AlarmManager alarmMgr = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getActivity(), BleScanService.class);
PendingIntent scanIntent = PendingIntent.getService(getActivity(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime(), intervalMillis, scanIntent);

Ble扫描服务

public class BleScanService extends Service implements LeScanCallback {

private final static String TAG = BleScanService.class.getSimpleName();

private final IBinder mBinder = new LocalBinder();

private BluetoothManager mBluetoothManager;

private BluetoothAdapter mBluetoothAdapter;

public class LocalBinder extends Binder {
        public BleScanService getService() {
            return BleScanService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initialize();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        long timeToScan = preferences.scanLength().get();
        startScan(timeToScan);

        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * Initializes a reference to the local bluetooth adapter.
     * 
     * @return Return true if the initialization is successful.
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter
        // through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        if (mBluetoothAdapter == null) {
            mBluetoothAdapter = mBluetoothManager.getAdapter();
            if (mBluetoothAdapter == null) {
                Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
                return false;
            }
        }

        Log.d(TAG, "Initialzed scanner.");
        return true;
    }

    /**
     * Checks if bluetooth is correctly set up.
     * 
     * @return
     */
    protected boolean isInitialized() {
        return mBluetoothManager != null && mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
    }

    /**
     * Checks if ble is ready and bluetooth is correctly setup.
     * 
     * @return
     */
    protected boolean isReady() {
        return isInitialized() && isBleReady();
    }

    /**
     * Checks if the device is ble ready.
     * 
     * @return
     */
    protected boolean isBleReady() {
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        Log.d(TAG, "Found ble device " + device.getName() + " " + device.getAddress());
        broadcastOnDeviceFound(device, scanRecord);
    }

    /**
     * Broadcasts a message with the given device.
     * 
     * @param device
     * @param scanRecord 
     */
    protected void broadcastOnDeviceFound(final BluetoothDevice device, byte[] scanRecord) {
        assert device != null : "Device should not be null.";

        Intent intent = new Intent(BleServiceConstants.ACTION_DEVICE_DISCOVERED);
        intent.putExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_DEVICE, device);
        intent.putExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_SCAN_RECORD, scanRecord);
        sendBroadcast(intent);
    }

    /**
     * Starts the bluetooth low energy scan It scans at least the
     * delayStopTimeInMillis.
     * 
     * @param delayStopTimeInMillis
     *            the duration of the scan
     * @return <code>true</code> if the scan is successfully started.
     */
    public boolean startScan(long delayStopTimeInMillis) {
        if (!isReady())
            return false;

        if (preferences.shouldScan().get()) {
            if (delayStopTimeInMillis <= 0) {
                Log.w(TAG, "Did not start scanning with automatic stop delay time of " + delayStopTimeInMillis);
                return false;
            }

            Log.d(TAG, "Auto-Stop scan after " + delayStopTimeInMillis + " ms");
            getMainHandler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    Log.d(TAG, "Stopped scan.");
                    stopScan();
                }
            }, delayStopTimeInMillis);
        }
        return startScan();
    }

    /**
     * @return an handler with the main (ui) looper.
     */
    private Handler getMainHandler() {
        return new Handler(getMainLooper());
    }

    /**
     * Starts the bluetooth low energy scan. It scans without time limit.
     * 
     * @return <code>true</code> if the scan is successfully started.
     */
    public boolean startScan() {
        if (!isReady())
            return false;

        if (preferences.shouldScan().get()) {
            if (mBluetoothAdapter != null) {
                Log.d(TAG, "Started scan.");
                return mBluetoothAdapter.startLeScan(this);
            } else {
                Log.d(TAG, "BluetoothAdapter is null.");
                return false;
            }
        }
        return false;
    }

    /**
     * Stops the bluetooth low energy scan.
     */
    public void stopScan() {
        if (!isReady())
            return;

        if (mBluetoothAdapter != null)
            mBluetoothAdapter.stopLeScan(this);
        else {
            Log.d(TAG, "BluetoothAdapter is null.");
        }
    }

    @Override
    public void onDestroy() {
        preferences.edit().shouldScan().put(false).apply();
        super.onDestroy();
    }
}

常量只是用于分发意图操作和额外名称的字符串。还有另一个偏好存储,用于存储扫描阶段应持续多长时间...您可以轻松地根据自己的需求进行更改。

然后,您需要使用与上述操作名称(BleServiceConstants.ACTION_DEVICE_DISCOVERED)匹配的意图过滤器注册广播接收器。

public class DeviceWatcher extends BroadcastReceiver {

  @Override
    public void onReceive(Context context, Intent intent) {
        BluetoothDevice device =  intent.getParcelableExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_DEVICE);

  // do anything with this information

  }
}

您可以设置AlarmManager以周期性地启动PendingIntent,该PendingIntent调用一个服务,该服务将扫描一段时间并侦听找到的设备。然后,可以通过意图将此信息发送到有趣的广播接收器。这些接收器将处理必须对此信息执行的任何操作。 - matcauthon
@matcauthon感谢您的答复。 这是否意味着有任何巧妙的方法进行被动扫描? - Stanley Ko
1
@RichardLeMesurier 谢谢。你的编辑比我的好听。 - matcauthon
1
@matcauthon “对于任何用例,不需要区分这两种扫描类型” - 这并不完全正确,对于持续发送可扫描广告的设备(例如定期发送优惠券的营销设备),您更喜欢不要有主动扫描的过载。不幸的是,Android似乎没有像iOS那样理解这一点。 - Aravind
这个答案是BLE被动扫描工作的好案例应该遵循。 - Huy Tower
显示剩余2条评论

2

请在AOSP仓库中找到btif_gatt_client.c文件,

进行编辑,

替换

BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_ACTI);

使用

BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_PASS);

然后构建AOSP,将映像刷入手机,被动扫描就可以工作了。


可能并不常见,像你刚才假设的那样自己构建应用程序并记录下来。 - Dushyant Suthar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接