Tuesday, September 18, 2012

Простое использование AsyncTask и ProgressDialog в Android


image

Практика создания приложений, отзывчивых к действиям пользователя, предполагает, что все тяжелые операции должны исполняться в отдельном потоке, сообщая тем или иным образом пользователю о своем прогрессе.

Android содержит массу способов для организации данного подхода, но одним из самых удобных можно признать использование AsyncTask и ProgressDialog.

Эта парочка превосходно решает задачу, но начинает приносить невыносимую боль, когда количество Activity с такой логикой переваливает за одну, что приводит к повторению управляющего кода, и еще большую боль, когда приложение должно поддерживать смену ориентации экрана.




AsyncTask

Для тех, кто не знаком с AsyncTask, поясню, что это специальный абстрактный класс, предоставляющий набор методов для реализации:
  • onPreExecute для размещения инициализирующего кода (UI поток)
  • doInBackground для размещения тяжелого кода, который будет выполняться в другом потоке
  • onProgressUpdate для информирования о прогрессе (UI поток)
  • onPostExecute для обработки результата, возвращенного doInBackground (UI поток)
и вспомогательных методов
  • isCancelled чтобы узнать не отменил ли кто задачу
  • publishProgress для перевода сообщения о прогрессе в UI поток с последующим вызовомonProgressUpdate
Суть проблемы

Использование упомянутых классов не представляет особого труда, достаточно пары сниппетов, чтобы код заработал как нужно и ProgressDialog начал свое информирование о ходе выполнения задачи. Но, как известно, дьявол кроется в деталях, поэтому стоит только поменять ориетацию экрана, как диалог пропадет, так же как и результат долгой, но невероятно ответственной операции.

Причина заключается в жизненном цикле Activity: смена ориентации экрана трактуется как смена конфигурации, что приводит к пересозданию Activity. Можно, конечно, отключить этот механизм, задав тегandroid:configChanges="orientation" у Activity и определив собственный код, который, при необходимости, произведет необходимые изменения. Но это будет необоснованное внедрение.

Решением будет создание специального класса по управлению связкой Activity-AsyncTask-ProgressDialog, назовем его AsyncTaskManager.

Activity

Итак, в идеале наша Activity должна делать всего пять вещей (код из проекта примера):
  • Создавать AsyncTaskManager в методе onCreate
        mAsyncTaskManager = new AsyncTaskManager(this, this);
  • Делегировать AsyncTaskManager восстановление задачи из состояния
        mAsyncTaskManager.handleRetainedTask(getLastNonConfigurationInstance());
  • Создавать конкретную задачу и отдавать ее в управление AsyncTaskManager’а
        mAsyncTaskManager.setupTask(new Task(getResources()));
  • Делегировать AsyncTaskManager сохранение задачи в состояние
        return mAsyncTaskManager.retainTask();
  • Обрабатывать асинхронное завершение задачи
Для последнего пункта и уменьшения связанности между Activity и AsyncTaskManager можно создать интерфейс для уведомления о завершении и реализовать его:

    public interface OnTaskCompleteListener {
        void onTaskComplete(Task task);
    }

В параметре метода будет передаваться задача, выполнение которой было завершено.

AsyncTaskManager

AsyncTaskManager должен отвечать за корректную работу всех компонентов, что сводится к списку следующих задач:
  • Создавать диалог при инициализации
  • При получении задачи в управление запускать ее
  • Отображать состояние задачи в диалоге
  • Отсоединяться от задачи после сохранения ее в состояние и присоединяться заново при восстановлении
  • Отменять задачу при отмене диалога
  • Закрывать диалог при завершении задачи
  • Сообщать Activity о завершении либо отмене задачи
Для общения с задачей достаточно следующего интерфейса, который следует реализовать и передать в задачу:

    public interface IProgressTracker {
        void onProgress(String message);
        void onComplete();
    }

Реализация:

    @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;
    }

Присоединение к задаче:

    mAsyncTask.setProgressTracker(this);

Отсоединение от задачи:

    mAsyncTask.setProgressTracker(null); 
    mAsyncTask = null;

Отмена диалога:

    @Override
    public void onCancel(DialogInterface dialog) {
        mAsyncTask.setProgressTracker(null);
        mAsyncTask.cancel(true);
        mTaskCompleteListener.onTaskComplete(mAsyncTask);
        mAsyncTask = null;
    }

AsyncTaskManager выполняет роль своеобразного ключа, который подключает и отключает работающую задачу к возможно пересозданному экземпляру Activity. Кроме того, он берет на себя и скрывает логику работы с ProgressDialog.

AsyncTask


Для задачи, помимо реализации основных методов, требуется реализация метода, который поможет соединять/разъединять ее с AsyncTaskManager’ом:

    public void setProgressTracker(IProgressTracker progressTracker) {
        mProgressTracker = progressTracker;
        if (mProgressTracker != null) {
            mProgressTracker.onProgress(mProgressMessage);
            if (mResult != null) {
                mProgressTracker.onComplete();
            }
        }
    }

Как видно из приведенного кода, задача сохраняет вычисленный результат и последнее сообщение о прогрессе, и, в зависимости от состояния, вызывает тот или иной метод трекера (AsyncTaskManager’а).

Таким образом, даже если задача завершится до пересоздания Activity, та получит уведомление о завершении задачи.

Результат

Теперь можно безбоязненно крутить телефон в руках – все задачи будут обработаны корректно.

Использование такого менеджера существенно снижает количество кода в Activity и позволяет переиспользовать данную функциональность в проекте. Данный подход я разработал и успешно применил в своем недавнем приложении.

Ссылки

Архив с проектом примера
Описание AsyncTask (en)
Простая работа с потоками (en)
Диалоги в Android (en)

©HabraHabr.ru