2012-05-13 10 views
48

В моих проектах я использую BroadcastReceiver s как обратный вызов из длинного потока (например, уведомляет о завершении загрузки и отправки некоторых данных ответа от Рабочего Thread, чтобы активность могла отображать соответствующее сообщение пользователю ..). Чтобы использовать BroadcastReceiver s, я должен быть осторожным, чтобы регистрировать и отменить регистрацию широковещательного приемника каждый раз, когда я его использую, а также заботиться о том, какие сообщения отправлять esspecialy, когда я использую этот метод для более разных действий (например, загрузка, создание WebService звонки и т. д.). А также для отправки пользовательских объектов через намерение Broadcast мне также нужно сделать объекты Parcelable.Android BroadcastReceiver или простой метод обратного вызова?

В отличие от этого подхода, я также видел подход методов обратного вызова, который кажется более простым, чем метод, который я использую. Способы обратного вызова - это простая реализация интерфейсных методов, которые могут использоваться для достижения такого же эффекта, как передача BroadcastRecaiver в приложении. Этот подход не требует реализации Parcelable для возврата сложных объектов, и он не использует такие ключи, как BroadcastReceiver .. Я думаю, что плохая часть заключается в том, что мне нужно проверить объект обратного вызова для нулевого значения, прежде чем я хочу вызвать метод обратного вызова. а также убедиться, что я запускаю код из реализации в потоке пользовательского интерфейса, поэтому я могу обновлять интерфейс без ошибок.

Хорошо, я надеюсь, вы поняли, что я хотел сказать :).

Теперь вопрос в том, как вы думаете, что метод обратного вызова лучше (легче, чище, быстрее ..), чем подход BroadcastReceiver, когда они используются только внутри одного приложения? (Обратите внимание, что я не использую Android Service для фоновых работ .. только AsyncTask и Thread s)

Спасибо!

ответ

78

Это очень интересный вопрос, и я столкнулся с той же проблемой. По-моему, оба механизма можно использовать в целом, и правильный подход к использованию зависит от вашего варианта использования. Вот некоторые моменты, которые необходимо принять во внимание, прежде чем принимать решение.

Использование обратного вызова механизм имеет некоторые преимущества, но есть и ограничения:

PRO

  • Это просто и прямо вперед в реализации.
  • Вы получаете безопасность типов между компонентами, которые взаимодействуют друг с другом.
  • Вы можете вернуть произвольные объекты.
  • Он упрощает тестирование, поскольку вам нужно только вводить макет-обратный вызов (например, сгенерированный через mockito или что-то подобное) в модульных тестах.

CONTRA

  • Вы должны переключиться в основной поток, чтобы сделать манипуляции UI.
  • У вас может быть только отношения 1 к 1. Связь 1-в-n (шаблон наблюдателя) не реализуется без дальнейшей работы. В этом случае я предпочел бы механизм Android Observer/Observable.
  • Как вы уже сказали, вы всегда должны проверить null перед вызовом функций обратного вызова, если обратный вызов может быть необязательным.
  • Если ваш компонент должен предложить своего рода API-интерфейс обслуживания с различными сервисными функциями, и вы не хотите иметь интерфейс обратного вызова только с несколькими общими функциями обратного вызова, вам необходимо решить, предоставлять ли вы специальный интерфейс обратного вызова для каждой функции обслуживания или вы предоставляете один интерфейс обратного вызова с множеством функций обратного вызова. В более позднем случае все клиенты обратного вызова, используемые для вызовов службы в ваш API, должны реализовать полный интерфейс обратного вызова, хотя большинство тел метода будет пустым. Вы можете обойти это, реализовав заглушку с пустыми телами и сделав ваш клиент обратного вызова наследованием от этого заглушки, но это невозможно, если он уже наследуется от другого базового класса. Возможно, вы можете использовать какой-то динамический обратный вызов прокси (см. http://developer.android.com/reference/java/lang/reflect/Proxy.html), но тогда он становится очень сложным, и я бы подумал об использовании другого механизма.
  • Клиент для вызовов обратного вызова должен распространяться через различные методы/компоненты, если он не доступен напрямую вызывающей стороне службы.

Некоторые моменты, касающиеся BroadcastReceiver -подходе:

PRO

  • Вы достигаете слабую связь между компонентами.
  • У вас может быть отношение 1-к-n (включая 1-к-0).
  • Метод onReceive() всегда выполняется на основной резьбе.
  • Вы можете уведомлять компоненты во всем своем приложении, поэтому коммуникационным компонентам не нужно «видеть» друг друга.

CONTRA

  • Это очень общий подход, так и сортировочный демаршаллинг данных, транспортируемого Intent является дополнительным источником ошибок.
  • Вы должны сделать свои действия Intent уникальными (например, добавив имя пакета), если вы хотите устранить корреляции с другими приложениями, поскольку их первоначальная цель - делать трансляции между приложениями.
  • Вы должны управлять BroadcastReceiver -правлением и регистрацией. Если вы хотите сделать это более удобным способом, вы можете реализовать пользовательскую аннотацию, чтобы аннотировать свою активность с действиями, которые должны быть зарегистрированы, и внедрить базовый класс Activity, который регистрирует и отменяет регистрацию с IntentFilter s в своем onResume() соответственно. onPause() методов.
  • Как вы уже сказали, данные, отправленные с Intent, должны реализовать интерфейс Parcelable, но, кроме того, существует строгое ограничение по размеру, и это вызовет проблемы с производительностью, если вы переносите большой объем данных с помощью вашего Intent. См. http://code.google.com/p/android/issues/detail?id=5878 для обсуждения этого вопроса. Поэтому, если вы хотите отправлять изображения, например, вы должны хранить их временно в репозитории и отправлять соответствующий идентификатор или URL-адрес для доступа к изображению из получателя вашего Intent, который удаляет его из репозитория после использования. Это приводит к дальнейшим проблемам, если есть несколько приемников (когда изображение должно быть удалено из хранилища и кто должен это делать?).
  • Если вы злоупотребляете этим механизмом уведомления, поток управления вашим приложением может быть скрыт, а при отладке вы закончите рисовать графики с последовательностями Intent s, чтобы понять, что вызвало определенную ошибку или почему эта цепочка уведомлений сломана на некоторых точка.

На мой взгляд, даже мобильное приложение должно иметь базу архитектуры на не менее 2-х уровнях: UI-слой и основной слой (с бизнес-логикой и т. Д.). В общем, длинные задачи выполняются в собственном потоке (возможно, через AsyncTask или HandlerThread при использовании MessageQueue s) внутри основного уровня, и пользовательский интерфейс должен обновляться после завершения этой задачи. В общем, с обратными вызовами вы достигаете жесткой связи между вашими компонентами, поэтому я предпочел бы использовать этот подход только внутри слоя, а не для связи через границы слоев. Для трансляции сообщений между UI- и базовым слоем я бы использовал BroadcastReceiver-Approach, который позволяет отделить ваш уровень пользовательского интерфейса от логического уровня.

+0

Я наградил это как ответ и дал ему щедрость, потому что это самый полный ответ. Спасибо всем за ваши мысли! – Cata

+0

Отличный ответ. Я поклонник шаблона обратного вызова/интерфейса, но я часто наследую проекты с широким использованием шаблона намерений Broadcast, и я часто задаюсь вопросом, не ошибаюсь ли я в этом. Приятно слышать, что на самом деле это вопрос предпочтений дизайна, а не лучшей практики. Мне нравится тип безопасности и четкость кода, предлагаемые интерфейсами, и я, вероятно, буду придерживаться их на данный момент –

+0

Очень интересное и подробное мнение +1 – Jorgesys

6

Я не вижу, что вы получаете, используя BroadcastReceiver в вашем случае. Возможно, обратные вызовы или, возможно, Handlers. BroadcastReceiver хорош, когда вы не знаете, кто подписчики.

+0

Спасибо за ваш ответ, хотя я подожду немного для получения дополнительных ответов. – Cata

2

Следует использовать Broadcastreceivers. Если вам нужно отправлять широковещательные передачи по всем приложениям, в то время как обратные вызовы (или обработчики, предложенные Алексом) лучше использовать в вашей ситуации.

Если вы хотите использовать другие, кроме этих двух, рассмотрите возможность использования Observer (Интерфейс включен в android) и делегата.

Для делегата просьба обращаться к this SO post.

3

Я просто добавить еще один вариант для других больших ответов вы получили уже ...

Вам не нужно создавать вещательный приемник для приема Намерения. В вашем андроид файла манифеста вы можете зарегистрировать любую активность, чтобы получить намерения:

<activity android:name=".MyActivity"> 
     <intent-filter > 
       <action android:name="intent.you.want.to.receive" /> 
       <category android:name="android.intent.category.DEFAULT" /> 
     </intent-filter>  
.... 
</activity> 

Затем переопределить метод onNewIntent(Intent) в своей деятельности, чтобы получить его.

Чтобы отправить намерение, используйте метод Context.startActivity(Intent). Скорее всего, вы захотите добавить флаг FLAG_ACTIVITY_SINGLE_TOP к своему намерению, чтобы он не создавал новый экземпляр вашей активности, если он уже запущен.

EDIT: Я заметил, что вы работаете в одном приложении. Поэтому простой обратный вызов, вероятно, лучше всего.Решение выше работает в одном приложении, но более подходит для разных приложений. Я оставлю это здесь на всякий случай, если это поможет кому-то. Удачи!

+0

Отличный совет!Я хотел бы добавить, однако, что тот же подход '' применим и к сервисам, если вы хотите изолировать не-пользовательские пользовательские реализации, и в этом случае вы бы назвали 'Context.startService (Intent) 'вместо этого. Если ваша целевая служба расширяет ['IntentService'] (http://developer.android.com/reference/android/app/IntentService.html), у вас будет« асинхронная очередь сообщений »бесплатно, поскольку« IntentService »будет только выполнить на одном «Intent» в то время, и метод onHandleIntent будет выполнен в рабочем потоке. – dbm

1

не уверен, в чем цель, но если вы хотите сохранить ту же идею использования намерений и трансляции радиоприемника и хотите повысить производительность и безопасность, чем обычные широковещательные радиоприемники, вы можете попробовать эту демонстрацию, доступную в библиотеке поддержки Android:

http://developer.android.com/resources/samples/Support4Demos/src/com/example/android/supportv4/content/LocalServiceBroadcaster.html

, если нет, то вы всегда можете использовать AsyncTask, обслуживание, погрузчики, и т.д. ...