Pull to refresh

Comments 37

Спасибо, интересно. А как вообще подобная проблема решается другими? Есть же open source приложения где тоже надо на сервер ходить.
Спасибо за вопрос. Я сильно не вдавался во многие open source приложения, но в них все по-разному, в зависимости от качества. Где-то люди используют AsyncTask, где-то броадкаст ресиверы, где-то — что-то похожее на описанное в статье. Тут, поймите, проблема не в самом походе в сервер, а именно в организации связанной архитектуры в своем приложении, где надо ходить на сервер.
Да, этот доклад мне в свое время очень помог, всем рекомендую. Проблема этого доклада, что там совершенно не описывались детали обратного взаимодействия сервиса с хелпером, т. е. он сказал «binder callback», а что это такое, как его писать — нет.
У меня, например, есть класс для запроса API, в котором реализованы методы для выполнения запроса с вызовом callback на главном потоке/на фоновом/и.т.д. (в общем, аналог AsyncTask, но самописный), на каждый возможный запрос — свой наследующийся от него класс, который задает параметры, парсит ответ от сервера и выдает в callback уже готовые объекты с данными.
Я так раньше делал, пытался выделять каллбэки для подобного рода библиотек. Сейчас только три пакета на подобную библиотеку: запросы, просто возвращают прямо, синхронно, String — ответ, буть то json, xml или что-то другое, потом другой пакет:Objects и Responses, который берут эту String и выдают java Objects с полями, ну и главный пакет, который просто имееть какие константы обще доступные и *MyLIB*Exception, который используется везде в библиотеке, а так же для обработки ответа с ошибкой. Думаю так лучше, а callback для приложения оставить, в каждом приложении может иметь смысл по разному вызывать синхронные, долговременные операции, буть то поток, буть то очередь или как то иначе, да и callback может быть разный, listener или intent и др.
А так ли необходимо использовать Intent'ы если и Activity и сервис находятся в одном приложении (т.е. исполняются в одном процессе)?
Activity ведь может получить ссылку на инстанс сервиса через биндинг. Также активити может быть уведомлена об остановке сервиса, что даст возможность корректно подчищать ссылки. Таким образом мы получим связь активити->сервис.
Ну а имея такую связь уже можно создать листенер для сервиса, и подписать активити на уведомления сервиса, получив таким образом обратную связь сервис->активити.
В результате мы получим быстрый, простой и прозрачный механизм взаимодействия (я не упомянул об механизме обработки сообщений сервисом — при такой схеме можно делать как заблагорассудиться — как вариант создать специальный рабочий поток и в нем через Handler уже обрабатывать команды).
По поводу биндинга сервисов: stackoverflow.com/questions/4908267/communicate-with-activity-from-service-localservice-android-best-practices

Крутой спец по андроиду CommonsWare считает их неоправданными для этой цели и советует их не использовать:
I wouldn't. Use the loosest possible coupling you can stand. Hence, on average, aim for the command pattern with startService() instead of the binding pattern with bindService().


Для себя решил использовать для связи:
Activity -> Service: Intent
Service -> Activity: LocalBroadcastManager
А вообще, как тут писал он же, есть три способа взаимодействия с сервисами:
  1. с помощью интентов;
  2. с помощью AIDL;
  3. используя непосредственно объект сервиса (как сингтон).


AIDL сложен и имеет смысл его использовать только если сервис находится в другом процессе (Remote). Для локальных сервисов остаются варианты 1 и 3.
Мне кажется остается вариант 1, потому что 3й это уж слишком конкретно. Я имею ввиду, что вы конкретно говорите, вот имя класса, вот метод, и он должен что-то вернуть (instance). Тут сразу минусы видны и не кто не гарантирует что сервис был создан, что он сейчас запущен, что ссылка через singleton будет актуальна. В свою очередь Intent, это не привязанность к какому либо классу, просто описание правил как читать поля и что с ними делать, тут только плюсы, можно даже поменять или удалить сервис, часть кода с Intent не пострадает.
Посмотрите мой ответ ниже — все хорошо там получается.
Не очень хорошо этот спец советует использовать синглтон для сервиса и метод ожидания запуска сервиса.
Вместо синглтона нужно использовать класс Binder, возвращающий ссылку на инстанс сервиса, а вместо ожидания пока MyService.getInstance() возвращает null, использовать ServiceConnection.
Пример можно посмотреть например здесь: http://stackoverflow.com/questions/5731387/bindind-to-the-same-service-instance-in-android
Да, это один из нормальных путей общения с сервисом. Так же этот вариант гарантирует интересующимуся когда сервис доступен для отсылки Intent, ну или же вызова метода через Binder. Согласен полностью, зависит от задачи.
Во-первых, как я уже сказал, я не претендую, что это — единственный верный способ. Я использую интенты, т. к., во-первых, это самый рекомендованный и стандартный способ посылки сообщений сервису, во-вторых, это самый простой, не требующий boilerplate кода способ. Локальный биндинг — вещь в целом неплохая, но он привязывает сервис к жизненному циклу активити, напрямую связывает их, требует отслеживания событий биндинга/анбиндинга и т. п. Я вообще восхищаюсь системой сообщений в андроид, и считаю, что по возможности лучше придерживаться ее. Кроме того, используя способ в статье, вы сможете очень легко перенести сервис в другой процесс, если надо (одной строчкой в манифесте). ResultReceiver бьет через процессы, все работает, а вот локальный биндинг тут уже придется менять на AIDL или Messenger, что не так тривиально.

По-поводу делать как хотим в сервисе — а сейчас разве не так? В данном примере у меня IntentService, он работает через Handler. Если надо, можно заменить на свой сервис, который будет работать, скажем на ExecutorService, все точно так же.
Я также ни на что не претендую. Но считаю приведенную мной выше информацию желательной для ознакомления при рассмотрении методов взаимодействия активити и сервиса.

По поводу Вашего кода — я не вникал в детали и не заметил, что там используется Handler. Все таки готовый пример был бы более наглядным.

[offtopic]У Вас новый Handler создается в потоке вызывающего кода (т.е. UI поток активити скорее всего) — я правильно понимаю? Если так, то обрабатываться Handler будет в этом-же потоке… Поправьте если я не прав[/offtopic]
В детали не вникали, а минусовать, смотрю, горазды. В моем коде используется IntentService, который основан внутри на хендлере. Этот хендлер создается на лупере WorkerThread'a. Ознакомьтесь с исходниками IntentService.
Мне кажется r_ii про хэндлер, который передается в ResultReceiver в конструктор, он там действительно для того, чтобы на нем вызывать onReceiveResult, поэтому onReceiveResult будет выполнен в том же потоке, из которого вызвался createIntent. А вообще каждый раз создавать новый Handler там тоже не нужно :)
Если про этот, то да, ответ выполняется в UI потоке, и так и должно быть, т. к. уже непосредственно будет модифицироваться UI. По-поводу хендлера, да, Вы правы, каждый раз создавать новый инстанс бессмысленно, мой недочет. Ничего смертельного, конечно, но ненужные объекты, в общем-то не нужны :)
Ну в таком случае оффтопик можно считать закрытым. Спасибо за прояснение ситуации.
P.S. пример с исходниками сильно помог бы вникнуть в суть.
ResultReceiver бьет через процессы

Только если свое приложение работает в нескольких процессах. Иначе будет падать эксепшн в intent.getParcelableExtra, т.к. класс такой не найдет.
Да, но когда я говорил, о том, что нужно в манифесте своего приложения добавить одну строчку, я именно об одном приложении и говорил, очевидно :) О взаимодействии разных package-ей — другая история, и у меня как раз назревает статья о том, как я делал плагин, играющий аудио семплы к своему MIDI приложению. Там как раз отдельный APK, другой пакет процесс, и т. п.
В книге «Рето Майер — Android 2. Программирование приложений» неплохо описаны состояния activity и взаимодействие с сервисами. Советую почитать, как дополнение к статье.
спасибо за статью. планировал написать похожую, т.к. часто использую аналогичную архитектуру. и вот вы меня избавили от такой необходимости :)

вопрос по поводу расширения
«прикрутить код по прерыванию выполняемой задачи»

Какого либо элегантного способа прерывания IntentService я не нашел. stopService() приводит к вызову onDestroy() сервиса, однако working tread продолжает исполнение до полного завершения обработки интента.
Не поделитесь опытoм как вы реализовали или планировали реализовать прерывание? спасибо.
С интент сервисом особо элегантного способа и нет. Однако, простейшее прерывание можно организовать и на нем. Как я себе вижу:

Во-первых, командам добавляем флаг isCancelled и проверяем его во время выполнения команды, если он == true, немедленно прекращаем все действия. Т. к. в интент сервисе мы всегда знаем, какая команда выполняется, мы можем просто выставить ей этот флаг. Для этого просто оверрайдим onStartCommand(...) и, если приходит ACTION_CANCEL, тормозим текущую команду, иначе super.onStartCommand(...). Разумеется, если команда уже выполняется и, скажем, в середине HTTP запроса на 10 секунд (что тоже не фонтан, конечно, но бывает), то дождаться ее завершения в любом случае придется, ничего не поделаешь.

Если нужны более продвинутые прерывания, лучше всего сделать свой сервис и использовать пул потоков (ExecutorService). Там у нас всегда будет future выполняемых тасков и мы можем интерраптнуть выполнение. Организовать очередь по-прежнему можно при помощи newFixedThreadPool, а, если надо — всегда можем держать несколько потоков. Единственное — в этой ситуации (параллельное выполнение) лучше избегать stopService, а использовать именно stopSelf, когда завершает выполнение последняя задача завершится. Лично я предпочитаю всегда добавлять cooldown на минутку прежде, чем окончательно убивать сервис. Если за минутку приходит новая таска, кулдаун сбрасывается, и мы избегаем лишних завершений и стартов сервиса. Это легко делается Handler'ом.
Спасибо!
Давно пытаюсь унифицировать способ работы с вебсервисами.
GoogleIO про работу с веб сервисами + ioshed дало стандарт, но все-равно сложно до конца все понять в их app.
Было бы очень круто, если бы вы выложили или дали ссылочку на код программы, где реализован ваш подход.
Может у вас есть некий макет, которым вам не жалко поделиться.
Код всей программы выложить не могу, т. к. это коммерческий продукт. В общем, код приведенный в статье содержит все основные элементы фреймворка, используемого в программе (с измененными именами переменных, но смыслом тем же), осталось собрать в кучу. Я постараюсь, как время будет, слепить простенький примерчик, основанный на этом подходе.
Да, я понимаю.
Примерчик было бы очень суперово)
Готов примерчик, ссылка в конце статьи.
в ioschede вроде все даже проще. Там есть один SyncService, который выполняет последовательно синхронизацию данных из разных источников. То есть берет из источника и пишет в ContentProvider.
Там последовательное выполнение операций, а в данной архитектуре я так понимаю можно запустить параллельно несколько действий, что бывает удобно.
А может можно проще? Наследовать и раширить объект Application. Вставить в него видимые ВСЕМ поля — и сервисам, и активностям. И использовать эти поля в синхронизированном режиме и через них обмениваться? Чем плох такой подход?
Как говорит документация All requests are handled on a single worker thread — they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time. То есть предложенный подход не позволяет выполнять 2 и более запросов одновременно. Верно?
Если включить мозг и понять, что предложенный подход нигде ничем не обязывает использовать IntentService, то можно прийти к выводу, что нет, позволяет, только нужно реализовать свой сервис, скажем, с ExecutorService.
только нужно реализовать свой сервис, скажем, с ExecutorService
Именно так я и сделал.

Спасибо за совет, даже не смотря на форму, в которой он был дан.
Рад, что смог помочь. Сорри за форму, так получилось, что писал коммент, находясь в не самом лучшем настроении. Как извинение держите плюсик.
Sign up to leave a comment.

Articles