frapontillo
frapontillo

Reputation: 10689

Avoid Service callback when Activity gets closed and re-opened

I have a LocalService that exposes a Binder with some APIs. I create a Service Listener, just like this:

if (dataServiceListener == null) {
    dataServiceListener = new DataServiceListener();
    mainActivity.getApplicationContext().bindService
        (new Intent(mainActivity, LocalService.class),
        dataServiceListener.svcConn, mainActivity.BIND_AUTO_CREATE);
}

After I call the method that the Binder in dataServiceListener exposes, I get the response in the dataServiceListener onResult() method. Up to this point, no kind of issues, everything is working. Some sort of problem occurs when I close the Activity that is waiting for the Service Listener callback and immediately reopen it. Even though I re-instantiate the dataServiceListener in onCreate(), I get two callbacks instead of one, the old one from the destroyed Activity and the latter (right) one; this way the results mix up on the UI. Is there a way to tell the Service or the Service Listener that when the activity finishes, the callbacks must be avoided. Or maybe even destroy the ServiceListener objects.

I think this is the issue that Mark L. Murphy (Commonsware) described in "The Busy Coder's Guide to Android Development":

The biggest catch is to make sure that the activity retracts the listeners when it is done.

How can I do this? Is there a way to get rid of the useless listeners when the activity finishes?

Thank you!

Upvotes: 1

Views: 4749

Answers (5)

Bruno Mateus
Bruno Mateus

Reputation: 1737

I had the same issue. I was working in a remote sevice using AIDL. I got this problem when i am trying do unregister my listeners using the remove method from ArrayList Collection inside a foreach loop, because I was not using asBinder in the comparision. Searching fora solution, I find out the RemoteCallbackList class in Android API. This class does exactly what i needed, and what i think you should do, on a easy way, taken all reponsabilites for the hard work that involves this task.

From the Android API:

To use this class, simply create a single instance along with your service, and call its register(E) and unregister(E) methods as client register and unregister with your service. To call back on to the registered clients, use beginBroadcast(), getBroadcastItem(int), and finishBroadcast().

Broadcast sample:

int i = callbacks.beginBroadcast();
while (i > 0) {
    i--;
    try {
        callbacks.getBroadcastItem(i).somethingHappened();
    } catch (RemoteException e) {
    // The RemoteCallbackList will take care of removing
    // the dead object for us.
   }
}
callbacks.finishBroadcast();

Upvotes: 9

frapontillo
frapontillo

Reputation: 10689

I finally solved the issue (and no, I haven't been working on it for so long :D).

The callback to the listener was made before the Fragment's onDestroy was called. So the boolean "dontupdate" value was never set to false. Overriding onBackPressed in the main activity solved the problem, as I invoked a destroy() method for each fragment that takes care of setting the boolean value to false.

Upvotes: 0

CommonsWare
CommonsWare

Reputation: 1007534

The code you show is for binding to a service. You do not show where you are registering a listener with that service. You apparently are, based upon your question and your reference to an onResult() method. Given the nature of your problem, I am going to guess that what you're doing is:

  1. Binding to the service in onCreate()
  2. In onServiceConnected(), you are calling some sort of setListener() method on the Binder

In that case, if we ignore configuration changes, the proper way to unwind matters would be to, in onDestroy(), call some removeListener() method on the Binder, then call unbindService().

Configuration changes, particularly in a pre-fragment world, make this complicated. It's the reason why this sample project (and the accompanying material in the book) is so icky. Binding is twitchy -- if you unbind from the old activity, and nothing else is keeping the service around, the service will shut down before the new activity gets a chance to bind. Binding is also state -- you cannot simply fail to unbind, lest you leak stuff.

So, the recipe becomes:

  1. Bind to the service in onCreate() using the Application Context
  2. In onServiceConnected(), call sort of setListener() method on the Binder
  3. In onRetainNonConfigurationInstance(), make note of the fact that you're undergoing a configuration change, and return some Object that has your Binder, your Listener, and all the rest of your state
  4. In onCreate(), use getLastNonConfigurationInstance() -- if it is null, proceed as normal, but if it is not null, hold onto that Binder and Listener and don't re-bind and re-register the listener
  5. In onDestroy(), if the flag from Step #3 above is false (i.e., we are not undergoing a configuration change), call some removeListener() method on the Binder, then call unbindService().

Using fragments with setRetainInstance(true) can probably simplify this some, though I have not worked through a sample for that yet.

Upvotes: 4

Nikolay Elenkov
Nikolay Elenkov

Reputation: 52956

Your activity has to register/unregister itself as the listener. You need to use the proper lifecycle callback methods, not onBackPressed(). Register onStart(), unregister onStop(). One way to do it is to make the listener a static member of your service, and provide static register/unregister methods. Then call those from your activity as appropriate.

Upvotes: 1

Vineet Shukla
Vineet Shukla

Reputation: 24031

I had this issue too. You need to release all the resources,listeners,threads from the service when it finishes.

Upvotes: 1

Related Questions