Создание удобной архитектуры Android- приложения

Preview:

DESCRIPTION

Создание удобной архитектуры Android- приложения. Александр Османов Android- разработчик, DataArt , Воронеж. Постановка задачи. Часто во время работы приложения необходимо выполнять продолжительные задачи в фоне: Выполнение HTTP запроса к серверу Математический расчет - PowerPoint PPT Presentation

Citation preview

Создание удобной архитектуры 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(int resultCode, Bundle data).

Command Processor

• В качестве процессора команд будет выступать Service– IntentService– Своя реализация сервиса с использованием ExecutorService

• Команды будут передаваться сервису в виде extras в Intent

• Сервис будет просто извлекать их и запускать в новом потоке (потоке из пула)

• Сервис также может хранить соответствие идентификатор-выполняемая команда для возможности реализации отмены команды

Command Processor

BaseCommand command = intent.getParcelableExtra(EXTRA_COMMAND);ResultReceiver callback = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER);

command.execute(intent, context, callback);

Command Processor

ServiceExecutorService

Command 1

ResultReceiver

ServiceHelper

• ServiceHelper — это промежуточный слой между UI и сервисом, скрывающий рутину по созданию интентов сервису и предоставляющий нашему UI лишь набор бизнес методов для вызова.

• Также он координирует ответы от сервиса и содержит информацию о командах, выполняющихся в данный момент.

• ServiceHelper «живет» в Application scope приложения

ServiceHelper

• Дополнительные методы ServiceHelper– isExecuting(int requestId)– cancelCommand(int requestId)– addListener(ServiceCallbackListener listener)– removeListener(ServiceCallbackListener listener)

• Пример:– public int askServer(String question)

Принцип работы

• Activity вызывает бизнес-метод ServiceHelper• ServiceHelper генерирует и сохраняет ID запроса,

запоминает, что данная команда выполняется• Собирает Intent, в который вкладывет ResultReceiver,

экземпляр команды и отправляет сервису• Когда сервис завершает операцию, ServiceHelper

отвечает за то, чтобы все активные Activity получили результат

ServiceCallbackListener

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

• Это позволяет в реализациях Activity просто переопределить метод onServiceCallback и реализовать в нем логику аналогично тому, как это делается со стандартными callback-методами системы.

ServiceCallbackListener

• void onServiceResult(int requestId, Intent intent, int resultCode, Bundle resultData);– requestId позволяет нам идентифицировать конкретный запрос– Intent позволит нам идентифицировать класс команды, если нас

не интерует конкретный запрос– resultCode, resultData – позволят обработать результат выполнения

команды

Принцип работы

Application

ServiceHelper

Service

Comand 1 Comand 2

Activity Listener

Provider

Receiver

Command

Отложенная проверка

• Имея ID запроса, мы можем выполнять отложенную проверку. Представим последовательность:– пользователь запустил действие, запустилась крутилка– закрыл приложение на 2 минуты– действие уже выполнилось– пользователь открыл снова

• тут мы проверяем в onResume, выполнилась ли операция, и убираем крутилку– т. е., просто вызываем getServiceHelper().isPending(requestId), если

нам это нужно.

Расширение №1. Отмена выполнения

• В базовый класс запрос добавим метод cancel()• Добавим метод cancelCommand(int commandId) в

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

Спасибо за внимание!

Вопросы?

Recommended