Комментарии 21
Был на Google IO 2014, спросил вопрос о том, как же делать callback из сервиса в фрагменты или активити. Т.е. идея в том, чтобы Activity -> Service. Тут в принципе понятно, тот же IntentService. А вот как обратно? Спрашивал нужно ли broadcast messaging или может сразу singleton manager к которому можно подписываться и слушать результаты. Сказали что они делают manager повсеместно внутри фреймворка, разумеется если ситуация в одном процессе. Так, что пока держусь этого курса. Если данные не особо критичны, не должны отображать 100% инфу с сервера, то пускать как описано в статье через DB кэш. Линк Activity -> Loader -> DB. Как только DB получает данные, отправить локальный броадкаст, чтобы Loader перезапустился.
Как то так и живем.
Как то так и живем.
В случае IntentService задачу решает ResultReceiver.
Так ведь в этом случае, Activity получит onDestroy и всеравно потом получит onReceiveResult. Если так, то не решает проблему. Поправьте меня если не прав, я не использую ResultReceiver.
Одна из проблем, которую я заметил используя другой OttoBus, то, что не явно кто отсылает и кто получает сообщения. Хотя, наверное это дело предпочтения больше. Я посмотрю на EventBus, может он другой подход использует. Спасибо.
НЛО прилетело и опубликовало эту надпись здесь
Действительно, очень удобная вещь.
Имеет смысл не лениться и для каждого события сделать отдельный класс, это событие описывающее — тогда не будет проблем с разбором того, кто что хотел и кто что получил.
А так как это класс — то туда легко и удобно добавить полученный результат запроса. При этом активити просто просит сервис что-то сделать — и ждёт event (Event)соответствующего запросу типа. Получив этот event, вызывает у него, к примеру, getData() — и в путь.
Ну и, более того, ещё удобнее в этот же Event добавить информацию о том, успешно ли выполнился запрос. В этом случае активити вначале спросит — эй, event.hasError()?
Кроме того часто бывает возможным такой сценарий: однообразные запросы формируются при выборе пользователем каких-то элементов из списка — ну, например, загрузка выписки по банковской карте.
Вполне может быть так, что пользователь кликнул по одной, ждать ему надоело, он кликнул по другой. А сразу после этого данные по первой карте загрузились и в активити прилетел соответствующий event.
Таким образом видим, что надо как-то отслеживать контекст запроса — и игнорировать ответ, если этот ответ устаревший.
Наиболее простой способ — добавить в event ещё одно поле — requestId.
Активити при вызове сервиса формирует (логично — что UUID) значение requestId и вместе с другими параметрами передаёт его на/в сервис. Сервис по окончании обработки создаёт event, устанавливает ему результат, признак ошибки, бла-бла-бла — и этот самый requestId.
А активити теперь получает event и сравнивает — requestId у этого event такой же, как только что сделанный? Если нет — значит игнорируем event этот, не нужен он больше…
Имеет смысл не лениться и для каждого события сделать отдельный класс, это событие описывающее — тогда не будет проблем с разбором того, кто что хотел и кто что получил.
А так как это класс — то туда легко и удобно добавить полученный результат запроса. При этом активити просто просит сервис что-то сделать — и ждёт event (Event)соответствующего запросу типа. Получив этот event, вызывает у него, к примеру, getData() — и в путь.
Ну и, более того, ещё удобнее в этот же Event добавить информацию о том, успешно ли выполнился запрос. В этом случае активити вначале спросит — эй, event.hasError()?
Кроме того часто бывает возможным такой сценарий: однообразные запросы формируются при выборе пользователем каких-то элементов из списка — ну, например, загрузка выписки по банковской карте.
Вполне может быть так, что пользователь кликнул по одной, ждать ему надоело, он кликнул по другой. А сразу после этого данные по первой карте загрузились и в активити прилетел соответствующий event.
Таким образом видим, что надо как-то отслеживать контекст запроса — и игнорировать ответ, если этот ответ устаревший.
Наиболее простой способ — добавить в event ещё одно поле — requestId.
Активити при вызове сервиса формирует (логично — что UUID) значение requestId и вместе с другими параметрами передаёт его на/в сервис. Сервис по окончании обработки создаёт event, устанавливает ему результат, признак ошибки, бла-бла-бла — и этот самый requestId.
А активити теперь получает event и сравнивает — requestId у этого event такой же, как только что сделанный? Если нет — значит игнорируем event этот, не нужен он больше…
Для этого достаточно объявить два компонента, описанных с помощью AIDL. Первый — тип передаваемых данных (по факту — простая линковка на .java класс, который будет передаваться, обязательно должен расширять Parcelable), второй — описание коллбэка. Далее будет сгенерирован класс, соответствующий описанному посредством AIDL коллбэку, с которым уже просто взаимодействовать. Много сложнее вопрос, как мне кажется, с реализацией аналогичного общения с оповещением из сервиса конкретных рецепиентов, в силу сложности контролирования их жц
С точки зрения Virgil Dobjanschi более удачный пример реализации паттерна B — паттерн С.
В презентации кстати сказано: «The last pattern is simply a variant of the previous one. We’re still going to use the Content Provider API, but we’re going to use the help of a Sync Adapter.The Sync Adapter is a concept that you should learn as soon as you get home and you start developing for applications....All our applications use the concept of the Sync Adapter to refresh content. Gmail, e-mail, all these apps use that particular concept. Please use it in your apps.»
Так что думаю реализовать паттерн B, если за 4 года этим никто не озаботился, смысла нет. Приятной наградой за труды в случае реализации паттерна С будет попадание приложения в раздел синхронизация в настройках.
Хотя на самом деле интересно провести голосование. Думаю большинство даже паттерн A не используют в том виде в котором как он представлен на схеме, и думаю они правы :)
В презентации кстати сказано: «The last pattern is simply a variant of the previous one. We’re still going to use the Content Provider API, but we’re going to use the help of a Sync Adapter.The Sync Adapter is a concept that you should learn as soon as you get home and you start developing for applications....All our applications use the concept of the Sync Adapter to refresh content. Gmail, e-mail, all these apps use that particular concept. Please use it in your apps.»
Так что думаю реализовать паттерн B, если за 4 года этим никто не озаботился, смысла нет. Приятной наградой за труды в случае реализации паттерна С будет попадание приложения в раздел синхронизация в настройках.
Хотя на самом деле интересно провести голосование. Думаю большинство даже паттерн A не используют в том виде в котором как он представлен на схеме, и думаю они правы :)
pattern C, безусловно, выглядит функциональнее. Но эту тему уже Даниил раскрыл :-) habrahabr.ru/company/e-Legion/blog/216857/
Сейчас работаю над проектом по паттерну B. В основном выбор на него пал как раз из-за того, что Content Provider — это фассад, сильно упрощающий логику активити и фрагментов. В GUI самое «сложное» — это отреагировать на признак «строка не синхронизирована». «Сложное» в кавычках, т.к. сложность заключается только в том, что в отдельных случаях нужно применять другие View, и все. Данные обновляются => идет оповещение слушателям => Loader'ы их перечитывают и GUI вы перерисовываете. Удобнее сложно придумать. Опять же нет проблем с выходом из активти и повторным входом в нее — у вас все результаты в кеше, вы все перечитаете и продолжите работу без потери каких-либо данных. Но самое главное — это фассад. В моем приложении данные хранятся не только в БД, а еще и в файловой системе, там довольно специфично все. И этот фасад дает шикарное разделения слоя GUI и DAO. Причем Content Provider у меня ничего не делает, кроме того, что следит за синхроноостью данных в базе, в файловой системе и вовремя вызывает сервис для синхронизации с сервером. Какая-либо логика работы с этими разношерстными данными вынесена в Content Helper'ы — промежуточный слой между Activity и Content Provider'ами. Эти хелперы отвечают за построения правильных запрсов к провайдерам и при необходимости вызывают и переиспользуют друг друга.
очень интересно, спасибо :-)
а Вы используете в реализации паттерна сторонние библиотеки или всё сами пишете?
и я правильно понял, что данные Вы сохраняете по инициализации PUT-запроса, а оповещение слушателям и обновление Loader'ов происходит после ответа сервера об их успешной обработке? или Вы на сервер ничего не пишете, а только читаете?
а Вы используете в реализации паттерна сторонние библиотеки или всё сами пишете?
и я правильно понял, что данные Вы сохраняете по инициализации PUT-запроса, а оповещение слушателям и обновление Loader'ов происходит после ответа сервера об их успешной обработке? или Вы на сервер ничего не пишете, а только читаете?
Пишем сами. Мое личное мнение — андроид — не та система, где стоит создавать библиотеки над библиотеками. Родные компоненты работают достаточно неплохо, а сторонние надстройки добавляют тормозов и багов. У нас полноценный REST-протокол с GET, POST, PUT и DELETE. В случае POST, PUT, DELETE первым шагом делаются изменения в локальной БД. POST приводит к появлению новой записи с заполнением реквизитов, пришедших из Activity. Запись помечается как status = «INSERTING». PUT — меняет существующую и помечает ее как «UPDATING». Delete только помечает запись как «DELETING». Этим занимается ContentProvider. Затем ContentProvider дергает сервис, чтобы сервис в фоновом потоке вызвал API. Сам ContentProvider при этом отправляет слушателям URI этой таблички уведомление о том, что данные изменились (статусы ведь поменялись) и завершает работу, не дожидаясь ответа сервиса. При этом если в GUI реально есть слушатели этой таблички, то они ее перечитают и покажут юзеру, что с таким-то объектом сейчас идет какая-то работа. Этот элемент будет недоступен для изменений, кликов, будет отмечен серым цветом и может даже крутящееся колесико будет в версии 1.2 :) Сервис же неспеша дернет API, получит ответ, положит его в таблицу путем вызова того же Content Provider'а уже из сервиса. POST, PUT обновят уже существующую запись, DELETE окончательно ее удалит. В случае POST, PUT Content Provider также меняет статус записи на «READY». После этих изменений Content Provider снова отправляет слушателям URI этой таблички уведомление о том, что данные изменились. Элементы GUI ее перечитают и покажут юзеру, что данные опять изменились (объект готов к работе, можно убирать колесико и разрешать кликать на него). На каждом из этих этапов данные персистенты и отвязаны от жизненного цикла GUI. И дает возможность строить GUI логику максимально гибко, т.к. нигде в этом цикле мы не блокировали GUI целиком, а только отдельные элементы, связанные с непосредственно обрабатываемыми данными. Это позволяет запускать одновременно любое количество операций в фоне, если конечно логика приложения это допускает.
Интересно. Но я так и не использую Content Provider, т.к. зачем если он только и делает, что привносит сложность в проект. Если есть SQLite, то зачем ее крыть Content Provider'ом. Из документации:
Или вы делитесь информацией из вашего приложения с другими приложениями?
Decide if you need a content provider. You need to build a content provider if you want to provide one or more of the following features:
You want to offer complex data or files to other applications.
You want to allow users to copy complex data from your app into other apps.
You want to provide custom search suggestions using the search framework.
You don't need a provider to use an SQLite database if the use is entirely within your own application.
Или вы делитесь информацией из вашего приложения с другими приложениями?
Пока не делимся. Но это ж не значит, что потом не захотим :) Если сразу будет Content Provider, писать придется меньше в итоге. Но это не основаня причина, почему я люблю Content Provider'ы в связке с CursorLoader'ами. Они упрощают жизненный цикл GUI. Вам не нужно самостоятельно заботиться о том, чтобы правильно и вовремя закрыть курсоры. При изменении конфигурации и пересоздании Activity Loader не разрушается. Новый экземпляр Activity присоединится к существующему экземпляру Loader'а и получит либо уже готовый курсор, либо дождется окончания запроса, отправленного еще предыдущим экземпляром Activity. Они естественным образом добавляют асинхронность в Ваш проект. Т.е. вы создали CursorLoader, получили курсор из ContentProvider'а, отрисовали ListView к примеру. Дальше где-то в другом фрагменте данные изменились (или вообще пришли обновления от API через сервис). Тот фрагмент/сервис независимо ни от кого обратился к ContentProvider'у, поменял данные в таблице. ContentProvider отправил оповещения Вашему CursorLoader'у, что данные изменились. Ваш Loader сам перечитал данные, обратившись к ContentProvider'у, получил новый курсор, заменил курсор у ListView — и Вы получили согласованность данных в разных фрагментах при том, что фрагменты между собой никак не взаимодействовали. В планшетном приложении таких фрагментов на экране может быть десяток, и здесь их независимость, асинхронность и при этом согласованность реально спасает. Это очень хорошо масштабируемая схема. И еще один плюс, о котором я писал в прошлом комментарии — это согласованность данных на уровне хранилища. У меня в приложении 3 источника данных — SQLite, файлы и REST API. ContentProvider служит фассадом для них всех, и GUI слой понятия не имеет, где данные реально хранятся. Нужно будет добавить/убрать/изменить хранилище — я смогу это сделать без каких-либо изменений GUI, т.к. у меня есть промежуточная абстракция данных, на которой изменения будут локализованы.
Здравствуйте!
Хоть и прошло уже достаточно много времени но до сих пор не находится ничего более подробно описывающее RESTful шаблоны на Android с поддержкой синхронизации, чем их описание от Virgil Dobjanschi на Google IO 2010. А точнее его предложения по реализации Pattern A/B/C. Или все пути так или иначе ведут к ним.
Но никак не могу найти работающие примеры кода, реализующие основные компоненты.
Pattern A — хоть и написано что это наиболее всречающийся, не нашёл.
Описываемый в статье Pattern B FinchVideo не работает, что-то не так с парсером YouTube, видимо с 2012 г. когда вышла книга с данным примером что-то в API YouTube изменилось.
Про загадочный Pattern С ещё меньше информации до сих пор.
Есть ли работающие примеры любого из шаблонов желательно с минимальным посторонним функционалом?
Хоть и прошло уже достаточно много времени но до сих пор не находится ничего более подробно описывающее RESTful шаблоны на Android с поддержкой синхронизации, чем их описание от Virgil Dobjanschi на Google IO 2010. А точнее его предложения по реализации Pattern A/B/C. Или все пути так или иначе ведут к ним.
Но никак не могу найти работающие примеры кода, реализующие основные компоненты.
Pattern A — хоть и написано что это наиболее всречающийся, не нашёл.
Описываемый в статье Pattern B FinchVideo не работает, что-то не так с парсером YouTube, видимо с 2012 г. когда вышла книга с данным примером что-то в API YouTube изменилось.
Про загадочный Pattern С ещё меньше информации до сих пор.
Есть ли работающие примеры любого из шаблонов желательно с минимальным посторонним функционалом?
Могу лишь предложить Вам посмотреть не так давно появившийся доклад Yigit Boyar-а о чистой архитектуре в андроиде с современными фреймворками. Линки на запись доклада и на исходники проекта. Это конечно не совсем то, что Вы просили, но, думаю, что в плане прикладном должно Вас удовлетворить.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
RESTful API под Android: pattern B