Необходимо в приложении реализовать "формочку входа". Упрощённая модель выглядит так:
1. пользователь нажимает кнопку
2. запускается "долгая" операция
3. по итогам операции пользователя перекидывает на какую-то активити
Как это реализовать корректно?
Известные нюансы платформы Андроид:
1. Операции, не связанные напрямую с обновлением UI нужно выполнять вне потока UI.
2. Пользователь может покинуть активити в любой момент, включая "сразу после запуска операции".
3. Пользователь может вернуться в активити в любой момент, включая "ещё до завершения операции" и "уже после завершения операции"
4. Если пользователь ушёл из активити, нет никаких гарантий, что при возврате в эту активити будет использоваться тот же инстанс.
(Для упрощения задачи предположим, что после нажатия кнопки мы тут же показываем ProgressDialog, который нельзя закрыть, поэтому сценарий с Back можно пропустить)
Требования:
Если пользователь нажал кнопку, то без всяких "предположим" и "если" операция должна отработать и, как только результат доступен, пользователя должно перекинуть на другую активити. Это поведение не должно зависеть от того, что пользователь сделал сразу после нажатия кнопки: принял входящий звонок, повернул девайс, нажал Home, решив почитать твиттер, и т.д.
1. Есть известное простое неправильное решение вроде такого:
...
new AsyncTask() {
void doWork() {
boolean authenticated = webService.authenticate(email, password);
if(authenticated) {
Intent intent = new Intent(AuthActivity.this, HomeActivity.class);
startActivity(intent);
finish();
}
}
}
...
Оно работает только если пользователь не крутит девайс, ему никто не звонит и он не решает пойти в Твиттер, пока мы тут аутентифицируемся.
2. Есть сложное неправильное решение вроде такого:
class WebServiceFacade extends IntentService {
... // оно пришлёт нам Broadcast
}
...
// дёргаем сервис
Intent authenticateIntent = new Intent(...)
startService(authenticateIntent);
...
// ждём ответ от сервиса
BroadcastReceiver receiver = .... {
Intent intent = new Intent(AuthActivity.this, HomeActivity.class);
startActivity(intent);
finish();
}
Эта штука совершенно точно не будет работать, если к моменту завершения операции пользователь не будет смотреть на наше активити. Т.к. мы просто будем отписаны от этих уведомлений.
3. Сложное правильное решение
Никакой специфики Андроида - пишем самодельный сервис например на ExecutorService таким образом, чтобы во-первых он всегда существовал, а во-вторых кроме отправки уведомлений через коллбек предоставлял доступ к результатам операций, которые завершились, но которые на момент завершения операций никто "не слушал". В этом случае при запуске активити мы сначала проверяем готовые результаты и на основании их делаем выводы, если результатов нет, то подписываемся и просто висим - ждём результат, если пользователь решает уйти - отписываемся (ну и дальше по кругу). Это даёт гарантию безопасного убийства и перезапуска активити, при этом для пользователя всё прозрачно. Что-то вроде такого:
(at)Singleton
class MyService {
...
}
...
(at)Inject
private MyService service;
...
void onResume() {
if(service.hasAuthResult()) {
Intent intent = new Intent(AuthActivity.this, HomeActivity.class);
startActivity(intent);
finish();
} else {
service.setListener(this);
}
}
...
void onPause() {
service.setListener(null);
}
...
void MyServiceListener.onAuthResult(bool isAuthenticated) {
Intent intent = new Intent(AuthActivity.this, HomeActivity.class);
startActivity(intent);
finish();
}
...
Я реализовал решение номер 3, оно отлично работает, но мне не нравится объём кода и сам факт наличия такого велосипеда. Может я что-то упускаю и при той же корректности можно всё сделать проще?