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.
Related
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.
in my activity, i have an asynctask that downloads a series of images...(it may take some time depending on the size of the images)... before i execute the asynctask, i display a progress dialog in onPreExecute and a notification (when user clicks on notification it is redirected to my activity with the progress dialog). when my asynctask completes, in onPostExecute, i remove the dialog and the notification.. i handle screen orientation by using onRetainNonConfigurationInstanceto save my asynctask so that when screen is rotated, i check if task is still running and i display the progress dialog if it is still running in onCreate
Problem : sometimes: my asynctask downloads only 1 file and it seems that it gets killed...(no exception in logcat)... as a result, my notication is always there and my progress dialog also... and they remain there indefinitely....
i have tried the solution by Jeff Axelrod there: How can I ensure an AsyncTask is completed before my activity is killed?:
It looks like if I override the onPause() event in my activity, and from within the overridden onPause(), I call cancel(false) on the AsyncTasks, then the activity is not destroyed until the AsyncTasks are completed.
seems to do the trick but problem is that my onPostExecute is not called anymore; all images download fine but as onPostExecute is not executed, notification and progress dialog still remain there forever.
waiting for your solutions guys! i read use asyntask only for short task; will the use of thread and handler solves my problem? will android kills my activity or thread if the latter is not finished??
Best way how to handle Asynctask is described in this article.
In short, the idea is to keep AsyncTask in fragment with setRetainInstance(true); these will keep You AsyncTask alive all time user is in activity holding this fragment and won't be destroyed on configuration change (orientation change).
If You need Your AsyncTask to run after user leaves Activity, for example goes to next Activity but You wish download to continue You should use services.
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.
I use AsyncTask in several activities.
i know how to Override onBackPress().
But when an AsyncTask is running i want to give the user the ability to go back even though the asynctask is still running.
How can I do this?
Also does an asynctask still run when activity that initiated it is closed before it finishes?
AsyncTasks run on until they complete or are cancelled. So in your case (the user presses Back and you end the main activity), the AsyncTask will carry on.
If you did want to end the AsyncTask from onBackPressed, the best you can do is to call
myAsyncTask.cancel(true);
but all that does is call the task's onCancelled() which can set a cancel flag that can be checked (usually) in doInBackground() - and if it is set the task can return.
Note that using cancel(true) does not stop the task. The docs talk about "interrupting" the task for the true case, but all they mean is that onCancelled will be called while doInBackground is running - at which point the task has an opportunity to set a flag that doInBackground can inspect.
For the true case, if the task does not implement onCancelled, and/or it does not set/inspect a flag, the task will complete doInBackground, but onPostExecute(Object) is never invoked - which is equivalent to the false case.
The async task will continue to run even if your application was closed. You have to be careful with that since your task will leak your context (keep it in memory) if it has a reference to it (so the activity) as long as your task is still running. You can avoid that by referencing your activity by a WeakReference.
You can stop a running task with cancel(true). A cancel will let the task finish its doInBackground but will never call onPostExecute. You could interrupt your background routine by checking isCanceled() and so return earlier since the task was killed.
I deleted one half of my post!
I did so confuse interrupt with a thread stop! Using true will of course NOT stop the actual thread. Thanks a lot to Torid and his answer which proved me wrong! Big lesson lernt, thanks for that!
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.