Полный список
— рассмотрим взаимодействие между Activity и ее фрагментами
После размещения фрагмента, хотелось бы начать с ним взаимодействовать. Т.е. размещать View-компоненты и работать с ними, обращаться к фрагментам из Activity и наоборот. Попробуем это реализовать.
Для чистоты эксперимента будем работать с двумя фрагментами: статическим и динамическим.
Project name: P1061_FragmentActivity
Build Target: Android 4.1
Application name: FragmentActivity
Package name: ru.startandroid.develop.p1061fragmentactivity
Create Activity: MainActivity
В strings.xml добавим строки:
Создаем layout и классы для двух фрагментов.
У фрагмента нет привычного для нас метода findViewById для поиска компонентов с экрана. Поэтому вызываем этот метод для View, которое будет содержимым фрагмента. В методе onCreateView мы создаем View и сразу же находим в нем кнопку и ставим ей обработчик. Затем отдаем View системе.
Все аналогично Fragment1.
Настраиваем основное Activity.
Кнопка, компонент fragment, в который помещен Fragment1, и контейнер FrameLayout, в который потом поместим Fragment2.
Обратите внимание на атрибут tools:layout. В нем указан layout-файл, и мы можем на этапе разработки видеть, как будет выглядеть статический фрагмент, когда приложение будет запущено.
Для этого надо нажать правой кнопкой на компоненте fragment, и через пункт Fragment Layout указать нужный layout.
Здесь мы просто добавляем Fragment2 в контейнер.
Все сохраняем, запускаем приложение.
Жмем кнопку Log в первом фрагменте и смотрим лог:
Button click in Fragment1
Жмем Log во втором фрагменте:
Button click in Fragment2
Все ок. Компоненты в фрагментах нашлись и обработчики среагировали на нажатия.
Атрибут onClick, который мы привыкли использовать для кнопки, здесь не прокатит. Указанный в этом атрибуте метод, будет вызван в Activity, а не в фрагменте.
Доступ к фрагменту из Activity
Разберемся, как получить доступ к фрагменту из Activity. Для этого у FragmentManager есть метод findFragmentById, который на вход принимает id компонента fragment (если фрагмент статический) или id контейнера (если динамический).
У нас в main.xml есть кнопка btnFind, вызывающая метод onClick при нажатии. Дорисуем в MainActivity.java метод onClick:
Используем метод findFragmentById. В первом случае на вход передаем id компонента fragment, т.к. Fragment1 у нас размещен именно так. При поиске Fragment2 указываем id контейнера, в который этот фрагмент был помещен. В результате метод findFragmentById возвращает нам объект Fragment.
Далее мы получаем доступ к его View с помощью метода getView, находим в нем TextView и меняем текст.
Все сохраняем, запускаем. Жмем кнопку Find
Тексты в фрагментах обновились. Тем самым из Activity мы достучались до фрагментов и их компонентов.
На всякий случай проговорю одну вещь из разряда «Спасибо кэп!». Если посмотреть на код MainActivity, то можно заметить, что работая с frag2 в методе onCreate и с frag2 в методе onClick мы работаем с текущим фрагментом Fragment2. Это так и есть. Оба frag2 в итоге будут ссылаться на один объект. Так что, если вы динамически добавили фрагмент, то у вас уже есть ссылка на него, и искать его через findFragmentById вам уже не надо.
Доступ к Activity из фрагмента
Теперь попробуем из фрагмента поработать с Activity. Для этого фрагмент имеет метод getActivity.
Давайте перепишем обработчик кнопки в первом фрагменте. Будем менять текст кнопки btnFind.
Получаем Activity методом getActivity, ищем в нем кнопку и меняем текст.
Сохраняем, запускаем. Жмем кнопку в первом фрагменте:
Работает. Из фрагмента мы поменяли компонент Activity.
Обработка в Activity события из фрагмента
Рассмотрим механизм, который описан в хелпе: фрагмент генерирует некое событие и ставит Activity обработчиком.
Например, в Activity есть два фрагмента. Первый – список заголовков статей. Второй – отображает содержимое статьи, выбранной в первом. Мы нажимаем на заголовок статьи в первом фрагменте и получаем содержимое во втором. В этом случае, цель первого фрагмента – передать в Activity информацию о том, что выбран заголовок. А Activity дальше уже сама решает, что делать с этой информацией. Если, например, приложение запущено на планшете в горизонтальной ориентации, то можно отобразить содержимое статьи во втором фрагменте. Если же приложение запущено на смартфоне, то экран маловат для двух фрагментов и надо запускать отдельное Activity со вторым фрагментом, чтобы отобразить статью.
Фишка тут в том, что первому фрагменту неинтересны все эти терзания Activity. Фрагмент – обособленный модуль. Его дело — проинформировать, что выбрана статья такая-то. Ему не надо искать второй фрагмент и работать с ним – это дело Activity.
Тут немного отвлекусь на небольшое лирическое отступление. Модульность, вообще, — очень важная и полезная штука. И ее надо использовать для универсальности, удобности и легкости в понимании работы своих приложений. Но уникальных рецептов, как правильно все организовать, конечно, нет. Каждый делает по-своему. Именно по этим причинам я в своих уроках даю чисто технические вещи про отдельные компоненты и не рассказываю, как организовывать и писать целое приложение. Иначе, форум бы уже ломился от сообщений, что я все делаю не так и надо по-другому, и каждый бы излагал свое видение. И была бы куча споров, где одна сторона говорит, что крокодил зеленый, а другая сторона говорит, что он нифига не зеленый, а длинный ))
Вернемся к уроку. Фрагмент должен сообщить в Activity, что выбрана статья. Для этого он будет вызывать некий метод в Activity. И как нам сообщает хелп, лучший способ тут – это использовать интерфейс, который мы опишем в фрагменте и который затем будет реализован в Activity. Схема известная и распространенная. Давайте реализуем. В нашем приложении никаких статей нет, поэтому будем просто передавать произвольную строку из второго фрагмента в Activity. А Activity уже будет отображать эту строку в первом фрагменте.
Описываем интерфейс onSomeEventListener. В нем метод someEvent, который на вход получает строку. Этот интерфейс будет реализовывать Activity.
В методе onAttach мы на вход получаем Activity, к которому присоединен фрагмент. Мы пытаемся привести это Activity к типу интерфейса onSomeEventListener, чтобы можно было вызывать метод someEvent и передать туда строку. Теперь someEventListener ссылается на Activity.
Далее, в onCreateView, в обработчике кнопки мы вызываем метод someEvent и передаем туда текст. Этот метод будет отработан в Activity.
Теперь меняем Activity.
Дописываем интерфейс onSomeEventListener к описанию класса.
onCreate без изменений.
Реализуем метод someEvent. Просто ищем первый фрагмент и вставляем туда текст.
Все сохраняем и запускаем. Жмем кнопку во втором фрагменте:
Второй фрагмент передал через интерфейс строку в Activity, а оно нашло первый фрагмент и отобразило там эту строку.
На следующем уроке:
— размещаем элементы в ActionBar
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Коммуникация между Activity и Service
Нам нужно передавать данные из активити в сервис и обратно. Как нам это сделать? Для решения нашей задачи у нас уже есть все необходимое. Все что нужно — это привязать сервис к ативити, используя bindService, передать нужные параметры и немного магии в виде использования классов Message. А магия заключается в том, чтобы использовать переменные экземпляра Message и в частности, replyTo. Данная переменная нужна нам, чтобы мы могли обратиться к экземпляру Messanger сервиса из активити и в сервисе к экземпляру Messanger-а активити. На самом деле, не так уж и просто. По крайней мере для моего не самого одаренного ума. Отчасти, я улучшаю документацию, которая уже есть — Services. Улучшаю тем, что добавляю связь с активити, передавая данные туда-обратно, чего нет в документации. Также, есть хороший пример на StackOverflow. В любом случае, надеюсь статья будет полезна хоть кому-то и я потрудился не зря.
Пример
В качестве примера реализуем сервис, который будем увеличивать и уменьшать значение счетчика и возвращать результат в активити, в TextView. Код макета опущу, ибо там две кнопки и текстовое поле — все просто.
Реализация
Приведу полностью код активити:
Поясню. При создании активити мы сразу привязываемся к сервису, реализуя интерфейс ServiceConnection и в нем оправляем сообщение сервису «установить значение счетчика», передавая ноль и создавая toServiceMessanger, передавая в конструктор интерфейс IBinder. Кстати, в сервисе обязательно нужно вернуть этот экемпляр, иначе будет NPE. С помощью этого класса мы и отправляем сообщения сервису. И вот она магия — в переменную replyTo мы сохраняем наш другой экземпляр Messenger — тот который получает ответ от сервера и именно через него и будет осуществляться связь с активити.
Для получения сообщения от сервиса используем свой Handler и просто ищем нужные нам переменные и делаем по ним действия. По кликам на кнопки(методы countIncrClick, countDecrClick) отправляем запросы к сервису, указывая нужное действие в переменной msg.what.
Далее, полный код сервиса:
Все по аналогии с логикой в активити. Даже не знаю, нужно ли что-то пояснять. Единственный момент — это то, что я сразу отправляю запрос обратно в активити в handleMessage, используя для этого волшебную переменную replyTo и вытаскивая выше нужный Messenger. И второй момент о котором я уже говорил — это:
без которого все упадет. Именно данный экземпляр интерфейса и будет передан в ServiceConnection
Заключение
Вцелом все. Такой вот надуманный пример взаимодействия активити и сервиса. Мне кажется, довольно таки нетривиальное взаимодействие, хотя кому-то может показаться иначе.
Код проекта есть на Bitbucket
Вопросы, уточнения и прочее в личку. Могут быть неточности по поводу каких-либо аспектов, поэтому смело пишите и поправляйте.
Надеюсь, пост был полезен хабраюзерам.
Источник
Организация архитектуры взаимодействия Activity и Service
Сегодня я решил поведать Вам мой способ организации activity-service interaction в Android приложениях. Мотивирован топик тем, что достаточно часто можно встретить приложения, в которых, скажем, поход на сервер организовывается внутри активити в AsyncTask. При этом часто встречается верная мысль, что это надо делать в сервисах, но нигде в оф. документации нет ни слова об организации правильной архитектуры двустороннего взаимодействия между ними.
Поэтому я методом проб и ошибок пришел к архитектуре, лично для меня покрывающей все необходимые вопросы.
Об этом методе я буду рассказывать далее.
С высоты птичьего полета
Давайте сначала рассмотрим высокоуровневую картину предлагаемой архитектуры.
Далее в статье я буду использовать два термина — управляемая и неуправляемая обратная связь. Это неофициальные термины, но я их буду использовать, т. к. они мне нравятся. Управляемые — это уведомления, осуществляемые платформой Android для нас (ContentProvider + ContentObserver система). Для того, чтобы UI получал управляемые уведомления нам ничего не нужно, кроме корректно реализованного провайдера.
Гораздо интересней — как реализованы неуправляемые уведомления, т. е. те, которые осуществляются при помощи нашей системы событий. Ведь не всегда выполнение какой-то операции в сервисе сопряжено с записью в провайдер, поэтому нам нужен свой механизм уведомления клиента о том, что сервис завершил работу.
Итак, данная архитектура подразумевает наличие четырех основных компонентов системы:
- Activity, выполняющую стандартную роль отображения интерфейса
- Service — сервис, выполняющий тяжелую работу в background потоке
- ServiceHelper — наш компонент, который будет склеивать нашу активити и сервис и предоставлять неуправляемые уведомления
- ContentProvider — необязательный, в зависимости от вашего UI компонент, который будет помогать осуществлять управляемые уведомления.
Сервис
Наш сервис выполняет роль command processor’а.
Каждый входящий интент несет в extras:
- Действие, которое необходимо выполнить
- Аргументы, определяемые командой
- ResultReceiver
Сервис смотрит на переданный action, сопоставляет ему команду, которую нужно выполнить, и передает аргументы и ResultReceiver команде.
Самый простой вариант реализации сервиса:
Здесь в большом блоке if просто ищется нужная команда. Понятное дело, здесь можно как угодно загнаться, чтобы избежать ифа: держать Map action-handler, сделать фабрику, использовать IoC и т. п., но это выходит за рамки статьи.
Handler
Обработчики инкапсулируют в себе выполняемую процедуру. У меня они образуют определенную иерархию, где базовый класс выглядит как:
следующим уровнем иерархии я реализовал базовую команду, выполняющую подготовку http запроса, но это, опять же выходит за рамки статьи. В целом, Вы наследуетесь от базовой команды и реализуете doExecute, в котором при необходимости вызываете sendUpdate метод, передаете код (успех/ошибка) и Bundle с данными.
ServiceHelper
ServiceHelper — это промежуточный слой между UI и сервисом, упрощающий вызовы к сервису для UI, и выполняющий рутинные операции по упаковке интентов. Также он координирует координирует ответы от сервиса и содержит информацию о командах, выполняющихся в данный момент.
Итак, как это работает:
- UI вызывает метод хелпера, хелпер возвращает ID запроса
- Хелпер запоминает ID запроса
- Собирает Intent, в который вкладывет ResultReceiver и отправляет сервису
- когда сервис завершает операцию, в onReceiveResult оповещаются все слушающие UI компоненты
Давайте посмотрим на код:
Сервис хелпер держит список подписчиков в массиве, именно на этот список будут рассылаться уведомления по работе команд.
это — общий метод по созданию нашего интента, который мы зашлем сервису.
Более интересным местом является pendingActivities — это регистр всех выполняющихся на данный момент задач на сервисе. Поскольку при вызове метода ServiceHelper мы получаем id, мы всегда можем узнать, выполняется команда или нет. Подробней об этом — чуть далее в статье.
Теперь пример public метода, который будет выполнять какое-то действие на нашем сервисе:
и вот таких методов, торчащих наружу будет ровно столько, сколько команд поддерживает ваш сервис.
Activity
Итак, как я уже сказал, у нас есть интерфейс:
Я считаю, что удобно в базовой абстрактной активити реализовывать сей интерфейс. Тогда в конкретных активити Вам надо будет всего лишь переопределить метод onServiceCallback для получения уведомлений, что очень похоже на стандартные callback методы в activity, т. е. грациозно вписывается в Ваш клиентский код.
Обратите внимание, как активити подписывается и отписывается от ServiceHelper в своих методах onResume/onPause. Это позволяет избегать проблем при пересоздании активити, например, при повороте экрана, или сворачивании приложения.
Давайте рассмотрим, что нам приходит в метод onServiceCallback:
- requestId — уникальный идентификатор, сгенерированный при отправке запроса
- requestIntent — оригинальный интент, который мы послали
- resultCode — код результата выполнения
- resultData — данные
Теперь, у нас есть все необходимое, чтобы в нашей activity мы всегда могли получить уведомление от нашего сервиса без кучи boilerplate кода.
Более того, что мне кажется очень полезным — мы можем идентифицировать как конкретный запрос по ID, так и все запросы одного типа по action, что дает нам огромную гибкость.
Также, имея ID запроса, мы можем выполнять отложенную проверку. Представим последовательность:
- пользователь запустил действие, запустилась крутилка
- закрыл приложение на 2 минуты
- действие уже выполнилось
- пользователь открыл снова
- тут мы проверяем в onResume, выполнилась ли операция, и убираем крутилку
т. е., просто вызываем getServiceHelper().isPending(requestId), если нам это нужно.
Заключение
Вот, пожалуй, и все.
Сразу скажу, что я не претендую на универсальность данной архитектуры или на какую-то ее уникальность. Она была медленно выведена мной путем проб и ошибок, просмотров различных материалов и т. п. Но, пока, все мои нужды в настоящих коммерческих проектах она покрывает на 100%.
Более того, если чего-от не хватает — ее можно легко расширить. Из очевидного:
- добавить помимо success и failure код progress, тогда с сервиса можно будет передавать информацию о прогрессе задачи и отображать ее в, скажем ProgressBar
- прикрутить код по прерыванию выполняемой задачи
- и т. п.
Еще одна деталь, у меня код ServiceHelper не синхронизирован, т. к. подразумевается, что его методы будут вызываться в UI thread всегда. Если у Вас это не так, то необходимо добавить синхронизацию при любом изменении состояния ServiceHelper.
В общем, спасибо за внимание, надеюсь, кому-то поможет. Готов отвечать на Ваши вопросы и замечания в комментах.
UPD: Выложил маленький sandbox примерчик, иллюстрирующий архитектуру на GitHub: https://github.com/TheHiddenDuck/android-service-arch
UPD 2: Добавил пример реализации сервиса, работающего на пуле потоков
Источник