androidbluetooth-lowenergyibeaconindoor-positioning-system

Ble scanning callback only get called several times then stopped


I have 2 phones with Android 5.0.2, they both installed the latest Radius Beacon's App: Locate Beacon, meanwhile, I turned on 2 IBeacon sender, and can see the RSSI keep changing in both phone with the App.

But when I tried to write some sample code to simulate above situation, I found the ble scan callback always stop get called after called 2 or 3 times, I initially suspect the 'Locate Beacon' may use different way, so I tried with 2 kinds of API, one is for old 4.4, and another is the new way introduced in android 5, but both the same behavior(but all running on android 5).

the 4.4 one:

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);
}}

And the 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);
}}

They are very simple and just show a counter in UI, proved finally always stopped at 2 or 3.

I've played this ble advertising receiving before on a SamSung note 2 with android 4.4 device, it works perfectly, the callback get called every second. then anyone can help? why Radius' Locate Beacon works well here?


Solution

  • Different Android devices behave differently when scanning for connectable BLE advertisements. On some devices (e.g. the old Nexus 4), the scanning APIs only get one callback per scan for transmitters sending a connectable advertisement, whereas they get a scan callback for every advertisement for non-connectable advertisements. Newer devices (e.g. the Nexus 5 and most built after 2015) provide a scan callback every single advertisement regardless of whether it is connectable.

    The Locate app you mention and its replacement app Beacon Scope use the open source Android Beacon Library to detect beacons. It is built on top of the same scanning APIs you show in your question, but it gets around this problem by defining a scan period (1.1 seconds by default in the foreground) and stopping and restarting a scan at this interval. Stopping and restarting the scan causes Android to send a new callback.

    A few other notes here:

    Full disclosure: I am the author of the Locate app and the lead developer on the Android Beacon Library open source project.