I have a bluetooth headset which is paired with my Nexus 5X (running Android 7.1) and I would like to connect to a GATT Server of the headset. I tried it with the following
Had the same problem, but waiting 600 ms wasn't enough. This is probably due to the BLE module used. I fixed the problem by calling my method
discoverServices();
after calling
device.connectGatt(this,false,gattCallback)
I'm basically just calling discoverServices every 5 seconds (this is arbitrarily chosen)
private void discoverServices() {
if(!gattConnected) { //just a boolean
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
gatt.discoverServices();
}
});
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
discoverServices();
}
}, 5000);
}
}
In the onServicesDiscovered(...) method of my gattCallback I make gattConnected true. This worked for me.
In fact,I solve this problem by Execution the method mBluetoothGatt.discoverServices() several times(10 or more),
int i = 10;
while (i > 0)
{
if (!mIsBLE_Finded) //如果服务发现失败,继续执行discoverServices方法
{
i--;
mBluetoothGatt.discoverServices();
System.out.println("BLEService-->" + "尝试次数:" + i);
}
else //在10次的尝试中,存在某次服务发现成功了
{
i = -1;
}
}
BLE on Android can be a little finicky.
Make sure you are calling mBluetoothGatt.discoverServices() on the UI thread.
if(newState == STATE_CONNECTED) {
Log.d(TAG, "Device connected");
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
boolean ans = mBluetoothGatt.discoverServices();
Log.d(TAG, "Discover Services started: " + ans);
}
});
}
Also try making BluetoothGatt gatt
a field variable instead of a local variable.
If you are doing any significant work, try using a library that masks all of the idiosyncrasies so you can focus on the high level logic. https://github.com/Polidea/RxAndroidBle.
Here is an example of how to read a characteristic.
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));
}, this::onReadFailure);
Or with Java 7 syntax
connectionObservable
.flatMap(new Func1<RxBleConnection, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(RxBleConnection rxBleConnection) {
return rxBleConnection.readCharacteristic(characteristicUuid);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<byte[]>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
onReadFailure(e);
}
@Override
public void onNext(byte[] bytes) {
readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));
}
});
This may help
I will explain in two steps: connecting and discovering services
connecting: connect from mainthread
set auto-reconnect to false
if version greater than or equals to M, set Transport type
else directly use reflection and handle it properly
Handler(applicationContext.mainLooper).post {
Log.d(TAG, " Post is called inside mainlooper")
mBluetoothGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, " Is Or Greater than M $mBluetoothDevice")
mBluetoothDevice!!.connectGatt(this, false,
onGhattListener, TRANSPORT_LE)
} else {
Log.d(TAG, " Less than M")
try {
Log.d(TAG, " Trying TRANPORT LE with reflection")
val m = mBluetoothDevice!!.javaClass.getDeclaredMethod("connectGatt", Context::class.java, Boolean::class.javaPrimitiveType, BluetoothGattCallback::class.java, Int::class.javaPrimitiveType)
m.isAccessible = true
val transport = mBluetoothDevice!!.javaClass.getDeclaredField("TRANSPORT_LE").getInt(null)
m.invoke(mBluetoothDevice, this, false, onGhattListener, transport) as BluetoothGatt
} catch (e: Exception) {
e.printStackTrace()
Log.d(TAG, " Catch to call normal connection")
mBluetoothDevice!!.connectGatt(this, false,
onGhattListener)
}
}
Log.d(TAG, "mBluetooth gatt is $mBluetoothGatt")
mBluetoothGatt?.let {
refreshDeviceCache(mBluetoothGatt!!)
}
}
discover services : in onGhattListener , if device is connected fire discoverServices()
from main thread
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int,
newState: Int) {
Log.d(TAG, "onConnectionStateChange $gatt and status $status and newstate $newState")
when (newState) {
BluetoothGatt.STATE_CONNECTED -> {
Handler(Looper.getMainLooper()).post {
gatt.discoverServices()
}
}
BluetoothGatt.STATE_DISCONNECTED -> {
}
BluetoothGatt.STATE_CONNECTING -> {
}
BluetoothGatt.STATE_DISCONNECTING -> {
}
}
}
this may solve your problem
call refresh method with reflection
fun refreshDeviceCache(gatt: BluetoothGatt): Boolean {
try {
val localMethod = gatt.javaClass.getMethod("refresh")
if (localMethod != null) {
return localMethod.invoke(gatt) as Boolean
}
} catch (localException: Exception) {
Log.e(TAG, "An exception occured while refreshing device")
localException.printStackTrace()
}
return false
}
Something that has been really useful for me is to wait for about 600ms after the connection has been established and then start the service discovery.