Download - Страх и ненависть в Event Bus
МАГАЗИНЧИК
• 500 страниц документации.
• В том числе 200 страниц описания API.
• Большое количество сложных use-cases.
• Разветвлённая логика и схема зависимостей.
МАГАЗИНЧИК
• 4 человеко-года на каждую платформу (iOS, Android).
• 6 разработчиков в команде Android.
•Общий размер команды — до 27 человек.
•≈40000 строк кода.
ОСОБЕННОСТИ
• Android 4.0+
•Одно действие — один запрос.
• Настройками кэширования занимается сервер.
• Ссылки для последующих запросов приходят с сервера.
ПРОБЛЕМЫ
•Много разнородных запросов (около 100 в последней версии API).
• Компонент-инициатор != компонент-получатель.
• Инициаторов может быть несколько.
• Получателей тоже!
КАК ДЕЛАТЬ ЗАПРОСЫ
• In-place AsyncTask.
• AQuery и иже с ними.
• Сервисы, контент-провайдеры, броадкасты…
• Реактивное программирование.
ЧЕМ ПЛОХИ ASYNCTASK
•Очень сложно переиспользовать.
•Очень много boilerplate-кода.
• Гроб, гроб, кладбище, утечка контекста.
requestDataTask = new AsyncTask<Void, Void, JSONObject>() { @Override protected JSONObject doInBackground(Void... params) { final String requestResult = apiService.getData(); final JSONObject json = JsonUtils.parse(requestResult); lruCache.cacheJson(json); return json; } };
ПОЧЕМУ НЕ КАТИТ AQUERY
• Слишком локальное применение.
•Опять же — много повторяющегося кода.
• Сложно прикручивать другие библиотеки.
aq.ajax("http://example.com", String.class, CACHE_TIME, new AjaxCallback<String>() { @Override public void callback(String url, String object, AjaxStatus status) { Type listType = new TypeToken<List<User>>() {}.getType(); List<User> list = new Gson().fromJson(object, listType); listener.onResponse(list); } });
ЧТО НЕ ТАК С CP
• Дважды конструируем объект (при разборе результата запроса и при десериализации из БД).
• Нужно инвалидировать данные. Зачем нам SQL?
Нам SQL не нужен
AsyncTask AQuery Service + CP
Понятность + + ≈
Удобство -1 3 2
Переиспользование — — +
Не делаем лишнего — + —
Пацаны так делают? — ≈ +
ГОТОВЫЙ ФРЕЙМВОРК
• Сильная связность
• Есть явная «точка сопряжения»
final Subscription subscription = createApiRequestObservable() //создали Observable с запросом .timeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS) //поставили таймаут .retry(RETRY_COUNT_FOR_REQUEST) //поставили кол-во повторов .onErrorResumeNext(createRequestErrorHandler()) // назначили обработчик ошибки .map(createJsonMapOperator()) //модифицировали Observable, чтобы получать JSONObject .onErrorReturn(createJsonErrorHandler()) //возвращаем в случае ошибки то, что ожидаем .doOnNext(createCacheOperation()); //кэшируем JSONObject .subscribeOn(Schedulers.newThread()) //трудоёмкое в отдельном потоке .observeOn(AndroidSchedulers.mainThread()) // обработка результата - в main thread .subscribe(subscriber); //обработчик результата
AsyncTask AQuery Service + CP RxJava
Понятность + + ≈ ≈
Удобство -1 3 2 4
Переиспользование — — + +
Не делаем лишнего — + — ≈
Пацаны так делают? — ≈ + +
VOLLEY
• Несколько уровней кэширования.
• Абстрагируемся от HTTP насколько возможно.
•Очень гибкое управление очередью.
ТИПИЧНАЯ СХЕМА ДЕЙСТВИЙ
• Создаём по действию новый запрос
• Добавляем его в очередь
• Забываем
• …
•ОТВЕТ СЕРВЕРА!!!
ОТВЕТ СЕРВЕРА
• Распространяется через event bus.
• Все желающие его получают.
• Не страшно, если никому он уже не нужен.
OTTO
• Подписка на события через аннотации.
• Подписались на то, что не приходит — не страшно.
• Приходит то, на что не подписывались — не беда!
public class BaseFragment extends Fragment { protected Bus bus = AzbukaApplication.getBus();
@Override public void onResume() { super.onResume(); bus.register(this); NetworkFacade.cancelOldRequests(this); }
@Override public void onPause() { super.onPause(); bus.unregister(this); NetworkFacade.markToCancel(this); }
ЧЕМ МАНИПУЛИРУЕМ
• BaseRequest — инкапсулирует парсинг и рассылку сообщений
• BaseResponse — bean + id
public static class PutUserNickRequest extends BaseRequest<PutUserNickResponse, NewUserNick, PutUserNickError> { /** * API v0.36 p. 74, Change user password (4.10.4) * @param url - POST /user/<user-id>/phone * @param nick - typed nick */ public PutUserNickRequest(String url, String nick) { super(Method.PUT, url, PutUserNickResponse.class, PutUserNickError.class, new NewUserNick(nick)); } }
ПОДПИСКА
• Нам не важно, кто отправил.
• Нам нужно лишь уметь обрабатывать данный тип ответа.
• Кто на экране — тот и главный!
@Subscribe public void onResponse(BasketRequest.BasketContentResponse basketContentResponse) { loadWelcome(); }
@Subscribe public void onError(OtherError error) { if (TextUtils.isEmpty(error.getMessage())) { Toast.makeText(getActivity(), getString(R.string.failed), Toast.LENGTH_LONG).show(); } else { Toast.makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG).show(); } }
LET IT CRASH
• Есть одна точка получения данных об ошибке.
• Есть один общий workflow обработки ошибки.
• При особой надобности — можем подписаться на ошибки конкретного класса.
ПЛЮСЫ И ПЛЮШКИ
• Лёгкая проверка работоспособности back-end.
• Часть приложения сделана как конечный автомат на основе Event Bus.
• Архитектура «непривязанных запросов» отлично ложится почти на любое приложение.