Question: What is the best practice for reporting progress/complete from long running task to an Activity? And what to do when the progress/complete report happens while the Activity is in the background/orientation changes?
Real life example:
An Activity makes a network call getting data from a server (this could take 10+ sec).
When this network call is finished, the Activity should be notified and the Activity should show that the network call is finished.
This is easy to implement as long as the app stays open. My problem is, what to do if the network call is finished while the app is in the background (activity will miss any callbacks).
I have been looking at the following ways to do this, but I can not decide what to do:
Service that spawns a thread where the network call is executed. The Service is bound to the Activity. On network call finish, Service callbacks to the Activity. If Activity is in background when the Service makes a callback (therefore the Activity misses the callback), should the Activity poll the Service for saved data?
IntentService that broadcasts the data when network call is finished (what to do if Activity misses this broadcast because it is in background?)
AsyncTask, but this is bad when Activity is in background etc.
How should I approach this problem?
I've solved this problem using HeadlessFragments as its called. This blog post explains in detail how to implement it and the concerns you mentioned are handled by it.
EDIT:
For the questions in the comment:
To your specific question, about callbacks being lost when Activity is in the background, the answer is no. Being in "background" means that Activity is still alive. From the link I posted, the callback, which is the Activity itself, is removed when its detached from the Activity i.e when the Activity is destroyed. So, if your Activity is in the background and not destroyed, it'll still get the callback and do whatever you do in that callback. Although Android can kill Activities that are paused, in which case, your Activity will be destroyed and you won't get the callback. In such case, you can either save the data you get from server in a persistent storage, like SQLite, and prevent making another network call or make the network call when the Activity is created which will ensure that whenever the Activity is created, you'll have data to display (given of course, the call goes through).
The use of Fragment was specifically to handle the configuration change you mentioned in your question. The running task is still being done by the AsyncTask and not by the Fragment. The Fragment only holds a reference to the object. So, I'd argue about it not being a "best practise".
Related
Just trying to clarify my understanding of how an IntentService is managed by the OS once terminating states have been reached. By terminating, I mean when the current activity is destroyed or the app process is killed, as per the following documentation:
https://developer.android.com/guide/components/activities/activity-lifecycle
Given the comment
Also, an IntentService isn't affected by most user interface lifecycle events, so it continues to run in circumstances that would shut down an AsyncTask
at https://developer.android.com/training/run-background-service/create-service;
I feel as if:
1) A started IntentService is unaffected by the activity lifecycle. Is this correct?
2) If (1) is true, will it continue to run indefinitely even after a terminating state is reached, up to some point that it either stops itself or the OS decides to stop it?
In my particular situation, I'm using an IntentService during app startup to query APIs, grab content, and then add a new (landing) Page to the Xamarin.Forms navigation stack (this would be equivalent to starting a new activity).
This leads me to my next question...
3) What happens if the app is already in a terminated state when it comes time to the IntentService creating a new Activity? Surely the Activity can't be added to the navigation stack as it no longer exists once the app is terminated?
Yes, a started IntentService is unaffected by the Activity Lifecycle. Actually, all Services outside of bound Services are unaffected by the Activity Lifecycle.
An IntentService will continue until it reaches completion of it's work, the application is destroyed, or if the System decides to kill the Service due to the changes in the Android 8.0 background Service rules.
Your use of terminated state is too broad... If the Application is already terminated, then nothing will happen because the IntentService would have been terminated already too. If it's the Activity that launched the IntentService that was terminated, then nothing happens, since by default, an IntentService has nothing to do with Activities, even if it's the one that started it.
For the last question, it really depends on how you choose to communicate the result of IntentService to an Activity.
If you're using a BroadcastReceiver, then nothing will happen because an IntentService will fire the broadcast without any problems, but the Activity won't be able to receive the results since it's terminated.
But if you're simply creating a new Activity, then you can simply use startActivity() with the result data added to the Intent. Though, I doubt the user will be happy to see an Activity suddenly open on the screen when they're no longer in your app. Starting a new Activity has nothing to do with a previous Activity, since any instance of a Context can start an Activity.
Honestly, based on your question, it sounds like you're very concerned with an IntentService and it's connection with the Activity that started it. If that's the case, you really shouldn't be using an IntentService, since that's not really it's purpose. It's not meant to have a connection with an Activity. It's simply meant to do work and finish.
Instead, a bound Service would be a better option since it has a direct connection with the Activity that started it.
Am having an activity that will start service, service will download something from internet and i have to update activity UI.
but consider below cases
If my activity is alive i have to update activity UI without broadcast receiver, What is the best way to do this.?
If my activity is not alive and when it comes alive/foreground then i have to update UI with service result, How to do this.? (without saving into DB)
Let me drop my suggestions point by point,
Yes you can achieve it by EventBus (or) interface. Coming to eventbus, it is similar to broad cast receiver.
When the activity is not alive, you are fetching the data and when coming to alive you had to update the UI. In order to do that somehow you had to save the value somewhere either internally or externally (OR) you had to fetch the values from the internet whenever the activity comes to alive.
Given the use case where the user has to perform an online transaction, eg online payment, using the app.
This should be done without blocking the UI, so i was going to use AsyncTask. The problem is with the following scenario:
- the user rotates the phone or gets an incoming call in the middle of the transacion, thus causing the activity to be destryed.
If I understood correctly the asynctask now has a reference to a stale object. So after the transaction is done, there is no way to inform the user about the result. Is it?
Should be use a service instead?
What is a Service?
Most confusion about the Service class actually revolves around what it is not:
A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).
Should you use a service or a thread?
A service is simply a component that can run in the background even when the user is not interacting with your application. Thus, you should create a service only if that is what you need.
If you need to perform work outside your main thread, but only while the user is interacting with your application, then you should probably instead create a new thread and not a service. For example, if you want to play some music, but only while your activity is running, you might create a thread in onCreate(), start running it in onStart(), then stop it in onStop(). Also consider using AsyncTask or HandlerThread, instead of the traditional Thread class. See the Processes and Threading document for more information about threads.
Remember that if you do use a service, it still runs in your application's main thread by default, so you should still create a new thread within the service if it performs intensive or blocking operations.
What causing the activity to be destryed when user rotates the phone?
If the configuration of the device (as defined by the Resources.Configuration class) changes, then anything displaying a user interface will need to update to match that configuration. Because Activity is the primary mechanism for interacting with the user, it includes special support for handling configuration changes.
Unless you specify otherwise, a configuration change (such as a change in screen orientation, language, input devices, etc) will cause your current activity to be destroyed, going through the normal activity lifecycle process of onPause(), onStop(), and onDestroy() as appropriate. If the activity had been in the foreground or visible to the user, once onDestroy() is called in that instance then a new instance of the activity will be created, with whatever savedInstanceState the previous instance had generated from onSaveInstanceState(Bundle).
Possible workaround to solve AsyncTask interruption due to Activity recreation:
In some special cases, you may want to bypass restarting of your activity based on one or more types of configuration changes. This is done with the android:configChanges attribute in its manifest. For any types of configuration changes you say that you handle there, you will receive a call to your current activity's onConfigurationChanged(Configuration) method instead of being restarted. If a configuration change involves any that you do not handle, however, the activity will still be restarted and onConfigurationChanged(Configuration) will not be called.
I'm developing an app with a service that forwards calls to a web-service, and a few activities that place those calls. The activities need to process the results of those calls. For example, I have a writeComment method on the service, that accesses the web-service and returns some information about the newly written comment.
Right now I let the Activity take care of all the threading. The Activity binds the service, and then uses an AsyncTask that calls the bound service's writeComment method.
All works well as long as the Activity isn't stopped while the AsyncTask is running. If it does (easily happens when flipping the phone), the AsyncTask dies a violent death when trying to update the UI in onPostExecute. I'm not entirely sure how to fix this - I do need to let the user know the server has been updated.
If I go the other way around, and register a callback with the Service, I'm still a bit stump, because I need to notify the Service the Activity has changed - I need to tell it not to notify me in the first Activity's onDestory, and reregister in the second Activity's onCreate. And I need to handle the case where the asynchronous task completes after onDestroy and before onCreate.
What is considered Best Practice in this case?
Thanks,
Itay.
My intuition tells me to let the service handle the threading. Services are far less transient (although still transient to some degree) than activities and therefore you'll have less issues of threads trying to interact with a Context (be it an Activity or a Service) that's no longer there. Have you looked at the IntentService class? It handles a lot of the threading for you.
In my app, I have a long-running service and Activities that need to render data in the service. The service also pings the Activities when there is a change but the Activity can also query the service. The way I approached this was two-fold.
Firstly, I bind my activity to the Service in order to send messages from Activity to service.
Secondly, the Service sends notifications with Broadcasts and the Activity listens for those broadcasts. I set that up in the Activity onResume and tear it down in the onPause. I think this is the part that you're missing.
I have the following framework for my application:
1. a Network thread that runs in the background (a queue) for issuing request and get async responses. The thread is started and stopped in the Application Object so it's leaving through out the whole application.
2. a DataManager which is also a member of Application and has different DataManagers for the data types i retrieve from the network. the data manager itself is the listener for the responses from the network so it's safe until the application itself dies.
3. this is the problematic part. Some of my Adapters and part of my Activities are DataListeners for my DataManagers, that means that the data manager keeps a reference to them.
When a phone call or some other phone event occurs i've noticed that the activity is usually in paused and not destroyed and so receives my events, which is ok. the problem starts when landscape\portrait is changed. since i keep a referenced to the activity in an Application bound object, the activity can't be destroyed on one hand, BUT the event is still getting to the listener, only the wrong one...
Basically i can fix that issue by removing the listener in onDestroy and retaining configuration boolean to tell me that request was allready issues and i just need to put a listener and try to retrieve the data from the data manager.
However :-) i was wondering how android handles this cases usually, if for example this was a Service running. or if the Service is a local Service that used Bound and passed on the Activity as a Listener to the network Event, the same things happen, untill the listener is not removed the Activity is leaked and lives on, but without it, no way to get callbacks from the network...
an Intent requires serilaztion and deserilazation of data which can be heavy (Bitmaps for example?)
And anywa, asuming i send an intent on each respose i get, how do i get the intent to the Activity (i know of getIntent, but if i get another one , not related, do i get it as an 'event' ?)
From what I gather it's customary on Android to remove yourself from listener lists when the activity is destroyed. It's kinda error-prone, but I think it's the generally accepted way to do it.
You could imagine your service accepts only one listener, which may or may not fit your case, and when the activity restarts its registering with the DataManager would overwrite the old activity which would in turn be garbage collected. The drawback is, you don't free the activity memory if it is destroyed but the service lives on, so it's probably better to just remove the activity from listeners.
Android development is rather different from other platforms (e.g. BlackBerry). I'm not able to give you a quick silver bullet solution, however here are my thoughts on this:
Some of my Adapters and part of my
Activities are DataListeners for my
DataManagers, that means that the data
manager keeps a reference to them.
OS kills Activities according to their lifecycle. So you should avoid keeping a handle to an Activity in another object which is supposed to live after the Activity is destroyed by OS. Otherwise you'll get memory leak.
Also keep in mind Application sublass instance does not always live for the whole application session (a session from a user perspective). If your app goes in the background, for example, due to an incoming phone call, then your entire process can be killed. See details here. As soon as you Application sublass contains some state which is not persisted if process is killed you may mistakenly expect your handles to point to some non-null entities. However after going to foreground (and process restore) those may just be nulls because a new instance of Application sublass has been created by OS.
Ok, so let me describe the problem and the solution i found in more details.
The problem:
I have a Service\Network Thread that needs to notify Activities that sent requests through it that either request or Error has arrived in an Async way. Using Listener Pattern requires me to set listener before or when i send a request like so:
mNetService.setRequest(request, this);
where this is Activity that implements my listener Interface.
But doing it this way requires me to remove the listener from the service in onDestroy and returning the listener, if i ever sent a request back in onCreate\onResume, but the response can also arrive exactly when the activity is not listening (landscape\portrait event) which requires me to keep the Error\Response in the service until some1 picks it up and resets it.
The solution i found:
using Broadcasts and BroadcastReciever.
this is only part of the solution but it let you have a listener to broadcasts (that can be specific for a certain class type meaning Activity) and action.
Since all of my Activities inherit a base Activity class i've made they all have a BroadcaseReciever inner class that listens on certain action in it's filter.
is i enable the listening in the C'tor of my Activity the listener will be registered in onResume and deregister in onPause.
If the listener gets onRecieved event it will call a method in the Activity (which i can override in my specific activty) and pass it the Intent i got which can contain all the data from the response.
The only missing part is what happens if the Activity dies for a second and only then the broadcast arrives ? ah, that's a problem, so android intorduces Sticky Broadcasts that stays there untill you remove them with removeStickyBroadcast(Intent), so when is ent broadcast from my service i send Sticky broadcast, when the Activity gets my Broadcast it removes it so it wont stay around and mislead the activity about new response that arrived.
The only problem with it is if i send a request, don't wait for the response and goes to the next Activity right away, in this case when i'll go back to that Activity it will think it got the response. Didn't find a proper solution to that just yet. But it's better then my previous solution.