web-dev-qa-db-fra.com

Comment forcer par programmation la découverte de services Bluetooth à faible consommation d'énergie sur Android sans utiliser le cache

J'utilise Android 4.4.2 sur un Nexus 7 . J'ai un périphérique Bluetooth à faible énergie dont les services changent lors du redémarrage . L'application Android appelle BluetoothGatt.discoverServices (). Toutefois, si Android interroge uniquement le périphérique une fois pour découvrir les services, les appels suivants de discoverServices () génèrent les données mises en cache du premier appel, même entre les déconnexions . en interrogeant le périphérique . Existe-t-il un moyen programmé d'obliger Android à actualiser son cache de services blu sans désactiver/activer l'adaptateur?

36
monzie

Je viens d'avoir le même problème. Si vous voyez le code source de BluetoothGatt.Java, vous pouvez voir qu'il existe une méthode appelée refresh ()

/**
* Clears the internal cache and forces a refresh of the services from the 
* remote device.
* @hide
*/
public boolean refresh() {
        if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
        if (mService == null || mClientIf == 0) return false;

        try {
            mService.refreshDevice(mClientIf, mDevice.getAddress());
        } catch (RemoteException e) {
            Log.e(TAG,"",e);
            return false;
        }

        return true;
}

Cette méthode efface réellement le cache d'un périphérique Bluetooth. Mais le problème est que nous n’y avons pas accès. Mais en Java, nous avons réflexion , nous pouvons donc accéder à cette méthode. Voici mon code pour connecter un périphérique Bluetooth actualisant le cache.

private boolean refreshDeviceCache(BluetoothGatt gatt){
    try {
        BluetoothGatt localBluetoothGatt = gatt;
        Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
        if (localMethod != null) {
           boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
            return bool;
         }
    } 
    catch (Exception localException) {
        Log.e(TAG, "An exception occured while refreshing device");
    }
    return false;
}


    public boolean connect(final String address) {
           if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");
                return false;
        }
            // Previously connected device. Try to reconnect.
            if (mBluetoothGatt != null) {
                Log.d(TAG,"Trying to use an existing mBluetoothGatt for connection.");
              if (mBluetoothGatt.connect()) {
                    return true;
               } else {
                return false;
               }
        }

        final BluetoothDevice device = mBluetoothAdapter
                .getRemoteDevice(address);
        if (device == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }

        // We want to directly connect to the device, so we are setting the
        // autoConnect
        // parameter to false.
        mBluetoothGatt = device.connectGatt(MyApp.getContext(), false, mGattCallback));
        refreshDeviceCache(mBluetoothGatt);
        Log.d(TAG, "Trying to create a new connection.");
        return true;
    }
78
Miguel

Utilisez les éléments suivants avant d’analyser le périphérique:

if(mConnectedGatt != null) mConnectedGatt.close();

Cela déconnectera le périphérique et videra le cache. Vous pourrez ainsi vous reconnecter au même périphérique.

2
Gyapti Jain

Dans certains appareils, même si vous déconnectez le socket, la connexion ne se terminera pas à cause du cache. Vous devez déconnecter le périphérique distant à l'aide de la classe BluetoothGatt. Comme ci-dessous

BluetoothGatt mBluetoothGatt = device.connectGatt(appContext, false, new BluetoothGattCallback() {
        };);
mBluetoothGatt.disconnect();

Remarque: cette logique a fonctionné pour moi dans les appareils basés en Chine.

2
Satheesh

En effet, la réponse de Miguel fonctionne. Pour utiliser refreshDeviceCache, je réussis avec cet appel:

// Attempt GATT connection
public void connectGatt(MyBleDevice found) {
    BluetoothDevice device = found.getDevice();
    gatt = device.connectGatt(mActivity, false, mGattCallback);
    refreshDeviceCache(gatt);
}

Cela fonctionne pour les OS 4.3 à 5.0 testés avec les périphériques Android et iPhone.

2
Thomas

Voici la version de Kotlin avec RxAndroidBle pour l'actualisation:

class CustomRefresh: RxBleRadioOperationCustom<Boolean> {

  @Throws(Throwable::class)
  override fun asObservable(bluetoothGatt: BluetoothGatt,
                          rxBleGattCallback: RxBleGattCallback,
                          scheduler: Scheduler): Observable<Boolean> {

    return Observable.fromCallable<Boolean> { refreshDeviceCache(bluetoothGatt) }
        .delay(500, TimeUnit.MILLISECONDS, Schedulers.computation())
        .subscribeOn(scheduler)
  }

  private fun refreshDeviceCache(gatt: BluetoothGatt): Boolean {
    var isRefreshed = false

    try {
        val localMethod = gatt.javaClass.getMethod("refresh")
        if (localMethod != null) {
            isRefreshed = (localMethod.invoke(gatt) as Boolean)
            Timber.i("Gatt cache refresh successful: [%b]", isRefreshed)
        }
    } catch (localException: Exception) {
        Timber.e("An exception occured while refreshing device" + localException.toString())
    }

    return isRefreshed
  }
}

Appel effectif:

Observable.just(rxBleConnection)
    .flatMap { rxBleConnection -> rxBleConnection.queue(CustomRefresh()) }
    .observeOn(Schedulers.io())
    .doOnComplete{
        switchToDFUmode()
    }
    .subscribe({ isSuccess ->
      // check 
    },
    { throwable ->
        Timber.d(throwable)
    }).also {
        refreshDisposable.add(it)
    }
0
Kebab Krabby