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?
Different Android devices behave differently when scanning for connectable BLE advertisements. On some devices (e.g. the 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. Other devices (e.g. the Nexus 5) provide a scan callback every single advertisement regardless of whether it is connectable.
The Locate app you mention uses 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:
This issue of getting multiple scan callbacks for connectable devices applies to both the 4.x and 5.x scanning APIs.
It is unclear whether the difference in delivering scan callbacks for connectable advertisements on different devices is due to Android firmware differences or bluetooth hardware chipset differences.
There doesn't seem to be a way to detect if a device requires a scan restart to get additional callbacks for connectable advertisements, so if you are targeting a wide variety of devices, you need to plan to stop and restart scanning.
Using Android's raw scanning APIs is a great way to understand how BLE beacons work. But there are lots of complexities with working with BLE beacons (this is just one example) which is why using a SDK like the Android Beacon Library is a good choice to keep you from pulling your hair out.
Full disclosure: I am the author of the Locate app in the lead developer on the Android Beacon Library open source project.
David - Are you sure that scan callback gets called for every non-connectable advertisement. I have a Xiaomi Redmi 3 and another Nexus 5 phone running Android 6.0. I have a BLE sensor that at every 1 minute interval sends the data. These phones appearing as central BLE device should receive and process the data from the sensor. I can see from an Over the Air (OTA) BLE capture device that it the sensor is sending data every 1 minute. However both phones seems to process data for few minutes at 1 minute interval but after that stop processing for 4 - 6 minutes and then start processing agenter code here
ain.
Time interval of phone processing on looks like this
1 min, 2 min, 3 min, 8min, 9min, 10min, 11 min
So after processing 3 packets at 1 minute interval, either phone will stop processing for 4 -6 minutes.
Here is code that does the processing.
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");
}
}
}
来源:https://stackoverflow.com/questions/29731176/ble-scanning-callback-only-get-called-several-times-then-stopped