Simple use of AsyncTask and ProgressDialog in Android
The practice of creating applications that are responsive to user actions suggests that all heavy operations should be performed in a separate thread, informing the user in one way or another about their progress.
Android contains a lot of ways to organize this approach, but one of the most convenient is the use of AsyncTask and ProgressDialog.
This couple perfectly solves the problem, but begins to bring unbearable pain when the amount of Activity with this logic exceeds one, which leads to a repetition of the control code, and even greater pain when the application must support a change in screen orientation.
AsyncTask
For those who are not familiar with AsyncTask, I’ll explain that this is a special abstract class that provides a set of methods for implementation:
- onPreExecute to host initialization code (UI thread)
- doInBackground for placing heavy code that will be executed in another thread
- onProgressUpdate for progress reporting (UI thread)
- onPostExecute to handle the result returned by doInBackground (UI thread)
- isCancelled to find out if anyone canceled a task
- publishProgress to translate the progress message into a UI thread followed by an onProgressUpdate call
The essence of the problem
Using the mentioned classes is not difficult, just a couple of snippets is enough for the code to work as it should and ProgressDialog starts informing about the progress of the task. But, as you know, the devil is in the details, so you just need to change the screen orientation, as the dialogue disappears, as well as the result of a long, but incredibly responsible operation.
The reason is the Activity life cycle: changing the screen orientation is interpreted as a configuration change, which leads to the re-creation of the Activity. You can, of course, disable this mechanism by setting a tag
android:configChanges="orientation"
for Activity and defining your own code, which, if necessary, will make the necessary changes. But this will be an unreasonable implementation.The solution is to create a special class for managing the Activity-AsyncTask-ProgressDialog bundle, let's call it AsyncTaskManager.
Activity
So, ideally, our Activity should do only five things (the code from the example project):
- Create AsyncTaskManager in onCreate Method
mAsyncTaskManager = new AsyncTaskManager(this, this);
- Delegate AsyncTaskManager task recovery from state
mAsyncTaskManager.handleRetainedTask(getLastNonConfigurationInstance());
- Create a specific task and give it to AsyncTaskManager
mAsyncTaskManager.setupTask(new Task(getResources()));
- Delegate AsyncTaskManager saving tasks to state
return mAsyncTaskManager.retainTask();
- Handle asynchronous task completion
public interface OnTaskCompleteListener {
void onTaskComplete(Task task);
}
In the parameter of the method, the task whose execution has been completed will be transferred.
AsyncTaskManager
AsyncTaskManager should be responsible for the correct operation of all components, which boils down to a list of the following tasks:
- Create a dialog during initialization
- When you receive a task in management, run it
- Display task status in dialog
- Disconnect from the task after saving it to the state and reconnect when restoring
- Cancel the task when canceling the dialog
- Close dialog at task completion
- Notify Activity of task completion or cancellation
public interface IProgressTracker {
void onProgress(String message);
void onComplete();
}
Implementation:
@Override
public void onProgress(String message) {
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
mProgressDialog.setMessage(message);
}
@Override
public void onComplete() {
mProgressDialog.dismiss();
mAsyncTask.setProgressTracker(null);
mTaskCompleteListener.onTaskComplete(mAsyncTask);
mAsyncTask = null;
}
Joining the task:
mAsyncTask.setProgressTracker(this);
Detach from task:
mAsyncTask.setProgressTracker(null);
mAsyncTask = null;
Cancel dialog:
@Override
public void onCancel(DialogInterface dialog) {
mAsyncTask.setProgressTracker(null);
mAsyncTask.cancel(true);
mTaskCompleteListener.onTaskComplete(mAsyncTask);
mAsyncTask = null;
}
AsyncTaskManager acts as a kind of key that connects and disconnects a working task to a possibly recreated instance of Activity. In addition, he takes over and hides the logic of working with ProgressDialog.
AsyncTask
For the task, in addition to implementing the main methods, an implementation of a method is required that will help connect / disconnect it with AsyncTaskManager:
public void setProgressTracker(IProgressTracker progressTracker) {
mProgressTracker = progressTracker;
if (mProgressTracker != null) {
mProgressTracker.onProgress(mProgressMessage);
if (mResult != null) {
mProgressTracker.onComplete();
}
}
}
As can be seen from the above code, the task saves the calculated result and the last progress message, and, depending on the state, calls one or another tracker method (AsyncTaskManager).
Thus, even if the task completes before the Activity is recreated, it will receive a notification about the completion of the task.
Result
Now you can fearlessly twist the phone in your hands - all tasks will be processed correctly.
Using such a manager significantly reduces the amount of code in Activity and allows you to reuse this functionality in a project. I developed and successfully applied this approach in my recent application.
References
Archive with a sample project
Description AsyncTask (en)
Simple work with streams (en)
Dialogs in Android (en)