Android odpowiednik NSNotificationCenter

95

W trakcie przenoszenia aplikacji na iPhone'a na Androida szukam najlepszego sposobu komunikacji w ramach aplikacji. Wydaje się, że intencje są właściwą drogą, czy to najlepsza (jedyna) opcja? NSUserDefaults wydaje się znacznie lżejszy niż Intents zarówno pod względem wydajności, jak i kodowania.

Powinienem również dodać, że mam podklasę aplikacji dla stanu, ale muszę powiadomić inne działanie o zdarzeniu.

Jan
źródło
3
Dla nowicjuszy w tym temacie druga odpowiedź jest najlepsza. Przewiń w dół ...
Stephan

Odpowiedzi:

6

Możesz spróbować tego: http://developer.android.com/reference/java/util/Observer.html

Rui Peres
źródło
42
Odpowiedź Shiki poniżej jest znacznie lepsza.
dsaff
5
@dsaff, mimo że jest bardziej kompletną odpowiedzią, w żaden sposób moja odpowiedź nie jest błędna, najwyraźniej nie zasługuję na -1. Co ma sens, to dać Ci +1 odpowiedzi Shiki.
Rui Peres
4
Shiki's jest lepszą odpowiedzią na to pytanie
Ramz
4
Zwróć uwagę, że tylko niepoprawne technicznie i odpowiedzi na spam powinny być odrzucane - ten nie pasuje do żadnego. +1 za rekompensatę i +1 za Shiki, ponieważ to świetna odpowiedź.
351

Najlepszym odpowiednikiem, jaki znalazłem, jest LocalBroadcastManager, który jest częścią pakietu obsługi Androida .

Z dokumentacji LocalBroadcastManager:

Pomoc w rejestracji i wysyłaniu rozgłoszeń intencji do lokalnych obiektów w procesie. Ma to wiele zalet w porównaniu z wysyłaniem globalnych emisji za pomocą sendBroadcast (Intent):

  • Wiesz, że transmitowane dane nie opuszczą Twojej aplikacji, więc nie musisz się martwić o wyciek prywatnych danych.
  • Inne aplikacje nie mogą wysyłać tych transmisji do Twojej aplikacji, więc nie musisz się martwić o dziury w zabezpieczeniach, które mogą wykorzystać.
  • Jest to bardziej wydajne niż wysyłanie globalnej transmisji przez system.

Używając tego, możesz powiedzieć, że an Intentjest odpowiednikiem pliku NSNotification. Oto przykład:

ReceiverActivity.java

Działanie, które obserwuje powiadomienia o zdarzeniu o nazwie "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

Drugie działanie, które wysyła / rozgłasza powiadomienia.

@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);
}

Z powyższego kodu, za każdym razem przycisk R.id.button_sendzostanie kliknięty, zamiarem jest nadawany i odbierany jest przez mMessageReceiverw ReceiverActivity.

Wynik debugowania powinien wyglądać następująco:

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! 
Shiki
źródło
11
Bardzo dziękuję za poświęcenie czasu na napisanie tak pomocnej, szczegółowej odpowiedzi.
Chris Lacy,
14
Prawdopodobnie nie powinieneś wywoływać registerReceiver w swojej metodzie onCreate, ponieważ spowoduje to wyciek Twojej aktywności, a metoda onDestroy nigdy nie zostanie wywołana. onResume wydaje się lepszym wyborem do wywołania registerReceiver i onPause do wywołania unregisterReceiver.
Stephane JAIS
4
Idealny odpowiednik NSNotificationCenter, powinna być akceptowaną odpowiedzią!
Leon Storey,
Chciałbym zwrócić uwagę, że używanie globalnych powiadomień może doprowadzić do pomieszanego projektu. Zastanów się, jakie byłoby najlepsze sprzężenie między komponentami, zanim przejdziesz na łatwą drogę. Czasami po prostu lepiej jest użyć słuchaczy lub czegoś podobnego do wzorca delegata iOS i tak dalej.
saulobrito
Dzięki, to zadziałało dla mnie. @Shiki, czy myślisz, że mógłbyś przekazać mi swoją opinię na to pytanie stackoverflow.com/questions/25598696/ ...
Axel
16

Oto coś podobnego do odpowiedzi @Shiki, ale z punktu widzenia programistów iOS i centrum powiadomień.

Najpierw utwórz rodzaj usługi 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);
 }
}

Następnie będziesz potrzebować również typu wyliczenia, aby zabezpieczyć się przed błędami w kodowaniu za pomocą ciągów - (NotificationType):

public enum NotificationType {

   LoginResponse;
   // Others

}

Oto użycie (dodawanie / usuwanie obserwatorów) na przykład w działaniach:

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();
    }
}

i oto w końcu, jak wysyłamy powiadomienie do NotificationCenter z jakiejś usługi oddzwonienia lub odpoczynku lub czegokolwiek:

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);
}

to wszystko, na zdrowie!

Elvis Rudonja
źródło
Dzięki za rozwiązanie! Odkryłem, że używanie Bundle paramszamiast HashMapjest wygodniejsze przy przekazywaniu parametrów różnych typów. Istnieje dobre połączenie między Intenta Bundle:intent.putExtras(params)
zubko
4

Możesz użyć tego: http://developer.android.com/reference/android/content/BroadcastReceiver.html , co daje podobne zachowanie.

Możesz zarejestrować odbiorców programowo za pomocą Context.registerReceiver (BroadcastReceiver, IntentFilter) i przechwytuje intencje wysłane przez Context.sendBroadcast (Intent).

Należy jednak pamiętać, że odbiornik nie otrzyma powiadomień, jeśli jego aktywność (kontekst) zostanie wstrzymana.

AngraX
źródło
Krótka uwaga projektowa: BroadcastReceivers i NSNotificationCenter mogą działać jako agregator zdarzeń. Zaletą nad delegatami lub obserwatorami jest to, że nadawca i odbiorca są odsprzężeni (w rzeczywistości mają połączenie komunikatów lub danych, ale jest to jeden z najsłabszych typów połączeń). Edytowane z korektą.
AngraX
4

Odkryłem, że użycie EventBus z Guava lib jest najprostszym sposobem komunikacji między komponentami w stylu publikowania i subskrybowania bez konieczności jawnej rejestracji między komponentami

zobacz ich próbkę na 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") );
} 

możesz dodać tę bibliotekę po prostu do Android Studio, dodając zależność do pliku build.gradle:

compile 'com.google.guava:guava:17.0'
Shlomi Hasin
źródło
Bardziej odpowiedni dla kodu po stronie „modelu”, który może być mniej zależny od platformy.
karmakaze
2

Kotlin : Oto wersja @ Shiki w Kotlinie z odrobiną refaktoryzacji we fragmencie.

  1. Zarejestruj obserwatora w Fragment.

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. Opublikuj powiadomienie w dowolnym miejscu. Tylko ty potrzebujesz kontekstu.

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

PS :

  1. możesz dodać Constant.kt jak ja, aby dobrze organizować powiadomienia. Constant.kt
object Constant {
    const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
  1. W przypadku kontekstu we fragmencie możesz użyć activity(czasami null) lub conextpolubić to, czego użyłem.
William Hu
źródło
0

Możesz użyć słabych odniesień.

W ten sposób możesz samodzielnie zarządzać pamięcią oraz dodawać i usuwać obserwatorów według własnego uznania.

Po dodaniu Observer dodaj te parametry - prześlij ten kontekst z działania, do którego go dodajesz, do pustego interfejsu, dodaj nazwę powiadomienia i wywołaj metodę, aby uruchomić interfejs.

Metoda uruchamiania interfejsu miałaby funkcję, która nazywa się run, aby zwrócić dane, które przekazujesz, coś takiego

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

Utwórz klasę obserwacji, która wywołuje odwołanie z pustym interfejsem. Skonstruuj także interfejs Themethodtorun z kontekstu przekazywanego w addobserver.

Dodaj obserwację do struktury danych.

Aby to wywołać, byłaby ta sama metoda, ale wszystko, co musisz zrobić, to znaleźć konkretną nazwę powiadomienia w strukturze danych, użyj Themethodtorun.run (nazwa_ powiadomienia, dane).

Spowoduje to wysłanie oddzwonienia do miejsca, w którym kiedykolwiek utworzyłeś obserwatora o określonej nazwie powiadomienia. Nie zapomnij ich usunąć, gdy skończysz!

Jest to dobre odniesienie dla słabych odniesień.

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

Jestem w trakcie przesyłania tego kodu na github. Miej oczy otwarte!

Victor Du Preez
źródło