Avoid Service callback when Activity gets closed and re-opened - android

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!

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();

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:
Binding to the service in onCreate()
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:
Bind to the service in onCreate() using the Application Context
In onServiceConnected(), call sort of setListener() method on the Binder
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
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
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.

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

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.

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.

Related

Events being received multiple times - Greenrobot eventbus

I am using Greenrobot's EventBus in my app, and it works fine.
However, if I press the back button to close the app, then restart the app instantly I seem to receive the event twice. If I then do so again, I will receive it three times and so on.
I am checking with logs and debugging to see if I have multiple instances of any classes, or if I am registering multiple times, but I can't see any extra classes and using isRegistered returns false.
Any ideas?
Thanks
Are your register/unregister calls paired correctly? E.g. if you register() in Activity.onResume(), are you calling unregister() in Activity.onPause().
Closing all activities does not kill your process. I.e. all registered classes are still there, you have to explicitly clean up and unregister from the event bus, or reuse them when the Activity comes back.
This is old, but just in case anyone has this problem also: Tread lightly when using EventBus inside dynamically generated things like Fragments or other classes; I didn't really understand why they were posting to the EventBus more than once, but I think it had to do with this (I had more than one dynamically generated Fragment). It worked normally once I put the register(), unregister(), onEvent() into the parent Activity code (which conveniently also uses onPause() and onResume()).
Same thing happening in my case when I am using the
EventBus.getDefault().postSticky(new Event("Hii !"));
for sending the event. The event is received multiple times when I come to that activity. So I fixed this by removing the event after receiving in onEvent method. This solved my problem. Used: removeStickyEvent(object)
#Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(Event event) {
/* Do something */
EventBus.getDefault().removeStickyEvent(event);
}
The problem was not that the event was actually fired multiple times, but that the handler was invoked multiple times. As seen in the code above, the bus.register method is called everytime I create the object; because of the activities lifecycle, this happened multiple times, causing the handler to be invoked multiple times.
I had a specific case that I want to share. Maybe it helps someone else.
While we are using a parent activity for all of our Activities in our project, we register and unregister EvenBus for each activity inside the parent class.
In one of our activities, we were calling EventBas before invoking the previous activity's EventBus. Then we had twice trigger

Android Fragment : which life cycle method to use for web service call

I am developing an application in which several fragments are involved.
In each fragment I have to call web service to fetch data.
Currently I am calling web service from onCreateView() method of Fragment. Issue i am getting that whenever web service call is in progress and if device orientation is changed then new web service call starts invoking.
I think this might be because onCreateView() method gets called on configuration change.
How can I solve this. and which Life cycle method should I use to call web service so that it will be get called only once
I have resolved this by following workaround
Create an operation identifier for each web service call method. E.g. for example "Authentication" for login call
Create one object of ArrayList say currentTasks
ArrayList<String> currentTasks = new ArrayList<String>();
In every method where I am calling web service, check if operation identifier of corresponding method is already present in ArrayList. If not then start operation.
String operationId = "Authentication";
if(currentTasks.indexOf(operationId) == -1)
{
<do web service call operation here>
currentTasks.add(operationId);
}
Method in which above operation's response is receiving, remove operation identifier from ArrayList
if(currentTasks.indexOf("Authentication") != -1){
currentTasks.remove("Authentication");
}
This will ensure that call will not go to web method which is currently in progress.
I know this is not the best way to achieve it and this might not the best practice to follow but for now this works for me.
If your web service code is getting restarted due to a device orientation change, than you're probably using an AsyncTask. This is a common problem with AsyncTasks and you have several ways to solve it.
One common workaround is to wrap your AsyncTask around an invisible Fragment and make sure this fragment does not get destroyed and recreated again during an orientation change. Check out this tutorial on how to do it: Retaining Objects Across Config Changes
A simple solution would be to use a static variable pointing to asynctask. Create and call an AsyncTask if variable is not null.
Regards,
Prateek

LoaderCallbacks.onLoadFinished not called if orientation change happens during AsyncTaskLoader run

Using android-support-v4.jar and FragmentActivity (no fragments at this point)
I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.
As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.
My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.
I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.
Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?
Nikolay identified the issue - Thank you.
I was calling initLoader fron onResume(). The Android documentation states:
"You typically initialize a Loader within the activity's onCreate()
method, or within the fragment's onActivityCreated() method."
Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.
I moved my initLoader call to onCreate() and that solved my problem.
I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.
It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:
void doLoaderStart() {
if (mLoadersStarted) {
return;
}
mLoadersStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
// WTF: Why aren't we calling doStart() here?!
}
mCheckedForLoaderManager = true;
}
Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().
You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.
This really seems like a bug to me.
* This is the code path even if you aren't using fragments, so don't get confused by that.

Which lifecycle event is best to register/unregister listeners?

I have manager classes that take an activity as a listener. I use the managers to do threaded calls, work etc and then call back to the listener(activity) when things are done, need changed and so on.
I want to register and unregister the activity as a listener when it is no longer visible. This will prevent unwanted changes from happening (like dialogs appearing when the activity is no longer visible).
My question is, what lifecycle events are best to do this registering. I started with onPause() and onResume() which worked well except when I had an activity that was doing stuff in onActivityResult(). Since onActivityResult() gets called before onResume() my managers are not always registered in time.
Do I need to register in onResume() AND onActivityResult() or is there a better way to approach this?
An alternative approach may be to postpone the processing currently done in onActivityResult() until after the listeners are registered in onResume().
Possible ways of doing this include posting to the message queue, e.g. using a Handler, setting a Runnable object to be called by onResume, or simply storing the result data received by onActivityResult().
This would also ensure that the activity really has come to the foreground when the listener methods are called.
onResume() and onPause() are the best for this. The onDestroy(), per the documentation, is not guaranteed to be invoked though this is a favorite for many people, so stick with the pauses and resumes.
You can have the handle of the current Activity in the Manager class. Register its presence on onCreate() and unregister it on either onCreate() by some other Activity, or onBackPressed() of the current Activity.
On a related note, I would recommend an MVC (or similar) architecture where the controller has awareness of the view's status (the controller can track the onCreate() and onBackPressed() of each Activity).

Android Activity to Service Connection

Activities have handler methods on them like onClick for various UI events. How does one guarantee that the service connection exists for these methods? The service connection has an onServiceConnected method for initialization after the service has connected. However this seems like not the best place for UI initialization. I want to avoid onClick(View v) { service.getValue() } from being a null service reference. On the other hand I don't want the UI rendering to depend on waiting for a service reference. It seems that onCreate() is the right place for setContentView() etc. On the other hand onCreate is initializing handlers which might not be using a valid service reference yet. How does one guarantee that a service reference is valid for UI handler methods. Or is this a good reason not to use a service reference at all? Whats the best practice here?
You have to signal to the Activity that Service is ready.
The simplest way would be to set a flag e.g. serviceAvailable = true, in ServiceConnection.onServiceConnected(). Then every time you need service you check this flag.
In the UI event listener, you could check if the service reference is null before using it:
if (service != null)
{
service.getValue();
}

Categories

Resources