На Хабре уже опубликована хорошая статья в которой описано совместное использование 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