48 votes

Android : Comment délier un service en toute sécurité

J'ai un service qui est lié au contexte de l'application de cette manière :

getApplicationContext().bindService(
                    new Intent(this, ServiceUI.class),
                    serviceConnection,
                    Context.BIND_AUTO_CREATE
            );

protected void onDestroy() {
            super.onDestroy();                  
            getApplicationContext().unbindService(serviceConnection);
        }

Pour une raison quelconque, parfois le contexte de l'application ne se lie pas correctement (je ne peux pas corriger cette partie), cependant dans onDestroy() je fais unbindservice qui génère une erreur

java.lang.IllegalArgumentException: Service not registered: tools.cdevice.Devices$mainServiceConnection.

Ma question est : Y a-t-il un moyen d'appeler unbindservice en toute sécurité ou de vérifier s'il est déjà lié à un service avant de le délier ?

Merci d'avance.

0 votes

0 votes

J'ai déjà parcouru cela auparavant, mais cela ne résout pas mon problème, disons simplement que j'appelle UnBindService sans appeler BindService(), comment gérez-vous l'erreur sans que l'application FC?

0 votes

Pouvez-vous simplement attraper l'exception et continuer ?

71voto

Andrey Novikov Points 2607

Essayez ceci :

boolean isBound = false;
...
isBound = getApplicationContext().bindService( new Intent(getApplicationContext(), ServiceUI.class), serviceConnection, Context.BIND_AUTO_CREATE );
...
if (isBound)
    getApplicationContext().unbindService(serviceConnection);

Note:

Vous devriez utiliser le même contexte pour lier un service et délier un service. Si vous liez un Service avec getApplicationContext(), vous devriez aussi utiliser getApplicationContext().unbindService(..)

1 votes

J'ai suivi votre réponse et je reçois toujours la même erreur. La réponse de @Yury corrige le problème.

0 votes

Les deux réponses sont correctes : celle d'Andrey Novikov et @Yury

0 votes

Il me semble que cette solution pourrait provoquer une fuite du service lié, car je viens de constater que le déliement doit être appelé sur le même contexte. Ainsi, tester sur un contexte différent pourrait entraîner des résultats étranges, ayant effectivement été lié deux fois à un service particulier (si cela est possible du tout ?)

8voto

Yury Points 10837

Ici vous pouvez trouver une explication claire et des codes sources sur la façon de travailler avec les services liés. Dans votre cas, vous devez remplacer les méthodes (onServiceConnected et onServiceDisconnected) de l'objet ServiceConnection. Ensuite, vous pouvez simplement vérifier la variable mBound dans votre code.

0 votes

Je crains que l'exemple donné soit pour les services locaux, pas pour les services de processus à distance.

5 votes

Contrairement à celui local, la liaison aux services distants nous donnera onServiceDisconnected() rappel si le processus du service cible meurt et onServiceConnected() sera de nouveau délivré quand il est redémarré. Si vous comptez sur ces rappels pour le drapeau mBound, vous obtiendrez une fuite de connexion au service si la tentative de déliaison est faite entre onServiceDisconnected() et onServiceConnected() lorsque le mBound serait false.

0 votes

Pour résoudre ce problème, ne définissez pas mBound sur false dans onServiceDisconnected. Et s'il est nécessaire de suivre les statuts connectés et déconnectés, introduisez peut-être une autre variable mConnected.

5voto

Emil Blablablå Points 51

Embrasser exactement ce que Andrey Novikov a proposé n'a pas fonctionné pour moi.

getApplicationContext().unbindService(serviceConnection);

Avec:

unbindService(serviceConnection);

3 votes

Qu'est-ce que serviceConnection?

5voto

Blundell Points 28342

J'ai trouvé qu'il y a deux problèmes. Tenter de lier plus d'une fois et aussi tenter de délier plus d'une fois.

Solution:

public class ServiceConnectionManager implements ServiceConnection {

    private final Context context;
    private final Class service;
    private boolean attemptingToBind = false;
    private boolean bound = false;

    public ServiceConnectionManager(Context context, Class service) {
        this.context = context;
        this.service = service;
    }

    public void bindToService() {
        if (!attemptingToBind) {
            attemptingToBind = true;
            context.bindService(new Intent(context, service), this, Context.BIND_AUTO_CREATE);
        }
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        attemptingToBind = false;
        bound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        bound = false;
    }

    public void unbindFromService() {
        attemptingToBind = false;
        if (bound) {
            context.unbindService(this);
            bound = false;
        }
    }

}

2 votes

Cela fonctionne, mais il semble vraiment maladroit de devoir maintenir 'bound' manuellement - il semble que cela devrait être une variable interne accessible.

0 votes

@DavidDoria Cela peut sembler maladroit mais c'est vraiment ainsi qu'Android montre comment le faire : developer.android.com/guide/components/bound-services.html

0 votes

Cette réponse a fonctionné pour moi car j'ai réalisé que j'appelais, stupidement, myService.unbindService(myServiceConnectionManager) au lieu d'appeler context.unbindService(...)

3voto

Rohit Singh Points 1934

Pourquoi obtenons-nous cette erreur?

Lorsque vous essayez de désenregistrer un service qui n'est pas enregistré.

Quels sont quelques exemples courants?

  • Associer et désassocier un service avec un Contexte différent.
  • appeler unbind(mserviceConnection) plus que bind(...)

Le premier point est explicite. Explorons plus en profondeur la deuxième source d'erreur. Déboguer vos appels bind() et unbind(). Si vous voyez ces appels dans cet ordre, votre application finira par obtenir l'IllegalArgumentException.

entrez une description d'image ici

Comment pouvons-nous éviter cela?
Il y a deux façons que vous devriez considérer pour lier et délier un service dans une Activity. Les documents Android recommandent que

  • Si vous voulez interagir avec un service uniquement lorsque l'Activity est visible, alors

bindService() dans onStart() et unbindService() dans onStop()

Votre Activity {

    @Override
    public void onStart(){
       super.onStart();
       bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onStop(){
       super.onStop();
       unbindService(mConnection);
    }

} 
  • Si vous voulez interagir avec un service même lorsque une Activity est en arrière-plan, alors

bindService() dans onCreate() et unbindService() dans onDestroy()

Votre Activity {

    @Override
    public void onCreate(Bindle sis){
       super.onCreate(sis);
        ....
        bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }         

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X