I have two static tables with about 500 records each which provide lookup material for some ListViews in my app. When the app first starts and the tables are created I run a process to populate the tables which takes less than a minute but I've been looking to run the process in background using Async Task with a progress dialog letting the user know what is happening.
My concern is that while the process is running and the data is being added and then the user tilts the phone the process will cancel. What would be the state of my database then? Is Async Task the best solution for this or should I use Threading?
So when you rotate or change the orientation of the phone, the activity is the only thing destroyed. You don't necessarily have to get rid of the async task. In fact it will live on. Just don't let another task come in and work on it ad-hocly.
So if you want to have your activity act as if upon rotating that you can start right back up, where you left off, there is a method called onRetainNonConfigurationInstance(). It's basically the method that stashes objects which can't be parceled like in saveInstanceState()
So the idea being:
public void onCreate(Bundle a) {
...
AsyncTask myTask = getNonConfigurationInstance();
if (myTask == null) {
myTask = new AsyncTask();
myTask.execute();
}
...
}
public Object onRetainNonConfigurationInstance() {
return myTask;
}
This will keep the async task running, and when you get your onCreate called after the rotation you just pick it back up and do what needs to be done.
One thing to be conscious of is the progressView. It will have to be destroyed and reinitialized to the new state. Also the overall dismissing of it and showing it in the first place should be done outside the AsyncTask. But nothing is to say that the AsyncTask can't call some callback that you always set in your onCreate() so that it will notify to tell to update the UI or play a sound of completion, etc.
You could also decide to handle the configuration changes on your own through the use of the android:configChanges in your manifest.
You then implement the onConfigurationChanged method and perform any actions inside.
See the developer doc.
When a configuration change occurs at
runtime, the activity is shut down and
restarted by default, but declaring a
configuration with this attribute will
prevent the activity from being
restarted. Instead, the activity
remains running and its
onConfigurationChanged() method is
called.
Related
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.
In my login Activity, just after the use log in, I sync data and create several tables in sql.
If the user open another app before this process finish, I want to cancel it and delete tables.
I thought I could put it in onPause or onStop, but then, when the process finishes and go to the dashboard activity, onPause or onStop should be called, and I don't want this.
I assume the solution is simple, but I can't figure out how to solve it.
Any idea?
The logic behind your solution is straightforward - create a flag (say for example shouldRollback) to true when you start your transactions. Once your transaction are successfully completed, you set this flag to false. In your onPause() method, simply check for this flag. If it is true, you know you need to roll back the changes.
I would only suggest this if this is your requirement. Personally, I would process all database operations in a background thread. The thread can run in the background even if other applications are opened. There will be too much overhead (if you have a large number of transactions) to keep rolling back inserts. You could use the transaction methods from the SQLiteDatabase object to automatically roll back changes if they were not completed successfully.
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.
From the Activity, I am creating a Handler to fire off my AsyncTask every 45 seconds in order to refresh the content of my ListView's DataAdapter. The AsyncTask works great and keeps the user informed on the progress through ProgressUpdates and Toast messages.
Since the thread's doInBackground is fire and forget and not re-usable, I am having to create a new instance of the AsyncTask from my Hander that is firing off every 45 seconds. The problem is when the screen is rotated and and then I get concurrent messages going off because the Hander was recreated and created a new instance of the AsyncTask, so the friendly user progress through ProgressUpdates and Toast messages is overwhelming and makes utilizing the ListView difficult.
And please don't suggest this as a solution: android:screenOrientation="portrait" is not an option.
For something that has to run so frequently, should I just be using a custom Thread and not the AsyncTask class? ToDo: Not shown, I have to update the Adapter later from the Sensor's onSensorChanged event to update bearings on for each location in the ListView, I was going to run that on a separate AsyncTask class because I don't need to notify the user everytime the device bearing has changed.
Since the AsyncThread cannot be reused, am I doing this all wrong? In short, what is the best way to have the Activity refresh the ListView and keeping off the UI thread when doing so?
The problem is when the screen is rotated and and then I get concurrent messages going off because the Hander was recreated and created a new instance of the AsyncTask.
Reason quoting from API Activity - 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.
So every object has a activity-scope life cycle (i.e. Handler, AsyncTask and etc. defined within your activity class) is suffered by this activity recreation. However, you can bypass this activity recreation, as stated in the later paragraph of Activity - Configuration Changes section:
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.
Not related to topic, but as a good practice, you should always destroy used object (Handler, AsyncTask and etc.) properly when activity is about to finish (i.e. in onDestroy() method).
For something that has to run so frequently, should I just be using a custom Thread and not the AsyncTask class?
AsyncTask is pretty handy but not suit for periodic task, I would use ScheduledExecutorService or TimerTask in this case, check out my answer here for sample code.
Can you please post a bit of your code ? It may be useful to understand where your problem is.
As york has pointed it out, you should probably use TimerTask. It seems that it suit better with what you are trying to do.
If it is the creation of a new instance of the Handler that create the probleme you can try something like this :
private Handler mHandler = null;
#Override
public void onCreate(Bundle _savedInstanceState) {
super.onCreate(_savedInstanceState);
setContentView(R.layout.my_layout);
if (mHandler == null) {
// TODO create your handler here
}
}
EDIT :
You can test _savedInstanceState == null too.
_savedInstanceState is used to save the state of the activity so turning the phone shouldn't be a problem anymore.
However, if you leave the activity and then go back to it, it will create a new handler (except if you instanciate it as a static variable).
I have a START and STOP button in the main screen of an App. there are some GUI and threads that are instantiated when I click on START. When I click on stop, I want everything to be stopped and the activity should come back to its origin state. To the state that is exactly same like when launched (when we tapped on App icon in mobile).
Is it possible to do this? I tried with finish() , this killed the app and exited . I don't want to exit from main screen. rather, on clicking STOP I want app to come back to origin or born state. Thanks.
How are you running your threads? Are they vanilla threads or subclasses of AsyncTask?
If these are instances of an AsyncTask object, you can use the cancel() method to cancel it and then inside your doInBackground() method, you could check the isCancelled() method to see if it has indeed been canceled, and then exit gracefully.
Pseudo code below:
private YourTask taskRef;
public void btnStartHandler() {
taskRef = new YourTask();
taskRef.execute();
}
public void btnStopHandler() {
taskRef.cancel();
}
and then, in your AsyncTask:
public Void doInBackground(Void... arg0) {
// Background loop start
if (this.isCancelled()) {
return;
}
// Background loop continue...
}
If you're using threads, you can interrupt them and catch the exception and handle it there. Furthermore, you could create a method that you call from onCreate() called initApp() or something that initializes everything. You could also use that initApp() from the STOP button click handler to reset values back to startup defaults.
You can restart the activity with finish() and then call startActivity(getIntent());. This will effectively restart your activity and put it in its default state, no matter how it was started.
Before doing that make sure to cancel all threads or AsyncTasks as TJF suggested (you can and should do this in the onDestroy overload).
For more info about restarting an activity, and a discussion about pros and cons, see this question: Reload activity in Android