Android-эквивалент NSNotificationCenter

95

В процессе переноса приложения с iPhone на Android я ищу лучший способ общения внутри приложения. Намерения кажутся правильным решением, это лучший (единственный) вариант? NSUserDefaults кажется намного легче, чем Intents, как с точки зрения производительности, так и с точки зрения кодирования.

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

Джон
источник
3
Для новичков в этой теме второй ответ - лучший. Прокрутите вниз ...
Стефан

Ответы:

6

Вы можете попробовать это: http://developer.android.com/reference/java/util/Observer.html

Руи Перес
источник
42
Ответ Шики ниже намного лучше.
dsaff 02
5
@dsaff, несмотря на то, что это более полный ответ, я ни в коем случае не ошибаюсь, я явно не заслуживаю -1. Что для вас имеет смысл, так это +1 ответ Шики.
Rui Peres
4
Шики - лучший ответ на вопрос
Рамз
4
Обратите внимание, что отрицать следует только технически неправильные и спам-ответы - этот не подходит ни к одному из них. +1 за компенсацию и +1 за Шики, потому что это отличный ответ.
351

Лучшим эквивалентом, который я нашел, является LocalBroadcastManager, который является частью пакета поддержки Android. .

Из документации LocalBroadcastManager:

Помощник для регистрации и отправки рассылки намерений локальным объектам в вашем процессе. Это имеет ряд преимуществ перед отправкой глобальных трансляций с помощью sendBroadcast (Intent):

  • Вы знаете, что данные, которые вы транслируете, не покинут ваше приложение, поэтому не нужно беспокоиться об утечке личных данных.
  • Другие приложения не могут отправлять эти широковещательные сообщения в ваше приложение, поэтому вам не нужно беспокоиться о наличии дыр в безопасности, которые они могут использовать.
  • Это более эффективно, чем отправка глобальной трансляции через систему.

Используя это, вы можете сказать, что Intentэто эквивалент NSNotification. Вот пример:

ReceiverActivity.java

Действие, отслеживающее уведомления о названном событии "custom-event-name".

@Override
public void onCreate(Bundle savedInstanceState) {

  ...
  
  // Register to receive messages.
  // This is just like [[NSNotificationCenter defaultCenter] addObserver:...]
  // We are registering an observer (mMessageReceiver) to receive Intents
  // with actions named "custom-event-name".
  LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
      new IntentFilter("custom-event-name"));
}

// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Get extra data included in the Intent
    String message = intent.getStringExtra("message");
    Log.d("receiver", "Got message: " + message);
  }
};

@Override
protected void onDestroy() {
  // Unregister since the activity is about to be closed.
  // This is somewhat like [[NSNotificationCenter defaultCenter] removeObserver:name:object:] 
  LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
  super.onDestroy();
}

SenderActivity.java

Вторая активность, которая отправляет / рассылает уведомления.

@Override
public void onCreate(Bundle savedInstanceState) {
  
  ...
  
  // Every time a button is clicked, we want to broadcast a notification.
  findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      sendMessage();
    }
  });
}

// Send an Intent with an action named "custom-event-name". The Intent sent should 
// be received by the ReceiverActivity.
private void sendMessage() {
  Log.d("sender", "Broadcasting message");
  Intent intent = new Intent("custom-event-name");
  // You can also include some extra data.
  intent.putExtra("message", "This is my message!");
  LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

В приведенном выше коде каждый раз, когда нажимается кнопка R.id.button_send, транслируется намерение, которое получает mMessageReceiverin ReceiverActivity.

Результат отладки должен выглядеть так:

01-16 10:35:42.413: D/sender(356): Broadcasting message
01-16 10:35:42.421: D/receiver(356): Got message: This is my message! 
Шики
источник
11
Большое спасибо за то, что нашли время написать такой полезный и подробный ответ.
Chris Lacy
14
Вероятно, вам не следует вызывать registerReceiver в своем методе onCreate, так как это приведет к утечке вашей Activity и ваш метод onDestroy никогда не будет вызван. onResume кажется лучшим выбором для вызова registerReceiver, а onPause - для вызова unregisterReceiver.
Stephane JAIS
4
Идеальный эквивалент NSNotificationCenter, должен быть принятым ответом!
Леон Стори,
Хочу отметить, что использование глобальных уведомлений может привести к путанице в дизайне. Прежде чем переходить к более легкому пути, подумайте о том, как лучше всего соединить ваши компоненты. Иногда лучше использовать слушателей или что-то похожее на шаблон делегата iOS и так далее.
saulobrito
Спасибо, это сработало для меня. @Shiki, как вы думаете, вы могли бы высказать мне свое мнение по этому вопросу stackoverflow.com/questions/25598696/…
Аксель
16

Вот что-то похожее на ответ @Shiki, но с точки зрения разработчиков iOS и Центра уведомлений.

Сначала создайте какую-нибудь службу NotificationCenter:

public class NotificationCenter {

 public static void addObserver(Context context, NotificationType notification, BroadcastReceiver responseHandler) {
    LocalBroadcastManager.getInstance(context).registerReceiver(responseHandler, new IntentFilter(notification.name()));
 }

 public static void removeObserver(Context context, BroadcastReceiver responseHandler) {
    LocalBroadcastManager.getInstance(context).unregisterReceiver(responseHandler);
 }

 public static void postNotification(Context context, NotificationType notification, HashMap<String, String> params) {
    Intent intent = new Intent(notification.name());
    // insert parameters if needed
    for(Map.Entry<String, String> entry : params.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        intent.putExtra(key, value);
    }
    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
 }
}

Затем вам также понадобится некоторый тип перечисления для защиты от ошибок при кодировании со строками - (NotificationType):

public enum NotificationType {

   LoginResponse;
   // Others

}

Вот пример использования (добавление / удаление наблюдателей) в действиях:

public class LoginActivity extends AppCompatActivity{

    private BroadcastReceiver loginResponseReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
           // do what you need to do with parameters that you sent with notification

           //here is example how to get parameter "isSuccess" that is sent with notification
           Boolean result = Boolean.valueOf(intent.getStringExtra("isSuccess"));
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        //subscribe to notifications listener in onCreate of activity
        NotificationCenter.addObserver(this, NotificationType.LoginResponse, loginResponseReceiver);
    }

    @Override
    protected void onDestroy() {
        // Don't forget to unsubscribe from notifications listener
        NotificationCenter.removeObserver(this, loginResponseReceiver);
        super.onDestroy();
    }
}

и вот, наконец, как мы отправляем уведомление в NotificationCenter из какой-либо службы обратного вызова или отдыха или чего-то еще:

public void loginService(final Context context, String username, String password) {
    //do some async work, or rest call etc.
    //...

    //on response, when we want to trigger and send notification that our job is finished
    HashMap<String,String> params = new HashMap<String, String>();          
    params.put("isSuccess", String.valueOf(false));
    NotificationCenter.postNotification(context, NotificationType.LoginResponse, params);
}

вот и все, ура!

Элвис Рудонья
источник
Спасибо за решение! Я обнаружил, что использование Bundle paramsвместо HashMapболее удобно для передачи параметров разных типов. Существует связь между хорошо Intentи Bundle:intent.putExtras(params)
Зубко
4

Вы можете использовать это: http://developer.android.com/reference/android/content/BroadcastReceiver.html , что дает аналогичное поведение.

Вы можете зарегистрировать получателей программно через Context.registerReceiver (BroadcastReceiver, IntentFilter), и он будет фиксировать намерения, отправленные через Context.sendBroadcast (Intent).

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

AngraX
источник
Небольшое примечание по проектированию: BroadcastReceivers и NSNotificationCenter могут работать как агрегатор событий. Преимущество перед делегатами или наблюдателями заключается в том, что отправитель и получатель разделены (на самом деле у них есть связь между сообщениями или данными, но это один из самых слабых типов связи). Отредактировано с исправлением.
AngraX
4

Я обнаружил, что использование EventBus библиотеки Guava - это самый простой способ для связи между компонентами в стиле публикации-подписки, не требуя, чтобы компоненты явно регистрировались друг с другом.

см. их образец на https://code.google.com/p/guava-libraries/wiki/EventBusExplained

// Class is typically registered by the container.
class EventBusChangeRecorder {
  @Subscribe public void recordCustomerChange(ChangeEvent e) {
    recordChange(e.getChange());
  }

// somewhere during initialization
eventBus.register(this);

}

// much later
public void changeCustomer() {
  eventBus.post(new ChangeEvent("bla bla") );
} 

вы можете добавить эту библиотеку просто в Android Studio, добавив зависимость в свой build.gradle:

compile 'com.google.guava:guava:17.0'
Шломи Хасин
источник
Больше подходит для «модельного» побочного кода, который может быть менее зависимым от платформы.
karmakaze 05
2

Kotlin : Вот версия @ Shiki на Kotlin с небольшим рефакторингом во фрагменте.

  1. Зарегистрируйте наблюдателя во фрагменте.

Fragment.kt

class MyFragment : Fragment() {

    private var mContext: Context? = null

    private val mMessageReceiver = object: BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            //Do something here after you get the notification
            myViewModel.reloadData()
        }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)

        mContext = context
    }

    override fun onStart() {
        super.onStart()
        registerSomeUpdate()
    }

    override fun onDestroy() {
        LocalBroadcastManager.getInstance(mContext!!).unregisterReceiver(mMessageReceiver)
        super.onDestroy()
    }

    private fun registerSomeUpdate() {
        LocalBroadcastManager.getInstance(mContext!!).registerReceiver(mMessageReceiver, IntentFilter(Constant.NOTIFICATION_SOMETHING_HAPPEN))
    }

}
  1. Публикуйте уведомление где угодно. Только вам нужен контекст.

    LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(Constant.NOTIFICATION_SOMETHING_HAPPEN))```

PS :

  1. вы можете добавить Constant.kt, как я, для хорошей организации уведомлений. Constant.kt
object Constant {
    const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
  1. В качестве контекста во фрагменте вы можете использовать activity(иногда null) или conextнравится то, что использовал я.
Уильям Ху
источник
0

Вы можете использовать слабые ссылки.

Таким образом, вы можете сами управлять памятью и добавлять и удалять наблюдателей по своему усмотрению.

Когда вы addObserver добавляете эти параметры - приведите этот контекст из действия, в которое вы его добавляете, к пустому интерфейсу, добавьте имя уведомления и вызовите метод для запуска интерфейса.

У метода для запуска интерфейса будет функция, которая называется run, чтобы вернуть данные, которые вы передаете, примерно так

public static interface Themethodtorun {
        void run(String notification_name, Object additional_data);
    }

Создайте класс наблюдения, который вызывает ссылку с пустым интерфейсом. Также создайте свой интерфейс Themethodtorun из контекста, передаваемого в addobserver.

Добавьте наблюдение в структуру данных.

Для вызова этого метода будет использоваться тот же метод, однако все, что вам нужно сделать, это найти конкретное имя уведомления в структуре данных, использовать Themethodtorun.run (имя_уведомления, данные).

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

Это хорошая ссылка для слабых ссылок.

http://learningviacode.blogspot.co.nz/2014/02/weak-references-in-java.html

Я загружаю этот код на github. Глаза открыты!

Виктор Дю Пре
источник