310 likes | 523 Views
Создание удобной архитектуры Android- приложения. Александр Османов Android- разработчик, DataArt , Воронеж. Постановка задачи. Часто во время работы приложения необходимо выполнять продолжительные задачи в фоне: Выполнение HTTP запроса к серверу Математический расчет
E N D
Создание удобной архитектуры Android-приложения Александр Османов Android-разработчик, DataArt, Воронеж
Постановка задачи • Часто во время работы приложениянеобходимо выполнять продолжительные задачи в фоне: • Выполнение HTTP запроса к серверу • Математический расчет • После выполнения задачи необходимо уведомить UI о завершении, чтобы UI смог обновиться. • Во время выполнения задачи может понадобиться узнать прогресс выполнения задачи
Почему нетривиально? • Часто нужна возможность контроля над очередностью выполнения задач, возможность отмены и отслеживания прогресса выполнения • Жизненный цикл визуальных компонентов часто ставит палки в колеса • До сих пор нету официально рекомендованной Google организации взаимодействия с сервисом: • Broadcasts/Local broadcasts • Binding • ResultReceiver • createPendingResult
Как UI может узнать, что что-то происходит? • «Управляемые» платформой — компоненты, реализующие ContentObserver. В случае использования приложением ContentProvider, а также адаптеров курсора подобные обновления достаются нам «бесплатно». О них речь не пойдет. • Уведомления, реализованные в приложении — наши нестандартные уведомления, когда мы просто хотим отправить результат или прогресс выполнения фоновой задачи в UI.
AsyncTask? • Самый очевидный и простой способ для новичка • Позволяет легко указывать, какой код исполнять в фоновом потоке, какой в UI потоке • Позволяет публиковать прогресс операции • Код пишется легко и быстро
AsyncTask • Смешивание кода, посылающего HTTP запросы, с кодом, отвечающим за UI в вашей Activity • Потеря контекста при пересоздании activity • Сложно контролировать очередность выполнения запросов • Вы не можете управлять процессом, в котором будет выполняться задача
Service! • Специально созданный для такого рода задач компонент с независимым жизненным циклом • Однако, требует дополнительной работы, чтобы организовать двустороннее общение с Activity
Наброски нашего фреймворка • Command — каждая задача, которую мы хотим выполнять в фоне — отдельный класс-команда • Command processor — сервис, отвечающий за планирование и выполнение команд • ContentProvider — поможет реализовать хранение данных + управляемые обновления UI. • Уведомления UI?
Broadcast • После выполнения работы сервис посылает broadcast-сообщение с результатом работы • UI получает сообщения при помощи BroadcastReceiver и ожидает входящих сообщений.
Broadcast • Преимущества • Легко использовать • Легко привязать к жизненному циклу Activity • Могут работать между процессами • Недостатки • Необходимо предпринять меры по защите сообщений, либо установив разрешения, либо ограничив принимающие пакеты • Также сообщения потенциально могут быть присланы извне другими приложениями. Опять необходимо предпринимать меры. • Проходит через системную очередь сообщений
LocalBroadcastManager • Преимущества • Легко использовать • Легко привязать к жизненному циклу Activity • Не нужно думать о безопасности • Недостатки • Не могут работать между процессами
ResultReceiver • Generic interface for receiving a callback result from someone. Use this by creating a subclass and implement onReceiveResult(int, Bundle), which you can then pass to others and send through IPC, and receive results they supply with send(int, Bundle). • Мы можем отправить экземпляр ResultReceiverпрямо как extra в Intent нашему сервису.
ResultReceiver • Преимущества • Работает как локально, так и через процессы • Не нужно думать о безопасности • Недостатки • Немного сложнее привязать к жизненному циклу Activity
Наброски нашего фреймворка • Command — каждая задача, которую мы хотим выполнять в фоне — отдельный класс-команда • Command processor — сервис, отвечающий за планирование и выполнение команд • ContentProvider — поможет реализовать хранение данных + управляемые обновления UI. • ResultReceiver — для возвращения результата работы фоновой задачи • Реестр выполняющихся в данный момент операций
Command Processor • Каждая задача, которую необходимо выполнить, будет инкапсулирована в класс-команду • Каждая команда будет реализовывать Parcelable для более удобной передачи ее сервису • Для каждой выполняемой команды будет создаваться уникальный идентификатор • Команды образуют иерархию, где базовый класс инкапсулирует ResultReceiverи реализует общие методы, например, sendResult(intresultCode, Bundle data).
Command Processor • В качестве процессора команд будет выступать Service • IntentService • Своя реализация сервиса с использованием ExecutorService • Команды будут передаваться сервису в виде extras в Intent • Сервис будет просто извлекать их и запускать в новом потоке (потоке из пула) • Сервис также может хранить соответствие идентификатор-выполняемая команда для возможности реализации отмены команды
Command Processor BaseCommandcommand = intent.getParcelableExtra(EXTRA_COMMAND); ResultReceivercallback = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER); command.execute(intent, context, callback);
Command Processor ExecutorService Service Command 1 ResultReceiver
ServiceHelper • ServiceHelper — это промежуточный слой между UI и сервисом, скрывающий рутину по созданию интентов сервису и предоставляющий нашему UI лишь набор бизнес методов для вызова. • Также он координирует ответы от сервиса и содержит информацию о командах, выполняющихся в данный момент. • ServiceHelper«живет» в Application scope приложения
ServiceHelper • Дополнительные методы ServiceHelper • isExecuting(intrequestId) • cancelCommand(intrequestId) • addListener(ServiceCallbackListener listener) • removeListener(ServiceCallbackListener listener) • Пример: • public intaskServer(String question)
Принцип работы • Activity вызывает бизнес-метод ServiceHelper • ServiceHelper генерирует и сохраняет ID запроса, запоминает, что данная команда выполняется • Собирает Intent, в который вкладывет ResultReceiver, экземпляр команды и отправляет сервису • Когда сервис завершает операцию, ServiceHelper отвечает за то, чтобы все активные Activity получили результат
ServiceCallbackListener • Слушателем может быть что угодно. Для удобства я сделал базовый класс Activity, выступающий в роли слушателя и подписывающийся на обновления при запуске. • Это позволяет в реализациях Activity просто переопределить метод onServiceCallback и реализовать в нем логику аналогично тому, как это делается со стандартными callback-методами системы.
ServiceCallbackListener • void onServiceResult(intrequestId, Intent intent,intresultCode, Bundle resultData); • requestIdпозволяет нам идентифицировать конкретный запрос • Intent позволит нам идентифицировать класс команды, если нас не интерует конкретный запрос • resultCode, resultData – позволят обработать результат выполнения команды
Принцип работы Application Activity Listener ServiceHelper Receiver Command Service Provider Comand 1 Comand 2
Отложенная проверка • Имея ID запроса, мы можем выполнять отложенную проверку. Представим последовательность: • пользователь запустил действие, запустилась крутилка • закрыл приложение на 2 минуты • действие уже выполнилось • пользователь открыл снова • тут мы проверяем в onResume, выполнилась ли операция, и убираем крутилку • т. е., просто вызываем getServiceHelper().isPending(requestId), если нам это нужно.
Расширение №1. Отмена выполнения • В базовый класс запрос добавим метод cancel() • Добавим метод cancelCommand(intcommandId) в ServiceHelper • Метод посылает Intent в сервис, который находит команду по идентификатору и вызывает cancel()
Расширение №2. Прогресс операции • Добавляем новый resultCodeпомимо SUCCESS и ERROR, и все.
Сторонние библиотеки, которые нам помогут • Ottoот Square – очень удобная реализация event bus. В нашей схеме подойдет для передачирезультатов команды • Использует reflection • Работает только в одном процессе и одном потоке • Groundy – неплохая библиотека, реализующая command processor на основе генерации кода
В итоге мы получили • Гибкую архитектуру для выполнения задач в фоне с поддержкой уведомлений UI • Поддержка жизненного цикла Activity • Возможность легко вынести выполнение задач в отдельный процесс, просто выставив атрибут сервису в манифесте • Возможность управлять стратегией выполнения задач меняя реализацию сервиса (IntentService vs ExecutorService) • Расширяемость
Пример исходного кода • Github - http://goo.gl/4voFM
Спасибо за внимание! Вопросы?