Android Application and AsyncTask basics
Submitted by charlie.collins on Sun, 02/14/2010 - 18:30
Tagged:
Programming on Android is pretty easy. At least it is at first, then the subtleties creep in -- like maintaining state, long running tasks, managing orientation changes, and so on. When you get to that stuff programming Android gets, well, not hard, but let's say more complicated.
The Android APIs are generally very nice, and there are ways to deal with just about any situation, it's just that they aren't always obvious. In this tutorial we will expand on the last one we worked on which covered SQLite and using a database. Here we are going to fill out more details, including adding an android.app.Application object to stash expensive objects and maintain state, adding a few more data operations, and putting said data operations onto a background Thread using AsyncTask (plus demonstrating saving instance state when the screen orientation is changed, and restoring same).
The complete application, which is intended to be very simple, will look like the screen shot below:
Sorry, images got lost when server crashed...
As you can see this example allows us to enter data, then press "Save," and that data is stored in a database (for details about how the database works see the previous example article, or the DataHelper code). Also, this example shows the current data from the database, and allows us to clear/delete it.
Again, this is a simplified example that is intended to give you an idea of what the possibilities and related problems with long running tasks are, and an overview of how to use the Application object. For more complete information, as always, see the Android docs (specifically for AsyncTask management stuff, and you might also want to check out Droid-Fu which is an excellent little utility library that helps, and goes much farther than this example, with AsyncTask management).
Ok, so on with our example. The complete code for this project, in case you just want to jump in that way, can be found here: http://totsp.com/svn/repo/AndroidExamples/trunk/.
NOTE The code for this example was updated for part 3 of this series and what is in SVN is now a little different from this example. See part 3 for details on what changed.
The first notable code with this update is the Main Activity, as seen below:
In this code we start with a typical onCreate method that inflates views and includes a Button click listener. But, you might also have noticed we are setting up a "MyApplication" object which is an instance of Application. The android.app.Application object is very useful, for many reasons. You can stick state there and share it between activities (you can do this with getters and setters, simple), and you can use it to instantiate objects that are needed by your app in more than one activity and are relatively expensive to create. The context of the Application object is "lives" as long as your application, and is different from the context of any single activity.
NOTE The Application object is not the only way to deal with non-persistent state that you need to SHARE across components. You can also use a static singleton for this. And, if your data is simple, you can of course just pass it via Intent extras. When the data is more complicated (beyond primitives), or larger, then Application/static singleton make sense. If the data needs to be persistent, then you'll want to use the filesystem or a database. Even if you do decide to use a static singleton, keep in mind that you should set it up and tear it down WITH THE APPLICATION CLASS. This handy combination (Application AND static singleton) allows easy/modular access to data elements, and still allows you to control the lifecycle. This recommendation is even in the Android documentation, though well hidden.
For our simple example we only have one activity, but we are using the application object to demonstrate what the possibilities are. To use the application object you need to extend it, and you need to add android:name="[ApplicationClassName] to the "application" element of your manifest. Our "MyApplication" object for this example is shown below:
Real simple. Here we *could* add getters and setters and store state, but basically we are using the longer lifecycle of the application object to create our DataHelper object and make it available to activities. Application let's you do much more than this, but the basics are state and longer life-cycle (see the docs for more info). Even though application is very useful, and very simple, it's often overlooked.
The next significant part of our new Main activity (going back to the previous code snippet above) is that it no longer calls data operations directly. Instead, it uses several inner AsyncTask classes to move these operations OFF of the main UI thread and onto another thread where they can run asynchronously.
This is HUGE. Ok, our sample application doesn't have anything fancy/long running to do, so it's overkill for the purposes of the example here, but in real life these data operations might be a lot more complicated and time consuming. And, you can use AsyncTask for anything, network operations are another common case.
AsyncTask replaces a lot of manual Handler/Thread/wiring that was needed in the original Android SDKs - it's a convenience for you. AsyncTask has an execute method that performs your long running task on a thread SEPARATE from the main UI thread automatically, and has clear pre and post methods where you CAN interact with the main UI thread too. From the example code, where we have 3 AsyncTask instances for our different data operations, the pattern should be clear (another approach might have been to have ONE task here, and to use the execute method arguments to determine the operation -- that would also allow us to manage the task state itself better, but that is getting beyond the scope of this example). One caveat with this example is that we are not properly using onPause (remember I said it was oversimplified). You should always clean up in onPause, and if you use an AsyncTask that shows a ProgressDialog, you'll want to make sure to dismiss it there (if it is showing, else it will leak).
NOTE Overall, the way we are handling the ProgressDialog in the AsyncTasks here is naive. It's better to use an external ProgressDialog and make use of the onProgressUpdate and publishProgress methods. That gives you more control over the dialog, and makes it easier to prevent errors when activities are restarted (orientation changes, for which you still need to use onPause and cleanup).
After the new task code we also have now implemented the onSaveInstanceState and onRestoreInstanceState methods of Activity. These are for instance state only (some call it "transient" state -- that may seem an odd turn of phrase, but this is state only associated with current instance of the activity, not persistent state, for that we are using the database). These allow us to save simple UI state, such as the contents of a text view, or what checkbox is checked, in case the activity is restarted suddenly, in mid course. This wouldn't really be all that important except for the fact that any orientation change does *exactly this* (yes, it restarts your activity and the life-cycle happens again). For this simple example we have only one input field ("input") to worry about, but you can see how this could be applied to any number of elements. And, in fact, this is unnecessary for a TextView because the framework does it for us with it's own onSaveInstanceState method. The trick is that you need to be aware of it and make sure you handle it in cases where it's not automatic (such as your own custom views). If you want to see how this works with a plain TextView, override the onSaveInstanceState method and do nothing, then change the orientation, and any text in the view will be lost.
That does it for this time. Our new AndroidExamples application now has a slightly more useful user interface that allows us to enter and delete data, and displays it all. Additionally we are now using the Application object to create our DataHelper, and our helper performs all of the data management tasks we need (creating the database, and basic CRUD operations). Also, every time we invoke data related methods we are now doing this with AsyncTask instances, so that these operations are not blocking the UI thread. This is still an oversimplified example, of course, and we are really only scratching the surface, but I do hope it might provide some insight as to where you can start to clean up your Android apps and deal with the sticky spots.
The Android APIs are generally very nice, and there are ways to deal with just about any situation, it's just that they aren't always obvious. In this tutorial we will expand on the last one we worked on which covered SQLite and using a database. Here we are going to fill out more details, including adding an android.app.Application object to stash expensive objects and maintain state, adding a few more data operations, and putting said data operations onto a background Thread using AsyncTask (plus demonstrating saving instance state when the screen orientation is changed, and restoring same).
The complete application, which is intended to be very simple, will look like the screen shot below:
Sorry, images got lost when server crashed...
As you can see this example allows us to enter data, then press "Save," and that data is stored in a database (for details about how the database works see the previous example article, or the DataHelper code). Also, this example shows the current data from the database, and allows us to clear/delete it.
Again, this is a simplified example that is intended to give you an idea of what the possibilities and related problems with long running tasks are, and an overview of how to use the Application object. For more complete information, as always, see the Android docs (specifically for AsyncTask management stuff, and you might also want to check out Droid-Fu which is an excellent little utility library that helps, and goes much farther than this example, with AsyncTask management).
Ok, so on with our example. The complete code for this project, in case you just want to jump in that way, can be found here: http://totsp.com/svn/repo/AndroidExamples/trunk/.
NOTE The code for this example was updated for part 3 of this series and what is in SVN is now a little different from this example. See part 3 for details on what changed.
The first notable code with this update is the Main Activity, as seen below:
public class Main extends Activity {
private static final String NAME = "NAME";
private EditText input;
private Button saveButton;
private Button deleteButton;
private TextView output;
private MyApplication application;
@Override
public void onCreate(final Bundle savedInstanceState) {
Log.d(MyApplication.APP_NAME, "onCreate");
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
// get "Application" object for shared state or creating of expensive resources - like DataHelper
// (this is not recreated as often as each Activity)
this.application = (MyApplication) this.getApplication();
// inflate views
this.input = (EditText) this.findViewById(R.id.in_text);
this.saveButton = (Button) this.findViewById(R.id.save_button);
this.deleteButton = (Button) this.findViewById(R.id.del_button);
this.output = (TextView) this.findViewById(R.id.out_text);
// initially populate "output" view from database
new SelectDataTask().execute();
// save new data to database (when save button is clicked)
this.saveButton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
new InsertDataTask().execute(Main.this.input.getText().toString());
Main.this.input.setText("");
}
});
// delete all data from database (when delete button is clicked)
this.deleteButton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
new DeleteDataTask().execute();
}
});
}
@Override
public void onSaveInstanceState(final Bundle b) {
Log.d(MyApplication.APP_NAME, "onSaveInstanceState");
if ((this.input.getText().toString() != null) && (this.input.getText().toString().length() > 0)) {
b.putString(Main.NAME, this.input.getText().toString());
}
super.onSaveInstanceState(b);
}
@Override
public void onRestoreInstanceState(final Bundle b) {
Log.d(MyApplication.APP_NAME, "onRestoreInstanceState");
super.onRestoreInstanceState(b);
String name = b.getString(Main.NAME);
if (name != null) {
// use onSaveInstanceState/onRestoreInstance state to manage state when orientation is changed (and whenever restarted)
// many views already do this automatically, so this is a silly example, but you could save your own state here too
this.input.setText(name);
}
}
private class InsertDataTask extends AsyncTask<String, Void, Void> {
private final ProgressDialog dialog = new ProgressDialog(Main.this);
// can use UI thread here
protected void onPreExecute() {
this.dialog.setMessage("Inserting data...");
this.dialog.show();
}
// automatically done on worker thread (separate from UI thread)
protected Void doInBackground(final String... args) {
Main.this.application.getDataHelper().insert(args[0]);
return null;
}
// can use UI thread here
protected void onPostExecute(final Void unused) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
// reset the output view by retrieving the new data
// (note, this is a naive example, in the real world it might make sense
// to have a cache of the data and just append to what is already there, or such
// in order to cut down on expensive database operations)
new SelectDataTask().execute();
}
}
private class SelectDataTask extends AsyncTask<String, Void, String> {
private final ProgressDialog dialog = new ProgressDialog(Main.this);
// can use UI thread here
protected void onPreExecute() {
this.dialog.setMessage("Selecting data...");
this.dialog.show();
}
// automatically done on worker thread (separate from UI thread)
protected String doInBackground(final String... args) {
List<String> names = Main.this.application.getDataHelper().selectAll();
StringBuilder sb = new StringBuilder();
sb.append("Names in database:\n");
for (String name : names) {
sb.append(name + "\n");
}
return sb.toString();
}
// can use UI thread here
protected void onPostExecute(final String result) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
Main.this.output.setText(result);
}
}
private class DeleteDataTask extends AsyncTask<String, Void, Void> {
private final ProgressDialog dialog = new ProgressDialog(Main.this);
// can use UI thread here
protected void onPreExecute() {
this.dialog.setMessage("Deleting data...");
this.dialog.show();
}
// automatically done on worker thread (separate from UI thread)
protected Void doInBackground(final String... args) {
Main.this.application.getDataHelper().deleteAll();
return null;
}
// can use UI thread here
protected void onPostExecute(final Void unused) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
// reset the output view by retrieving the new data
// (note, this is a naive example, in the real world it might make sense
// to have a cache of the data and just append to what is already there, or such
// in order to cut down on expensive database operations)
new SelectDataTask().execute();
}
}
}
In this code we start with a typical onCreate method that inflates views and includes a Button click listener. But, you might also have noticed we are setting up a "MyApplication" object which is an instance of Application. The android.app.Application object is very useful, for many reasons. You can stick state there and share it between activities (you can do this with getters and setters, simple), and you can use it to instantiate objects that are needed by your app in more than one activity and are relatively expensive to create. The context of the Application object is "lives" as long as your application, and is different from the context of any single activity.
NOTE The Application object is not the only way to deal with non-persistent state that you need to SHARE across components. You can also use a static singleton for this. And, if your data is simple, you can of course just pass it via Intent extras. When the data is more complicated (beyond primitives), or larger, then Application/static singleton make sense. If the data needs to be persistent, then you'll want to use the filesystem or a database. Even if you do decide to use a static singleton, keep in mind that you should set it up and tear it down WITH THE APPLICATION CLASS. This handy combination (Application AND static singleton) allows easy/modular access to data elements, and still allows you to control the lifecycle. This recommendation is even in the Android documentation, though well hidden.
For our simple example we only have one activity, but we are using the application object to demonstrate what the possibilities are. To use the application object you need to extend it, and you need to add android:name="[ApplicationClassName] to the "application" element of your manifest. Our "MyApplication" object for this example is shown below:
public class MyApplication extends Application {
public static final String APP_NAME = "AndroidExamples";
private DataHelper dataHelper;
@Override
public void onCreate() {
super.onCreate();
Log.d(APP_NAME, "APPLICATION onCreate");
this.dataHelper = new DataHelper(this);
}
@Override
public void onTerminate() {
Log.d(APP_NAME, "APPLICATION onTerminate");
super.onTerminate();
}
public DataHelper getDataHelper() {
return this.dataHelper;
}
public void setDataHelper(DataHelper dataHelper) {
this.dataHelper = dataHelper;
}
}
Real simple. Here we *could* add getters and setters and store state, but basically we are using the longer lifecycle of the application object to create our DataHelper object and make it available to activities. Application let's you do much more than this, but the basics are state and longer life-cycle (see the docs for more info). Even though application is very useful, and very simple, it's often overlooked.
The next significant part of our new Main activity (going back to the previous code snippet above) is that it no longer calls data operations directly. Instead, it uses several inner AsyncTask classes to move these operations OFF of the main UI thread and onto another thread where they can run asynchronously.
This is HUGE. Ok, our sample application doesn't have anything fancy/long running to do, so it's overkill for the purposes of the example here, but in real life these data operations might be a lot more complicated and time consuming. And, you can use AsyncTask for anything, network operations are another common case.
AsyncTask replaces a lot of manual Handler/Thread/wiring that was needed in the original Android SDKs - it's a convenience for you. AsyncTask has an execute method that performs your long running task on a thread SEPARATE from the main UI thread automatically, and has clear pre and post methods where you CAN interact with the main UI thread too. From the example code, where we have 3 AsyncTask instances for our different data operations, the pattern should be clear (another approach might have been to have ONE task here, and to use the execute method arguments to determine the operation -- that would also allow us to manage the task state itself better, but that is getting beyond the scope of this example). One caveat with this example is that we are not properly using onPause (remember I said it was oversimplified). You should always clean up in onPause, and if you use an AsyncTask that shows a ProgressDialog, you'll want to make sure to dismiss it there (if it is showing, else it will leak).
NOTE Overall, the way we are handling the ProgressDialog in the AsyncTasks here is naive. It's better to use an external ProgressDialog and make use of the onProgressUpdate and publishProgress methods. That gives you more control over the dialog, and makes it easier to prevent errors when activities are restarted (orientation changes, for which you still need to use onPause and cleanup).
After the new task code we also have now implemented the onSaveInstanceState and onRestoreInstanceState methods of Activity. These are for instance state only (some call it "transient" state -- that may seem an odd turn of phrase, but this is state only associated with current instance of the activity, not persistent state, for that we are using the database). These allow us to save simple UI state, such as the contents of a text view, or what checkbox is checked, in case the activity is restarted suddenly, in mid course. This wouldn't really be all that important except for the fact that any orientation change does *exactly this* (yes, it restarts your activity and the life-cycle happens again). For this simple example we have only one input field ("input") to worry about, but you can see how this could be applied to any number of elements. And, in fact, this is unnecessary for a TextView because the framework does it for us with it's own onSaveInstanceState method. The trick is that you need to be aware of it and make sure you handle it in cases where it's not automatic (such as your own custom views). If you want to see how this works with a plain TextView, override the onSaveInstanceState method and do nothing, then change the orientation, and any text in the view will be lost.
That does it for this time. Our new AndroidExamples application now has a slightly more useful user interface that allows us to enter and delete data, and displays it all. Additionally we are now using the Application object to create our DataHelper, and our helper performs all of the data management tasks we need (creating the database, and basic CRUD operations). Also, every time we invoke data related methods we are now doing this with AsyncTask instances, so that these operations are not blocking the UI thread. This is still an oversimplified example, of course, and we are really only scratching the surface, but I do hope it might provide some insight as to where you can start to clean up your Android apps and deal with the sticky spots.







Comments
Thanks!
Concurrency
That would create a
AsyncTask errors doInBackGround
Are you saying that the
the thing is when click on
Unknown paste ID, it may have
how can i retreive the saved
See the example?
Hi, I make use of your code
Because of several comments
Thank you for such AWESOME code
Destroy activity
thread replacement with async task
Nice