Android equivalente a NSNotificationCenter

95

No processo de portar um aplicativo do iPhone para o Android, estou procurando a melhor maneira de me comunicar dentro do aplicativo. As intenções parecem ser o caminho a percorrer. Esta é a melhor (única) opção? NSUserDefaults parece muito mais leve do que os Intents em desempenho e codificação.

Devo também acrescentar que tenho uma subclasse de aplicativo para estado, mas preciso tornar outra atividade ciente de um evento.

John
fonte
3
Para os iniciantes neste tópico, a segunda resposta é a melhor. Role para baixo ...
Stephan

Respostas:

5

Você pode tentar isso: http://developer.android.com/reference/java/util/Observer.html

Rui Peres
fonte
42
A resposta de Shiki abaixo é muito melhor.
dsaff
5
@dsaff apesar de ser uma resposta mais completa, de forma alguma a minha resposta está errada, claramente não mereço -1. O que faz sentido é você marcar a resposta de Shiki com +1.
Rui Peres
4
A resposta de Shiki é a melhor para a pergunta
Ramz
4
Observe que apenas respostas tecnicamente incorretas e de spam devem ser rejeitadas - esta não se encaixa em nenhuma das duas. +1 para compensação e +1 para Shiki também, porque essa é uma ótima resposta.
351

O melhor equivalente que encontrei é LocalBroadcastManager, que faz parte do Android Support Package .

Da documentação LocalBroadcastManager:

Ajudante para registrar e enviar transmissões de Intents para objetos locais dentro de seu processo. Isso tem uma série de vantagens sobre o envio de transmissões globais com sendBroadcast (Intent):

  • Você sabe que os dados que está transmitindo não sairão do seu aplicativo, então não precisa se preocupar com o vazamento de dados privados.
  • Não é possível que outros aplicativos enviem essas transmissões ao seu aplicativo, então você não precisa se preocupar em ter brechas de segurança que eles possam explorar.
  • É mais eficiente do que enviar uma transmissão global pelo sistema.

Ao usar isso, você pode dizer que um Intenté equivalente a um NSNotification. Aqui está um exemplo:

ReceiverActivity.java

Uma atividade que observa as notificações do evento nomeado "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

A segunda atividade que envia / transmite notificações.

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

Com o código acima, toda vez que o botão R.id.button_sendé clicado, um Intent é transmitido e recebido pormMessageReceiver em ReceiverActivity.

A saída de depuração deve ser semelhante a esta:

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
fonte
11
Muito obrigado por dedicar seu tempo para escrever uma resposta tão útil e detalhada.
Chris Lacy
14
Você provavelmente não deve chamar registerReceiver em seu método onCreate, pois isso vazará sua Activity e seu método onDestroy nunca será chamado. onResume parece uma escolha melhor para chamar registerReceiver e onPause para chamar unregisterReceiver.
Stephane JAIS
4
Equivalente perfeito a NSNotificationCenter, deve ser a resposta aceita!
Leon Storey
Gostaria de salientar que o uso de notificações globais pode levar você a um design confuso. Pense sobre qual seria o melhor acoplamento entre seus componentes antes de pular para o caminho mais fácil. Às vezes, é melhor usar ouvintes ou algo semelhante ao padrão de delegado do iOS e assim por diante.
saulobrito de
Obrigado, isso funcionou para mim. @Shiki, por favor, você acha que poderia me dar sua opinião sobre esta questão stackoverflow.com/questions/25598696/…
Axel
16

Aqui está algo semelhante à resposta @Shiki, mas do ponto de vista dos desenvolvedores iOS e do centro de notificação.

Primeiro crie algum tipo de serviço 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);
 }
}

Então, você também precisará de algum tipo de enum para evitar erros na codificação com strings - (NotificationType):

public enum NotificationType {

   LoginResponse;
   // Others

}

Aqui está o uso (adicionar / remover observadores), por exemplo, em atividades:

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

e aqui está, finalmente, como postamos uma notificação no NotificationCenter de algum serviço de retorno de chamada ou de descanso ou qualquer outro:

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

é isso, saúde!

Elvis Rudonja
fonte
Obrigado pela sua solução! Descobri que usar em Bundle paramsvez de HashMapé mais conveniente para passar parâmetros de diferentes tipos. Há uma boa conexão entre Intente Bundle:intent.putExtras(params)
zubko
4

Você pode usar isto: http://developer.android.com/reference/android/content/BroadcastReceiver.html , que apresenta um comportamento semelhante.

Você pode registrar os receptores programaticamente por meio de Context.registerReceiver (BroadcastReceiver, IntentFilter) e ele irá capturar intents enviados por meio de Context.sendBroadcast (Intent).

Observe, entretanto, que um receptor não receberá notificações se sua atividade (contexto) tiver sido pausada.

AngraX
fonte
Uma nota rápida de design: BroadcastReceivers e NSNotificationCenter podem operar como um agregador de eventos. A vantagem sobre Delegados ou Observadores é que o emissor e o receptor são desacoplados (na verdade, eles têm acoplamento de mensagem ou dados, mas esse é um dos tipos de acoplamento mais fracos). Editado com correção.
AngraX
4

Descobri que o uso de EventBus de Guava lib é a maneira mais simples de comunicação no estilo de publicação-assinatura entre componentes sem exigir que os componentes se registrem explicitamente uns com os outros

veja seu exemplo em 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") );
} 

você pode adicionar esta lib simplesmente no Android Studio adicionando uma dependência ao seu build.gradle:

compile 'com.google.guava:guava:17.0'
Shlomi Hasin
fonte
Mais adequado para o código lateral do 'modelo', que pode ser menos dependente da plataforma.
karmakaze
2

Kotlin : Aqui está uma versão de @Shiki em Kotlin com uma pequena refatoração em um fragmento.

  1. Registre o observador no 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. Publique a notificação em qualquer lugar. Só você precisa do contexto.

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

PS :

  1. você pode adicionar um Constant.kt como eu para organizar bem as notificações. Constant.kt
object Constant {
    const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
  1. Para o contexto em um fragmento, você pode usar activity(às vezes null) ou conextgostar do que usei.
William Hu
fonte
0

Você pode usar referências fracas.

Desta forma, você pode gerenciar a memória sozinho e adicionar e remover observadores como quiser.

Quando você addObserver adiciona esses parâmetros - transmita esse contexto da atividade que você está adicionando na interface vazia, adicione um nome de notificação e chame o método para executar a interface.

O método para executar a interface teria uma função que é chamada run para retornar os dados que você está passando algo assim

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

Crie uma classe de observação que invoca uma referência com uma interface vazia. Também construa sua interface Themethodtorun a partir do contexto que está sendo passado no addobserver.

Adicione a observação a uma estrutura de dados.

Para chamá-lo seria o mesmo método, porém tudo que você precisa fazer é encontrar o nome da notificação específica na estrutura de dados, use o Themethodtorun.run (notification_name, data).

Isso enviará um retorno de chamada para onde você criou um observador com um nome de notificação específico. Não se esqueça de removê-los quando terminar!

Esta é uma boa referência para referências fracas.

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

Estou enviando este código para o github. Mantenha os olhos abertos!

Victor Du Preez
fonte