Pull to refresh

Поворот экрана во время выполнения долговременной операции

Reading time 4 min
Views 7.9K

Введение


При разработке практически любого приложения с пользовательским интерфейсом, программист рано или поздно встречается с ситуацией когда нужно выполнить долговременную операцию. Во время долговременной операции обычно пользователю показывают окно «Пожалуйста, подождите...» или что то в этом роде.

Платформа Android, да и наверное многие другие платформы не позволяют выполнять долговременные операции в UI потоке. Выполняя долговременную операцию в UI потоке вы просто напросто повесите программу.

Android предлагает для решения такого рода задач AsyncTask. AsyncTask позволяет выполнять долговременную операцию и взаимодействовать с UI потоком.

Проблема


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

Все прекрасно работает до тех пор, пока не сменится ориентация экрана (Книжная → Альбомная, Албомная → Книжная) или приложение не будет отправлено в фон. Обычно при таком подходе после смены ориентации экрана происходит краш приложения.

Почему происходит краш приложения


Потому, что при смене ориентации эрана Android пересоздает Activity, в итоге Activity на который вы передавали ссылку AsyncTask-у уже уничтожен и ваш AsyncTask пытается взаимодействовать с уничтоженным объектом.

Решения


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

Каждый из подходов требует реализации определенных твиков, дополнительных тестов, отладки, написания дополнительного кода или ограничения возможностей пользовательского интерфейса.
Столкнувшись с этой проблемой меня сильно раздражало, что для такой тривиальной задачи нет готового коробочного решения (во всяком случае я не нашел).

Можно я попробую


Не судите, пожалуйста, строго и не сочтите за самопиар, я просто хочу написать как я решаю данную проблему и поделиться решением с другими.

Я написал некоторое подобие фреймворка (я считаю, что фреймворк это слишком громко для моего решения, но ввиду особенностей использования все же подобие фреймворка) и разместил его на Github.

Исходные коды открыты, лицензия Apache 2.0, подчеркну еще раз, что я ничего не продаю, не навязываю и не попрашайничаю. Фреймворк называется Asmyk.

Как работает


При активации Activity (событие onResume), Activity отмечается в контексте приложения. Далее фоновая задача адресует UI задачи Activity из контекста.

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

Как внедрить


Скачиваем AAR файл и подключаем к проекту.

Если вы не расширяете класс Application своей реализацией, укажите в файле AndroidManifest.xml в теге application атрибут:

android:name="net.mabramyan.asmyk.core.AsmykApplicationContext"

Пример:


<application
...
    android:name="net.mabramyan.asmyk.core.AsmykApplicationContext"
...
>
...

Если вы расширяете класс Application своей реализацией, необходимо наследовать свою реализацию от класса AsmykApplicationContext.

Activity с которыми вы будете работать из фона должны наследоваться от AsmykCompatActivitiy.
Примечание: Можете наследовать хоть все свои Activity от AsmykCompatActivitiy.

Пример


UI «Пожалуйста, подождите...»


Создаете Activity наследуемую от AsmykPleaseWaitActivity или от AsmykBasicPleaseWaitActivity.

Что нужно реализовать для AsmykPleaseWaitActivity


Метод описывает действия которые должна выполнить Activity при обновлении статуса операции. Объект progressObj будет передан из AsmykPleaseWaitTask:

void onProgress(final Object progressObj)

Метод описывает действия которые должна выполнить Activity при провале операции. Объект errorObj будет передан из AsmykPleaseWaitTask.

void onFail(final Object errorObj) 

Метод описывает действия которые должна выполнить Activity при успешном выполнении операции. Объект successObj будет передан из AsmykPleaseWaitTask.

void onSuccess(final Object successObj)

Что нужно реализовать для AsmykBasicPleaseWaitActivity


Метод описывает действия которые должна выполнить Activity при успешном выполнении операции. Объект successObj будет передан из AsmykPleaseWaitTask:

void onSuccess(final Object successObj)

Методы onFail и onProgress уже реализованы. В качестве аргументов принимают String с описанием ошибки или с описанием нового состояния фоновой задачи соответственно.

Фоновая задача


Далее создаем фоновую задачу реализуя класс AsmykPleaseWaitTask. Вам придется реализовать всего один метод описывающий вашу фоновую задачу:

void doInBackground(final AsmykApplicationContext ctx)

В процессе выполнения задачи вы можете вызывать метод void fireProgress(AsmykApplicationContext ctx, final Object progressObj) — данный метод впоследствии вызовет onProgress у вашей AsmykPleaseWaitActivity. По завершении задачи вызовите метод fireSuccess или fireFailed в зависимости от результата выполнения операции.

Запуск


Пример вызова фоновой задачи:


pleaseWaitTask.start((AsmykApplicationContext) MainActivity.this.getApplicationContext());
Intent intent = new Intent(MainActivity.this, PleaseWaitActivity.class);
startActivity(intent);

Внимание: AsmykPleaseWaitTask реально начнет выполнение задачи только после того, как будет показана Activity наследуемая от AsmykPleaseWaitActivity.

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

Пример простенького приложения вы можете посмотреть тут.

Обращение к комментирующим


Спасибо за то, что прочли и нашли время прокомментировать.
Я за критику если она конструктивная.
Для того, что бы пост был полезным и для других читателей, давайте сделаем так:
Если вы считаете какие либо конструкции кода не правильными, опишите почему.
Если вы предлагаете метод который удобнее или правильнее чем вышеописанный, прикладывайте ваше решение применительно к задаче (это основная задача которую решает вышеописанный фреймворк):
1) Мы уже получили от пользователя порцию данных для регистрации, пользователь нажимает «Продолжить»
2) Выполняем шаг регистрации 1. Последовательно отправляется N различных запросов. Тут я хочу показывать пользователю, что именно сейчас программа делает. При этом поворот экрана и т.п. не должны обрывать процесс регистрации.
С моей точки зрения, таких задач может быть множество в реальном приложении.

P.S. Я полагаю, если мы будем прикладывать реальные примеры, пост будет максимально полезным.

У меня все! Спасибо за внимание.
Tags:
Hubs:
+7
Comments 29
Comments Comments 29

Articles