【问题标题】:Android equivalent to NSNotificationCenterAndroid 相当于 NSNotificationCenter
【发布时间】:2011-04-26 05:56:17
【问题描述】:

在将 iPhone 应用程序移植到 android 的过程中,我正在寻找在应用程序内进行通信的最佳方式。意图似乎是要走的路,这是最好的(唯一)选择吗? NSUserDefaults 在性能和编码方面似乎都比 Intent 轻得多。

我还应该添加我有一个状态的应用程序子类,但我需要让另一个活动知道一个事件。

【问题讨论】:

  • 对于这个话题的新手来说,第二个答案是最好的。向下滚动...

标签: android iphone ios events nsnotificationcenter


【解决方案1】:

我找到的最佳等价物是 LocalBroadcastManager,它是 Android Support Package 的一部分。

来自 LocalBroadcastManager 文档:

帮助注册 Intent 并将广播发送到进程中的本地对象。与使用 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,都会广播一个Intent,并被mMessageReceiverReceiverActivity中接收。

调试输出应如下所示:

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! 

【讨论】:

  • 非常感谢您抽出宝贵时间撰写如此有用、详细的回复。
  • 您可能不应该在 onCreate 方法中调用 registerReceiver,因为这会泄漏您的 Activity 并且您的 onDestroy 方法将永远不会被调用。 onResume 似乎是调用 registerReceiver 的更好选择,而 onPause 似乎是调用 unregisterReceiver 的更好选择。
  • 完全等同于NSNotificationCenter,应该是公认的答案!
  • 我想指出,使用全局通知可能会导致设计混乱。在跳到简单的方法之前,想想你的组件之间的最佳耦合是什么。有时使用监听器或类似于 iOS 委托模式的东西会更好。
  • 感谢这对我有用。 @Shiki 请你认为你可以就这个问题给我你的意见stackoverflow.com/questions/25598696/…
【解决方案2】:

这里有类似@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更方便传递不同类型的参数。 IntentBundle 之间有很好的联系:intent.putExtras(params)
【解决方案3】:

【讨论】:

  • 下面 Shiki 的回答要好得多。
  • @dsaff 尽管是一个更完整的答案,但我的答案绝不是错误的,我显然不应该得到 -1。对你来说有意义的是 +1 Shiki 的回答。
  • Shiki 是这个问题的更好答案
  • 请注意,只有技术上不正确和垃圾邮件的答案才应该被否决——这两个都不适合。 +1 补偿和 Shiki 也 +1,因为这是一个很好的答案。
【解决方案4】:

你可以使用这个:http://developer.android.com/reference/android/content/BroadcastReceiver.html,它给出了类似的行为。

您可以通过 Context.registerReceiver(BroadcastReceiver, IntentFilter) 以编程方式注册接收器,它将捕获通过 Context.sendBroadcast(Intent) 发送的意图。

但请注意,如果接收者的活动(上下文)已暂停,则接收者将不会收到通知。

【讨论】:

  • 快速设计说明:BroadcastReceivers 和 NSNotificationCenter 都可以作为事件聚合器运行。与 Delegates 或 Observers 相比的优势在于发送者和接收者是解耦的(它们实际上具有消息或数据耦合,但这是最弱的耦合类型之一)。 已更正。
【解决方案5】:

我发现使用 Guava lib 的 EventBus 是组件间发布-订阅式通信的最简单方式,无需组件之间显式注册

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

您可以通过将依赖项添加到您的 build.gradle 来简单地在 Android Studio 上添加此库:

compile 'com.google.guava:guava:17.0'

【讨论】:

  • 更适合“模型”端代码,可以减少对平台的依赖。
【解决方案6】:

Kotlin:这是 @Shiki 在 Kotlin 中的版本,在片段中进行了一些重构。

  1. 在 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. 在任何地方发布通知。只有你需要上下文。

    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

【讨论】:

  • 如何将变量传递给接收端?
【解决方案7】:

你可以使用弱引用。

这样你就可以自己管理内存并随意添加和删除观察者。

当您添加观察者时,添加这些参数 - 将该上​​下文从您要添加的活动中转换到空接口,添加通知名称,然后调用方法来运行接口。

运行接口的方法将有一个名为 run 的函数,用于返回您传递的数据,如下所示

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

创建一个使用空接口调用引用的观察类。 还要从 addobserver 中传递的上下文构造您的 Themethodtorun 接口。

将观察结果添加到数据结构中。

调用它是相同的方法,但是您需要做的就是在数据结构中找到特定的通知名称,使用 Themethodtorun.run(notification_name, data)。

这将向您创建具有特定通知名称的观察者的位置发送回调。 完成后不要忘记删除它们!

这是弱引用的好参考。

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

我正在将此代码上传到 github。睁大眼睛!

【讨论】:

    【解决方案8】:

    我写了一个可以做同样工作的包装器,相当于 iOS 使用 LiveData

    包装器:

    class ObserverNotify {
        private val liveData = MutableLiveData<Nothing>()
    
    
        fun postNotification() {
            GlobalScope.launch {
                withContext(Dispatchers.Main) {
                    liveData.value = liveData.value
                }
            }
        }
    
        fun observeForever(observer: () -> Unit) {
            liveData.observeForever { observer() }
        }
    
        fun observe(owner: LifecycleOwner, observer: () -> Unit) {
            liveData.observe(owner) { observer()}
        }
    
    }
    
    class ObserverNotifyWithData<T> {
        private val liveData = MutableLiveData<T>()
    
    
        fun postNotification(data: T) {
            GlobalScope.launch {
                withContext(Dispatchers.Main) {
                    liveData.value = data
                }
            }
        }
    
        fun observeForever(observer: (T) -> Unit) {
            liveData.observeForever { observer(it) }
        }
    
        fun observe(owner: LifecycleOwner, observer: (T) -> Unit) {
            liveData.observe(owner) { observer(it) }
        }
    
    }
    

    声明观察者类型:

    object ObserverCenter {
        val moveMusicToBeTheNextOne: ObserverNotifyWithData<Music> by lazy { ObserverNotifyWithData() }
        val playNextMusic: ObserverNotify by lazy { ObserverNotify() }
        val newFCMTokenDidHandle: ObserverNotifyWithData<String?> by lazy { ObserverNotifyWithData() }
    }
    

    在活动中观察:

    ObserverCenter.newFCMTokenDidHandle.observe(this) {
        // Do stuff
    }
    

    通知:

    ObserverCenter.playNextMusic.postNotification()
    ObserverCenter.newFCMTokenDidHandle.postNotification("MyData")
    

    【讨论】:

    • 但是编译器说“liveData.observe(owner){observer()}”和“liveData.observe(owner){observer(it)}”有错误
    【解决方案9】:

    @Shiki 的答案在 2020 年 6 月可能是正确的,但在 2022 年 1 月,LocalBroadcastManager 恰好被弃用。

    经过两天的研究,我最终发现 SharedFlow 被 Android 指示“向应用程序的其余部分发送滴答声,以便所有内容同时定期刷新”。

    意义,或多或少,我们可以从 Swift 的 NSNotificationCenter 中得到什么。

    这是我在应用中实现共享流的方式:

    首先,您需要创建一个 InAppNotif Singleton,它实际上是您的 Activity 的共享 ViewModel(注意最后一点:为您的 Activity 共享,而不是您的所有应用程序^^)

    enum class InAppNotifName {
        NotifNameNumber1,
        NotifNameNumber2,
        NotifNameNumber3
    }
    
    object InAppNotif: ViewModel() {
        
        private val _sharedNotif = MutableSharedFlow<InAppNotifName>(0)
        val sharedNotif: SharedFlow<InAppNotifName> = _sharedNotif.asSharedFlow()
    
        private fun sendNotif(name: InAppNotifName) {
            CoroutineScope(Default).launch {
                _sharedNotif.emit(name)
            }
        }
    
        public fun notifyNotif1() {
            sendNotif(InAppNotifName.NotifNameNumber1)
        } 
    
        public fun notifyNotif2() {
            sendNotif(InAppNotifName.NotifNameNumber1)
        }
    
        public fun notifyNotif3() {
            sendNotif(InAppNotifName.NotifNameNumber1)
        }
    
    }
    

    第二步,如果你有很多Fragment在应用通知中接收,并且你不想重复自己,则需要创建一个“接收通知”界面

    fun AnyReceivingNotif.observeInAppNotif() {
        CoroutineScope(Default).launch {
            InAppNotif.sharedNotif.collect {
                onReceivingInAppNotif(it)
            }
        }
    }
    
    interface AnyReceivingNotif {
        suspend fun onReceivingInAppNotif(value: InAppNotifName)
    }
    

    顺便说一句,“暂停”这个词只有在您需要在收到通知时更新 UI 时才有用。

    最后,从任何要接收 InAppNotif 的对象,你需要做的就是让它符合你的 AnyReceivingNotif 接口,然后完成 onReceivingInAppNotif 函数

    class MyFragment: Fragment(), AnyReceivingNotif {
    
        override suspend fun onReceivingInAppNotif(value: InAppNotifName) {
            when (value) {
                InAppNotifName.NotifNameNumber1 -> { /* Do complicated things */ }
                InAppNotifName.NotifNameNumber2 -> { /* Do some stuff */ }
                InAppNotifName.NotifNameNumber3 -> {
                    withContext(Default){
                        /* Update the UI */
                    }
                }
            }
        }
    
    }
    

    【讨论】:

    • 对不起,Shiki 的回答实际上可以追溯到 2012 年。十年后,事情发生了变化,现在 LocalBroadcastManager 已被弃用...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-06
    • 1970-01-01
    • 2011-10-16
    • 2018-02-09
    • 2012-09-14
    • 2013-09-05
    • 1970-01-01
    相关资源
    最近更新 更多