Reputation: 725
I'm developing an android app that works with Bluetooth LE. It works well on Android 4.3 or 5.0.1 and on various devices, but in Android M (6.0.1) it isn't stable. I wrote a lite sample project that has same behavior and issues, and below is my all code;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends ActionBarActivity {
private BluetoothAdapter mBluetoothAdapter;
private int REQUEST_ENABLE_BT = 1;
private Handler mHandler;
private static final long SCAN_PERIOD = 2000;
private BluetoothLeScanner mLEScanner;
private ScanSettings settings;
private List<ScanFilter> filters;
private BluetoothGatt mGatt;
private boolean connectionState = false, isScanning = false;
private TextView rssiTxt;
private RSSITimer rssiTimer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "BLE Not Supported",
Toast.LENGTH_SHORT).show();
finish();
}
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// VIEW
rssiTxt = (TextView) findViewById(R.id.rssiTxt);
(rssiTimer = new RSSITimer()).startTimer();
}
@Override
protected void onResume() {
super.onResume();
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
filters = new ArrayList<ScanFilter>();
}
scanLeDevice(true);
}
}
@Override
protected void onPause() {
super.onPause();
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
//scanLeDevice(false);
}
}
@Override
protected void onDestroy() {
rssiTimer.stopTimerTask();
if (mGatt == null) {
return;
}
mGatt.close();
mGatt = null;
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == Activity.RESULT_CANCELED) {
//Bluetooth not enabled.
finish();
return;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private void scanLeDevice(final boolean enable) {
if(isScanning) {
return;
} else {
isScanning = true;
}
if (enable) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
isScanning = false;
} else {
mLEScanner.stopScan(mScanCallback);
isScanning = false;
}
}
}, SCAN_PERIOD);
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLEScanner.startScan(filters, settings, mScanCallback);
}
} else {
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
Log.i("callbackType", String.valueOf(callbackType));
Log.i("result", result.toString());
BluetoothDevice btDevice = result.getDevice();
if ("PRODi".equals(device.getName()) || "PRODi(TEST)".equals(device.getName())) {
connectToDevice(btDevice);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
for (ScanResult sr : results) {
Log.i("ScanResult - Results", sr.toString());
}
}
@Override
public void onScanFailed(int errorCode) {
Log.e("Scan Failed", "Error Code: " + errorCode);
}
};
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("onLeScan", device.toString());
if ("PRODi".equals(device.getName()) || "PRODi(TEST)".equals(device.getName())) {
connectToDevice(device);
}
}
});
}
};
public void connectToDevice(BluetoothDevice device) {
if (mGatt == null) {
mGatt = device.connectGatt(getApplicationContext(), false, gattCallback);
scanLeDevice(false);// will stop after first device detection
return;
} else {
mGatt.connect();
scanLeDevice(false);// will stop after first device detection
}
}
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
Log.i("gattCallback", "STATE_CONNECTED");
gatt.discoverServices();
connectionState = true;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "STATE_CONNECTED", Toast.LENGTH_SHORT).show();
}
});
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.e("gattCallback", "STATE_DISCONNECTED");
connectionState = false;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "STATE_DISCONNECTED", Toast.LENGTH_SHORT).show();
}
});
break;
default:
Log.e("gattCallback", "STATE_OTHER");
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
List<BluetoothGattService> services = gatt.getServices();
Log.i("onServicesDiscovered", services.toString());
gatt.readCharacteristic(services.get(1).getCharacteristics().get(0));
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic
characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
gatt.disconnect();
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
final int rssi2 = rssi;
runOnUiThread(new Runnable() {
@Override
public void run() {
rssiTxt.setText("RSSI: " + rssi2);
}
});
}
};
/*TIMER for Read RSSI*/////////////////////////////////////////////////////////////////////////////////////////
class RSSITimer {
//we are going to use a handler to be able to run in our TimerTask
final Handler handler = new Handler();
private Timer timer;
private TimerTask timerTask;
private int interval = 1000;
public void startTimer() {
//set a new Timer
timer = new Timer();
//initialize the TimerTask's job
initializeTimerTask();
//schedule the timer, after the first 1000ms the TimerTask will run every [INTERVAL]ms
timer.schedule(timerTask, 1000, interval); //
}
public void stopTimerTask() {
//stop the timer, if it's not already null
if (timer != null)
{
timer.cancel();
timer = null;
}
}
public void initializeTimerTask() {
timerTask = new TimerTask() {
public void run() {
handler.post(new Runnable() {
public void run() {
if (mBluetoothAdapter.isEnabled()) {
if (connectionState == false) {
scanLeDevice(true);
} else {
mGatt.readRemoteRssi();
}
}
}
});
}
};
}
}
}
It have this various issues;
I know that DeadObjectException
occured in BluetthGatt
during this issues and readRemoteRssi()
returned false, but can't solve it.
I know that Android 6 have some changes in BLE and I searched a lot and Studied Documents, but no result! But I found that when I set the interval
to 100
instead of 1000
, it works BETTER, but not Absolutely (on Android 6.0.1). Yes, I am sure that interval
and how to implement it (Runnable, AlarmManager, TimerTask, ScheduledExecutorService, ...) are effective to that, but WHY?! It's my qustion.
Have anybody idea? Thanks and Sorry about my English.
UPDATE: Here is a link to my log, when I put appliction under some pressure. Sorry, it is bigger than stackoverflow capacity!! I see clearly that Bluetooth restarted in it...
Upvotes: 2
Views: 1836
Reputation: 727
Enable "Bluetooth HCI snoop log" and use Wireshark to analyze packets. This way you can see Bluetooth stack events. Bluetooth HCI snoop log Menu
Upvotes: 1
Reputation: 1215
It looks like you are scanning and connecting/reading/writing at the same time. On Android this leads to stability problems and the bluetooth stack can crash. Try to go easier on the bluetooth stack, never scan and connect at the same time, only connect to one device and wait until the callbacks returned before doing the next thing, you should even add some breaks between your ble tasks. We had a lot of issues when doing too much on the ble stack at the same time, like a crashing bluetooth stack, not able to connect to devices anymore until you restart the device and so on.
Upvotes: 0
Reputation: 18472
don't connect again, after disconnect
Execute device.connectGatt(getApplicationContext(), true, gattCallback); instead of device.connectGatt(getApplicationContext(), false, gattCallback); to automatically reconnect.
don't work when application goes to background
You need to have a foreground service or something else in your application process that prevents your process from being killed. This is because the BLE apis are based on callbacks and not intents.
bluetooth chip reset between disconnect and trying to connect again!! don't receive callback of readRemoteRssi() after few seconds
Definitely a bug in Android. File a bug report. Could you post the FULL logcat output (not only the output filtered from your application)?
don't receive callback of readRemoteRssi() after few seconds
No idea about this one...
Upvotes: 0