Ble 扫描回调只被调用几次然后停止

Ble scanning callback only get called several times then stopped

我有 2 个 phones Android 5.0.2,他们都安装了最新的 Radius Beacon 的应用程序:Locate Beacon,同时,我转在 2 IBeacon 发送器上,可以看到 RSSI 不断变化 在两个 phone 和 App.

但是当我尝试编写一些示例代码来模拟上述情况时,我发现 ble 扫描回调总是 在调用 2 或 3 次后停止调用,我最初怀疑'Locate Beacon' 可能使用不同的方式,所以我尝试了两种 API,一种用于旧的 4.4,另一种是 android 5 中引入的新方式,但两者的行为相同(但所有 运行 都在 android 5).

4.4一:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter;
private static final String LOG_TAG = "BleCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        calledTimes++;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                calledTimesTextView.setText(Integer.toString(calledTimes));
            }
        });
        Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    mBluetoothAdapter.startLeScan(mLeScanCallback);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

和 5.0.2:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mLescanner;
private ScanCallback mLeScanCallback;
private static final String LOG_TAG = "BleFingerprintCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    this.mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    this.mLescanner = this.mBluetoothAdapter.getBluetoothLeScanner();

    ScanSettings bleScanSettings = new ScanSettings.Builder().setScanMode(
            ScanSettings.SCAN_MODE_LOW_LATENCY).build();

    this.mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            calledTimes++;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    calledTimesTextView.setText(Integer
                            .toString(calledTimes));
                }
            });
            Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {

        }

        @Override
        public void onScanFailed(int errorCode) {
        }
    };
    this.mLescanner.startScan(null, bleScanSettings, this.mLeScanCallback);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

它们非常简单,只是在 UI 中显示一个计数器,最后证明总是停在 2 或 3。

我之前在带有 android 4.4 设备的 SamSung note 2 上播放过这个 ble 广告接收,它工作完美,回调每秒被调用一次。 那么有人可以帮忙吗? 为什么 Radius 的 Locate Beacon 在这里工作得很好

不同的 Android 设备在扫描 可连接 BLE 广告时表现不同。 在某些设备上(例如 Nexus 4) ,对于发送 connectable 广告的发送器,扫描 API 每次扫描仅获得一个回调,而对于 non-connectable 的每个广告,它们都会获得一个扫描回调广告。其他设备(例如 Nexus 5)无论是否可连接,都会为每个广告提供扫描回调。

Locate app you mention uses the open source Android Beacon Library 检测信标。它建立在您在问题中显示的相同扫描 API 之上,但它通过定义扫描周期(前台默认为 1.1 秒)并以此间隔停止和重新启动扫描来解决此问题。 停止并重新启动扫描会导致 Android 发送新的回调。

这里还有一些其他注意事项:

  • 为可连接设备获取多个扫描回调的问题适用于 4.x 和 5.x 扫描 API。

  • 目前尚不清楚在不同设备上为可连接广告传送扫描回调的差异是由于Android固件差异还是蓝牙硬件芯片组差异。

  • 似乎没有办法检测设备是否需要重新启动扫描以获得可连接广告的额外回调,因此如果您的目标设备种类繁多,您需要计划停止并重新开始扫描。

  • 使用 Android 的原始扫描 API 是了解 BLE 信标如何工作的好方法。但是使用 BLE 信标有很多复杂性(这只是一个例子),这就是为什么使用像 Android Beacon Library 这样的 SDK 是一个很好的选择,可以让你不费吹灰之力。

完全披露:我是 Locate app in the lead developer on the Android Beacon Library 开源项目的作者。

David - 您确定每个 non-connectable 广告都会调用扫描回调。我有一个小米 Redmi 3 和另一个 Nexus 5 phone 运行 Android 6.0。我有一个 BLE 传感器,每隔 1 分钟发送一次数据。这些 phone 作为中央 BLE 设备出现应该接收和处理来自传感器的数据。我可以从无线 (OTA) BLE 捕获设备看到传感器每 1 分钟发送一次数据。然而,两个 phones 似乎都以 1 分钟的间隔处理数据几分钟,但之后停止处理 4 - 6 分钟,然后开始处理 agenter code hereain。 phone 处理的时间间隔如下所示 1 分钟、2 分钟、3 分钟、8 分钟、9 分钟、10 分钟、11 分钟 因此,在以 1 分钟的间隔处理 3 个数据包后,phone 将停止处理 4 -6 分钟。

这是进行处理的代码。

public class BluetoothDataReader {
    private final Context context;

    public BluetoothDataReader(Context context) {
        this.context = context;
    }

    public void startReading() {
        BluetoothAdapter btAdapter = getBluetoothAdapter();
        if (btAdapter == null) return;

        BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .build();
        scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new ScanRecordReader());
    }

    public void uploadScanBytes(SensorDataUploader sensorDataUploader, int count) {
        BluetoothAdapter btAdapter = getBluetoothAdapter();
        if (btAdapter == null) return;

        BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .build();
 //       scanner.startScan(Arrays.asList(new ScanFilter.Builder().setDeviceAddress("26:50:26:50:26:50").build()), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
           scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
    }

    @Nullable
    private BluetoothAdapter getBluetoothAdapter() {
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        if(btAdapter == null){
            Log.i(BluetoothDataReader.class.getName(), "No bluetooth adapter available");
            return null;
        }

        if(!btAdapter.isEnabled()){
            Log.i(BluetoothDataReader.class.getName(), "Enable bluetooth adapter");
            Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            context.startActivity(enableBluetooth);
        }
        return btAdapter;
    }

    private class LimitedScanRecordReader extends ScanCallback {
        private final int limit;
        private final BluetoothLeScanner scanner;


        private int scanRecordRead = 0;
        private final SensorDataUploader sensorDataUploader;

        private LimitedScanRecordReader( SensorDataUploader sensorDataUploader, int limit, BluetoothLeScanner scanner) {
            this.limit = limit;
            this.scanner = scanner;
            this.sensorDataUploader = sensorDataUploader;
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {
//            if(scanRecordRead++ < limit) {
   //         if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
   //         if(result.getDevice().getAddress().equals("C0:97:27:2B:74:D5")) {

            if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
                long timestamp = System.currentTimeMillis() -
                        SystemClock.elapsedRealtime() +
                        result.getTimestampNanos() / 1000000;



                byte[] rawBytes = result.getScanRecord().getBytes();
                Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes));
                sensorDataUploader.upload(timestamp, rawBytes);
            }
//            }else {
//                scanner.stopScan(this);
//            }
        }
        public String byteArrayToHex(byte[] a) {
            StringBuilder sb = new StringBuilder(a.length * 2);
            for(byte b: a)
                sb.append(String.format("%02x", b & 0xff));
            return sb.toString();
        }

        public void onScanFailed(int errorCode) {
            Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
        }

        public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
            Log.i(DataTransferService.class.getName(), "Batch scan results");
        }
    }

    private class ScanRecordReader extends ScanCallback {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            byte []rawBytes = result.getScanRecord().getBytes();
            Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes ));
//            Map<ParcelUuid, byte[]> serviceData = result.getScanRecord().getServiceData();
//            for(ParcelUuid uuid : serviceData.keySet()) {
//                Log.i(DataTransferService.class.getName(), uuid.toString() + ":" +  byteArrayToHex(serviceData.get(uuid)));
//            }
//            Log.i(DataTransferService.class.getName(),result.toString());
        }
        public String byteArrayToHex(byte[] a) {
            StringBuilder sb = new StringBuilder(a.length * 2);
            for(byte b: a)
                sb.append(String.format("%02x", b & 0xff));
            return sb.toString();
        }

        public void onScanFailed(int errorCode) {
            Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
        }

        public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
            Log.i(DataTransferService.class.getName(), "Batch scan results");
        }
    }
}