松散耦合组件之间的通信

注意

本电子书于 2017 年春季出版,之后再未更新。 书中有许多内容仍然很有价值,但有些材料已经过时。

发布-订阅模式是一种消息传递模式,在此模式下,发布者可在无需知道任何接收方(称为订阅方)的情况下发送消息。 同样,订阅方可在不了解任何发布方的情况下侦听特定消息。

.NET 中的事件可实现发布-订阅模式,如果不需要松散耦合(例如控件和包含它的页面),则这些事件是通信层在组件之间最简单直接的方法。 但是,发布服务器和订阅服务器的生存期通过对象引用彼此耦合,而订阅服务器类型必须引用发布服务器类型。 这可能会造成内存管理问题,尤其是在订阅静态或长期对象事件的对象生存期较短时。 如果不删除事件处理程序,则订阅服务器通过在发布服务器中引用它来保持活动状态,这将阻止或延迟订阅服务器的垃圾回收。

MessagingCenter 简介

Xamarin.FormsMessagingCenter 类可实现发布-订阅模式,允许不便按对象和类型引用进行链接的组件之间进行基于消息的通信。 这种机制允许发布方和订阅方在没有彼此引用的情况下进行通信,这有助于减少它们之间的依赖关系,同时允许独立开发和测试组件。

MessagingCenter 类提供多播发布-订阅功能。 这意味着可以有多个发布方发布单个消息,并且可能有多个订阅方正在侦听同一消息。 图 4-1 演示了这种关系:

Multicast publish-subscribe functionality

图 4-1:多播发布-订阅功能

发布方使用 MessagingCenter.Send 方法发送消息,而订阅方使用 MessagingCenter.Subscribe 方法侦听消息。 此外,订阅方还可以使用 MessagingCenter.Unsubscribe 方法取消消息订阅(如果需要)。

在内部,MessagingCenter 类使用弱引用。 这意味着它不会使对象保持活动状态,而是允许对它们进行垃圾回收。 因此,只有当类不再希望接收消息时才需要取消订阅消息。

eShopOnContainers 移动应用使用 MessagingCenter 类在松散耦合的组件之间进行通信。 该应用定义了三条消息:

  • 将商品添加到购物篮时,CatalogViewModel 类会发布 AddProduct 消息。 作为回应,类 BasketViewModel 订阅消息,并增加购物篮中的商品数量。 此外,BasketViewModel 类还会取消订阅此消息。
  • 当用户将品牌或类型筛选器应用于目录中显示的商品时,Filter 消息由 CatalogViewModel 类发布。 作为回应,CatalogView 类订阅该消息并更新 UI,以便仅显示符合筛选条件的商品。
  • 成功创建并提交新订单后,当 CheckoutViewModel 导航到 MainViewModel 时,ChangeTab 消息由 MainViewModel 类发布。 作为回应,MainView 类订阅消息并更新 UI,以便“我的个人资料”选项卡处于活动状态,显示用户的订单

注意

虽然 MessagingCenter 类允许在松散耦合的类之间进行通信,但它并没有为这个问题提供唯一的体系结构解决方案。 例如,视图模型和视图之间的通信也可以通过绑定引擎和属性更改通知来实现。 此外,两个视图模型之间的通信也可以通过在导航期间传递数据来实现。

在 eShopOnContainers 移动应用中,MessagingCenter 用于在 UI 中更新以响应另一个类中发生的操作。 因此,消息在 UI 线程上发布,订阅方在同一线程上接收消息。

提示

执行 UI 更新时封送给 UI 线程。 如果需要从后台线程发送的消息来更新 UI,请通过调用 Device.BeginInvokeOnMainThread 方法在订阅方的 UI 线程上处理该消息。

有关 MessagingCenter 的详细信息,请参阅 MessagingCenter

定义消息

MessagingCenter 消息是用于标识消息的字符串。 以下代码示例显示了在 eShopOnContainers 移动应用中定义的消息:

public class MessageKeys  
{  
    // Add product to basket  
    public const string AddProduct = "AddProduct";  

    // Filter  
    public const string Filter = "Filter";  

    // Change selected Tab programmatically  
    public const string ChangeTab = "ChangeTab";  
}

在本例中,消息是使用常数定义的。 这种方法的优点是它提供了编译时类型安全性和重构支持。

发布消息

发布方通过一种 MessagingCenter.Send 重载通知订阅方查看消息。 以下代码示例演示如何发布 AddProduct 消息:

MessagingCenter.Send(this, MessageKeys.AddProduct, catalogItem);

在此示例中,Send 方法指定了三个参数:

  • 第一个参数指定发送方类。 发送方类必须由任何想要接收消息的订阅方指定。
  • 第二个参数指定消息。
  • 第三个参数指定要发送给订阅者的有效负载数据。 在这种情况下,有效负载数据是 CatalogItem 实例。

Send 方法将使用“即发即弃”方法发布消息及其有效负载数据。 因此,即使没有注册的订阅方接收消息,消息也仍会发送。 在这种情况下,将忽略已发送的消息。

注意

MessagingCenter.Send 方法可以使用泛型参数来控制消息的传递方式。 因此,不同的订阅方可以接收共享一个消息标识但发送不同有效负载数据类型的多条消息。

订阅消息

订阅方可以使用一种 MessagingCenter.Subscribe 重载进行注册以接收消息。 以下代码示例演示 eShopOnContainers 移动应用如何订阅和处理 AddProduct 消息:

MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(  
    this, MessageKeys.AddProduct, async (sender, arg) =>  
{  
    BadgeCount++;  

    await AddCatalogItemAsync(arg);  
});

在此示例中,Subscribe 方法订阅 AddProduct 消息,并执行回调委托以响应接收消息。 此回调委托(指定为 lambda 表达式)执行更新 UI 的代码。

提示

请考虑使用不可变的有效负载数据。 请不要尝试从回调委托中修改有效负载数据,因为多个线程可能同时访问接收到的数据。 在这种情况下,有效负载数据应不可变,以避免并发错误。

订阅方可能不需要处理已发布消息的每个实例,这可以通过 Subscribe 方法中指定的泛型类型参数来控制。 在此示例中,订阅方者仅接收从 CatalogViewModel 类发送的 AddProduct 消息,其有效负载数据是 CatalogItem 实例。

取消订阅消息

订阅者可以取消订阅他们不想再收到的消息。 这是通过 MessagingCenter.Unsubscribe 重载之一实现的,如以下代码示例所示:

MessagingCenter.Unsubscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct);

在此示例中,Unsubscribe 方法语法反映了订阅接收 AddProduct 消息时指定的类型参数。

总结

Xamarin.FormsMessagingCenter 类可实现发布-订阅模式,允许不便按对象和类型引用进行链接的组件之间进行基于消息的通信。 这种机制允许发布方和订阅方在没有彼此引用的情况下进行通信,这有助于减少它们之间的依赖关系,同时允许独立开发和测试组件。