На Хабре уже опубликована хорошая статья в которой описано совместное использование ProgressDialog и AsyncTask, здесь я опишу, как добиться похожего функционала но на фрагментах, точнее используя DialogFragment и AsyncTaskLoader.
Итак, цель:

Реализация
1. Отправку сообщения из асинхронного процесса вынесем в абстрактный класс AbstractTaskLoader, наследник от AsyncTaskLoader:
Далее, наш класс, выполняющий какую либо длительную операцию уже наследуется от этого абстрактного класса и может информировать о процессе выполнения задачи:
2. Следующий шаг это реализация ProgressDialog-а отображающего сообщение и отвечающего за корректный запуск/перезапуск нашего Loader-a при смене ориентации экрана. Класс включает также обработчики обратного вызова для Loader-a и диалога:
Класс так же сделан абстрактным, впрочем это необязательно, просто один из вариантов реализации наименьшего связывания. Важны только результаты работы:
onLoadComplete(Object data);
onCancelLoad();
и реализация этих методов в наследнике (я сделал через интерфейс, см далее).
Здесь в конструктор передается Loader, который будет выполняться и тестовое сообщение, отображаемое сразу при запуске. Handler используется для передачи информационных сообщений от Loader-а о прогрессе. В onCreateDialog создаем и показываем наш диалог.
Далее нам надо указать, что при смене ориентации экрана этот фрагмент сохраняет свое состояние. Делается это c помощью метода setRetainInstance(true), в этом случае onDestroy при смене ориентации экрана вызываться не будет и диалог не закроется.
Переопределяем метод onActivityCreated, в котором так же запускаем наш Loader при первом запуске или повторно соединяемся к существующему (при смене ориентации экрана).
Небольшое отступление:
Метод setRetainInstance не совсем корректно работает именно для DialogFragment в паре с “библиотекой совместимости” (compatibility-library), т.е метод onDestroy все-таки вызывается хотя не должен. Но есть обходное решение, в нашем диалоге добавляем:
Подробности здесь.
Обработка запуска и завершения работы Loader-a:
Обработка отмены операции пользователем — нажатие кнопки “Back”:
3. Базовый каркас создан, теперь используем его.
Создаем интерфейс для уведомления о завершении работы:
Создаем нашу реализацию ProgressDialog-а на базе созданного абстрактного класса:
И Builder для удобного построения и запуска Loader-a:
4. Последнее, это наш Loader. Для примера просто каждую секунду обновляет тест сообщения в ProgreesDialog-e:
Добавим статитческий метод для удобного запуска:
5. Вызов
Скачать исходный код примера, конструктивная критика и замечания приветствуются.
Источники:
1. Блог Evelina Vrabie, стьтья 'Android Fragments, Saving State and Screen Rotation'
2. Stack Overflow
Итак, цель:
- отображать ProgressDialog при выполнении длительной операции, текст сообщения которого может информировать о ходе выполнения задачи;
- корректная поддержка смены ориентации приложением.

Реализация
1. Отправку сообщения из асинхронного процесса вынесем в абстрактный класс AbstractTaskLoader, наследник от AsyncTaskLoader:
public abstract class AbstractTaskLoader extends AsyncTaskLoader<Object> { //type of the published values public static int MSGCODE_PROGRESS = 1; public static int MSGCODE_MESSAGE = 2; private Handler handler; protected AbstractTaskLoader(Context context) { super(context); } protected void setHandler(Handler handler){ this.handler = handler; } /** * Helper to publish string value * @param value */ protected void publishMessage(String value){ if(handler!=null){ Bundle data = new Bundle(); data.putString("message", value); /* Creating a message */ Message msg = new Message(); msg.setData(data); msg.what = MSGCODE_MESSAGE; /* Sending the message */ handler.sendMessage(msg); } } …
Далее, наш класс, выполняющий какую либо длительную операцию уже наследуется от этого абстрактного класса и может информировать о процессе выполнения задачи:
@Override public Object loadInBackground() { … publishMessage(result); … }
2. Следующий шаг это реализация ProgressDialog-а отображающего сообщение и отвечающего за корректный запуск/перезапуск нашего Loader-a при смене ориентации экрана. Класс включает также обработчики обратного вызова для Loader-a и диалога:
public abstract class AbstractTaskProgressDialogFragment extends DialogFragment implements LoaderCallbacks<Object>, OnCancelListener { private ProgressDialog progressDialog; private final AbstractTaskLoader loader; protected abstract void onLoadComplete(Object data); protected abstract void onCancelLoad(); protected AbstractTaskProgressDialogFragment(AbstractTaskLoader loader,String message){ loader.setHandler(handler); this.loader = loader; Bundle args = new Bundle(); args.putString("message", message); setArguments(args); } @Override public ProgressDialog onCreateDialog(Bundle savedInstanceState) { String message = getArguments().getString("message"); progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage(message); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setOnCancelListener(this); progressDialog.show(); return progressDialog; } private final Handler handler = new Handler() { public void handleMessage(Message msg) { if(msg.what == AbstractTaskLoader.MSGCODE_MESSAGE){ String message = AbstractTaskLoader.getMessageValue(msg); if(message!=null){ //update progress bar message if(progressDialog!=null){ progressDialog.setMessage(message); } } } } }; ...
Класс так же сделан абстрактным, впрочем это необязательно, просто один из вариантов реализации наименьшего связывания. Важны только результаты работы:
onLoadComplete(Object data);
onCancelLoad();
и реализация этих методов в наследнике (я сделал через интерфейс, см далее).
Здесь в конструктор передается Loader, который будет выполняться и тестовое сообщение, отображаемое сразу при запуске. Handler используется для передачи информационных сообщений от Loader-а о прогрессе. В onCreateDialog создаем и показываем наш диалог.
Далее нам надо указать, что при смене ориентации экрана этот фрагмент сохраняет свое состояние. Делается это c помощью метода setRetainInstance(true), в этом случае onDestroy при смене ориентации экрана вызываться не будет и диалог не закроется.
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, loader.getArguments(), this); }
Переопределяем метод onActivityCreated, в котором так же запускаем наш Loader при первом запуске или повторно соединяемся к существующему (при смене ориентации экрана).
Небольшое отступление:
Метод setRetainInstance не совсем корректно работает именно для DialogFragment в паре с “библиотекой совместимости” (compatibility-library), т.е метод onDestroy все-таки вызывается хотя не должен. Но есть обходное решение, в нашем диалоге добавляем:
@Override public void onDestroyView() { if (getDialog() != null && getRetainInstance()) getDialog().setOnDismissListener(null); super.onDestroyView(); }
Подробности здесь.
Обработка запуска и завершения работы Loader-a:
@Override public Loader<Object> onCreateLoader(int id, Bundle args) { loader.forceLoad(); return loader; } @Override public void onLoadFinished(Loader<Object> loader, Object data) { onLoadComplete(data); ((AbstractTaskLoader) loader).setHandler(null); hideDialog(); }
Обработка отмены операции пользователем — нажатие кнопки “Back”:
@Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); loader.cancelLoad(); //base metod loader.setCanseled(true); //custom flag onCancelLoad(); //do not invoke dismiss for this dialog }
3. Базовый каркас создан, теперь используем его.
Создаем интерфейс для уведомления о завершении работы:
public interface ITaskLoaderListener { void onLoadFinished(Object data); void onCancelLoad(); }
Создаем нашу реализацию ProgressDialog-а на базе созданного абстрактного класса:
public class TaskProgressDialogFragment extends AbstractTaskProgressDialogFragment { private ITaskLoaderListener taskLoaderListener; private final Handler handler = new Handler(); protected TaskProgressDialogFragment(AbstractTaskLoader loader, String message) { super(loader, message); } protected void setTaskLoaderListener(ITaskLoaderListener taskLoaderListener){ this.taskLoaderListener = taskLoaderListener; } @Override protected void onLoadComplete(final Object data) { if(taskLoaderListener!=null){ taskLoaderListener.onLoadFinished(data); } } @Override protected void onCancelLoad() { if (taskLoaderListener != null) { taskLoaderListener.onCancelLoad(); } } ...
И Builder для удобного построения и запуска Loader-a:
public static class Builder { FragmentActivity fa; AbstractTaskLoader loader; ITaskLoaderListener taskLoaderListener; Boolean cancelable; String progressMsg; public Builder(FragmentActivity fa, AbstractTaskLoader loader, String progressMsg) { this.fa = fa; this.loader = loader; this.progressMsg = progressMsg; } public Builder setTaskLoaderListener( ITaskLoaderListener taskLoaderListener) { this.taskLoaderListener = taskLoaderListener; return this; } public Builder setCancelable(Boolean cancelable) { this.cancelable = cancelable; return this; } public void show() { String TAG_FRAGMENT = UUID.randomUUID().toString(); // remove prev if exists FragmentManager fm = fa.getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment prev = fm.findFragmentByTag(TAG_FRAGMENT); if (prev != null) { ft.remove(prev); } // create dlg fragment TaskProgressDialogFragment fragment = new TaskProgressDialogFragment( loader, progressMsg); fragment.setTaskLoaderListener(taskLoaderListener); fragment.setCancelable(cancelable); Bundle args = new Bundle(); args.putString("message", progressMsg); fragment.setArguments(args); // show the dialog. fragment.show(ft, TAG_FRAGMENT); } }
4. Последнее, это наш Loader. Для примера просто каждую секунду обновляет тест сообщения в ProgreesDialog-e:
public class SampleTask extends AbstractTaskLoader { @Override public Object loadInBackground() { String result = ""; for(int i=0; i<10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = "Wait: " + String.valueOf(i); publishMessage(result); if(isCanselled()){ break; } Log.d(TAG, result); } return result; }
Добавим статитческий метод для удобного запуска:
public static void execute(FragmentActivity fa, ITaskLoaderListener taskLoaderListener) { SampleTask loader = new SampleTask(fa); new TaskProgressDialogFragment.Builder(fa, loader, "Wait...") .setCancelable(true) .setTaskLoaderListener(taskLoaderListener) .show(); }
5. Вызов
public class SampleActivity extends FragmentActivity implements ITaskLoaderListener{ … SampleTask.execute(this, this); @Override public void onCancelLoad() { Log.d(TAG, "task canceled"); } @Override public void onLoadFinished(Object data) { if(data!=null && data instanceof String){ Log.d(TAG, "task result: " + data); } }
Скачать исходный код примера, конструктивная критика и замечания приветствуются.
Источники:
1. Блог Evelina Vrabie, стьтья 'Android Fragments, Saving State and Screen Rotation'
2. Stack Overflow
