
The practice of creating applications that are responsive to the actions of the user, assumes that all heavy operations must be executed 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 it starts to bring unbearable pain when the number of Activity with such logic passes for one, which leads to the repetition of the control code, and even more pain when the application must support changing the orientation of the screen.
AsyncTask
For those who are not familiar with AsyncTask, I will clarify that this is a special abstract class that provides a set of methods to implement:
- onPreExecute for placing the initialization code (UI stream)
- doInBackground for placing heavy code that will be executed in another thread
- onProgressUpdate to report progress (UI stream)
- onPostExecute to handle the result returned by the doInBackground (UI stream)
and auxiliary methods
- isCancelled to find out if someone canceled the task
- publishProgress to translate the progress report into a UI thread and then call onProgressUpdate
The essence of the problem
Using the mentioned classes is not difficult, just a couple of snippets are enough for the code to work as needed and ProgressDialog starts informing about the progress of the task. But, as you know, the devil is in the details, so you should only change the screen orientation, as the dialogue will disappear, as well as the result of a long, but incredibly responsible operation.
The reason is the life cycle of the Activity: changing the orientation of the screen is interpreted as a change of configuration, which leads to the re-creation of the Activity. You can, of course, disable this mechanism by setting the
android:configChanges="orientation"
tag of the Activity and defining your own code, which, if necessary, will make the necessary changes. But it will be an unreasonable introduction.
The solution will be to create a special class for managing the bundle Activity-AsyncTask-ProgressDialog, let's call it AsyncTaskManager.
Activity
So, ideally, our Activity should do only five things (code from the example project):
- Create AsyncTaskManager in the onCreate method
mAsyncTaskManager = new AsyncTaskManager(this, this);
- Delegate AsyncTaskManager to restore task from state
mAsyncTaskManager.handleRetainedTask(getLastNonConfigurationInstance());
- Create a specific task and give it to the management of AsyncTaskManager
mAsyncTaskManager.setupTask(new Task(getResources()));
- Delegate AsyncTaskManager save task to state
return mAsyncTaskManager.retainTask();
- Handle asynchronous task completion
For the last point and reduce connectivity between the Activity and AsyncTaskManager, you can create an interface for notification of completion and implement it:
public interface OnTaskCompleteListener { void onTaskComplete(Task task); }
In the method parameter, the task will be transferred, the execution of which has been completed.
AsyncTaskManager
AsyncTaskManager should be responsible for the correct operation of all components, which boils down to the list of the following tasks:
- Create a dialog at initialization
- When receiving a task in management, run it
- Display task status in dialog
- Detach from the task after saving it to the state and rejoin when restoring
- Cancel a task when canceling a dialog
- Close the dialog when completing the task
- Report Activity to complete or cancel task
To communicate with the task, the following interface is sufficient, which should be implemented and passed to the task:
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 a task:
mAsyncTask.setProgressTracker(this);
Detaching from the task:
mAsyncTask.setProgressTracker(null); mAsyncTask = null;
Cancel dialogue:
@Override public void onCancel(DialogInterface dialog) { mAsyncTask.setProgressTracker(null); mAsyncTask.cancel(true); mTaskCompleteListener.onTaskComplete(mAsyncTask); mAsyncTask = null; }
AsyncTaskManager performs the role of a peculiar key that connects and disconnects a running task to a possibly recreated Activity instance. In addition, it takes over and hides the logic of working with ProgressDialog.
AsyncTask
For the task, in addition to the implementation of the basic methods, the implementation of the method is required which will help to 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 message about the progress, and, depending on the state, calls one or another tracker method (AsyncTaskManager).
Thus, even if the task is completed before the Activity is re-created, it will receive a notification of the completion of the task.
Result
Now you can safely turn the phone in your hands - all tasks will be processed correctly.
Using such a manager significantly reduces the amount of code in the Activity and allows you to reuse this functionality in the project. I developed this approach and successfully applied it in my recent application.
Links
Archive with draft projectAsyncTask Description (en)Simple work with threads (en)Conversations in Android (en)