Has anyone any idea on how to solve the generic problems related to starting a task in background from an activity and when the task is finished posting the result to the activity that created ? (the activity might get destroyed in the meantime due to a orientation change ,or receiving a call , or might be in the process of destroying and recreation)
Depending of what you are doing you might consider using a Service as well.
Related
I've read
"How to know activity has been finished?
" and "Proper way to know whether an Activity has been destroyed"
but none of them got real answers
I've a background task updating a screen with it's progress
The user has a button to cancel the background task at any moment, and if he does that the background task will be stoped and activity will be finished...
BUT as all of this happen in an asynchronous enviroment the following situation may happen:
1- the background task stacks some notification to update progress activity
2- the user cancel the background task
3- the background task is stopped (i mean, stops having progress) and activity is finished (activity.finish())
4- the previous stacked updates are delivered to the activity which tries to perform some update on its fields and lead to error
I would like an "oficial android approach" better than having a boolean which is set to true during onDestroy()
Since you're finishing the activity by calling finish() then I think checking isFinishing() before updating the UI would work in your use case.
For more advanced use cases I suggest you should look into RxJava for doing asynchronous background tasks that are tightly coupled with activity lifecycle.
I know you are using it quite well with your Non-UI codes in AsnycTask but I am just wondering if there is any kind of problem while using AsyntTask? I am not having any code which produce the problem. But I am just curious to know Any bad experience if you have with AsnycTask and would like to share it.
Memory Leak :
Even though activity is destroyed, AsyncTask holds the Activity's reference since it has to update UI with the callback methods.
cancelling AsyncTask :
cancelling AsyncTask using cancel() API will not make sure that task will stop immediately.
Data lose :
When screen orientation is done. Activity is destroyed and recreated, hence AsysncTask will hold invalid reference of activity and will trouble in updating UI.
Concurrent AsyncTasks: Open Asynctask.java go to line number 199, it shows you can create only 128 concurrent tasks
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
Rotation: When Activity is restarted, your AsyncTask’s reference to the Activity is no longer valid, so onPostExecute() will have no effect.
Cancelling AsyncTasks: If you AsyncTask.cancel() it does not cancel your AsyncTask. It’s up to you to check whether the AsyncTask has been canceled or not.
Lifecycle: AsyncTask is not linked with Activity or Fragment, so you have to manage the cancellation of AsyncTask.
There are some workarounds to solve above issues for more details have a look at The Hidden Pitfalls of AsyncTask
I just want to share the information that if you are using Asynctask, it will keep on doing its work even of the activity does not exist.
So in case you have asynctask which starts in onCreate() of the activity, and you rotate the device. At each rotation, a new activity is created with a new instance of Asysntask. So many requests will be send over the network for same task.In this way, a lot of memory will be consumed which effects the app performance resulting in crashing it. So to deal with it Loaders(Asynctask Loaders) are used.
For more info check the video:
Loaders
I have noticed that if I try to rotate the device while an AsyncTask is running the App crashes.
This seems caused by the fact Activity is destroyed and recreated in the rotation.
To avoid this I want to capure the rotation event and execute it only if there aren't active AsyncTasks... if there are AsyncTasks that are active, the app should pause the rotation and execute it when these are completed.
How I could do this?
Its not possible to pause screen rotation. You can only stop it entirely using configChanges in your activity manifest entry (but that is bad practice). What you should do is to put your async task in retained fragment. Until recently you could use Activity.getLastNonConfigurationInstance and Activity.onRetainNonConfigurationInstance to keep reference to AsyncTask between Activity being destroyed and recreated but now its deprecated. But you can still use it.
read here for more information: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()
Also:
if I try to rotate the device during AsincTask the App crashes
this actually should not happen, it is possible that you keep reference to your Activity in AsyncTask and use it after it is destroyed. This is called reference leak. To avoid it keep reference to your Activity in WeakReference, also if your AsyncTask is an inner class, then make it static. If it is possible, destroy your asynctask in Activity.onDestroy - by cancelling it, in async task check if it is cancelled and stop processing. If you use it to download things then consider retained fragment or IntentService.
Re-design your app and use a fragment with setRetainInstance(true). Put your AsyncTask inside the fragment. This is the right thing to do in this case.
Do not do that. Instead, handle everything correctly through orientation changes.
A worker Fragment with an AsyncTask in it is a good solution.
The fragment stays across orientation changes so the task does not get interrupted and always reports to the correct Activity via Fragment's getActivity().
This tutorial shows exactly how to do this
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
You could check if your AsyncTask is running and, if it is, prevent the application from rotating. Something like this would do the trick:
if (myAsyncTask.getStatus() == AsyncTask.Status.RUNNING)
{
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
else
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
The above code will check if you AsyncTask is running and, if it is, get the current screen orientation and lock it. You could add a similar check to unlock rotation if the AsyncTask is no longer running.
I'm sure this question gets asked a lot, but I'm looking for the simplest solution with Android best practices in mind (no hacky manifest that tries to keep a single Activity instance). Also, I'm not looking for a retain Fragment solution.
I'm looking for the simplest way in an Activity to initiate a background task and provide a callback function. If the Activity gets re-created (config change), then I want the old activity to release the reference, and attach a callback to the new Activity instance.
Lastly, I don't want to have to perform the operation again. Meaning, if it's some HTTP resource, it should be cached so that the operation is not run again wastefully.
Thanks!
First of all you need to decouple a task execution and activities(UI). To achieve this you can use events which UI sends to some task executor. The basic idea looks like this:
when method onResume was called you register you activity to receive events.
then later somewhere you execute the task to load some data from network.
next you have three options:
a) the task finished and your activity exists. The task sends event "i'm_finished". And your activity receives it.
b) the task is finished but your activity was destroyed/hided. When activity was hided the system called onStop method of your activity where you unregister your activity to receive new event. So event was not delivered.
c) the task is not finished and new activity was created. New activity checks if data is available or is downloading now in onResume method. If it's downloaded then show it, if not then wait.
The detailed explanation is here: http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html.
The most popular event libraries for Android: Otto and EventBus
I my experience the simplest way is actually to use an AsyncTask (http://developer.android.com/reference/android/os/AsyncTask.html). However, that might blow up after a config change. So you should probably attach that AsyncTask to a "HeadlessFragment". For the Fragment you can enable "setRetainInstanceState" so the fragment will not be recreated. Then after config change you can just attach to that fragment again.
If I have an AsyncTask started in an Activity by user interaction. The AsyncTask, when finished, will modify the UI and execute a Toast. Let's say that the user exits the Activity before the AsyncTask has finished. Can this cause problems as in Exceptions: I.e. could it happen that an UI element pointer goes null and that when the AsyncTask finishes it could cause runtime exceptions?
As it is now I've done a design where the Application class handles the AsyncTask and notifies the Activity through a BroadcastReceiver to do UI tasks if Activity still is around (i.e. more of an Observer pattern). Is this a "safer" design?
/ Henrik
I believe this does cause a problem. If the activity that created the AsyncTask is not around anymore, the an exception is thrown because the parent handler is not there anymore. The correct approach is to keep the reference of the AsyncTask in that activity, and capture onPause() event. In the pause event, I would cancel the AsyncTask and clean up if there is anything that needs to be cleaned up.
To answer your second question, it all depends on what is the requirement. If the requirement is for that task to still be around then yes you can attach the AsyncTask to the application. But it sounds like there is something that might be not correct here. You said if Activity still is around. If you don't need the task once the activity has disappeared then you might as well go with my original approach which is cancel the task and throw it away when the activity is paused.
Also, one final note. If you keep a reference to the activity around even after the activity has stopped, you will have a memory leak because that activity still has a reference that cannot be cleaned up until the task has completed.
This article sounds similar to what you are doing. If you really want to keep the task around then this seems like a good solution. I also found Android AsyncTask Context Terminated that might help you.