UWP开源项目 LLQNotifier 页面间通信利器(移植EventBus)

前言

EventBus是一个Android版本的页面间通信库,这个库让页面间的通信变得十分容易且大幅降低了页面之间的耦合。小弟之前玩Android的时候就用得十分顺手,现在玩uwp就觉得应该在这平台也写个类似的库。

这个库原理很简单,就是把观察者模式封装成库,页面想收到某类通知就注册相关事件,在其他页面发出通知后就做响应。

LLQNotifier的使用:

//声明一种通知事件
public class Event1
{
    public string Flag { get; set; }
}

//注册并监听事件
public class subscriber
{
    public subscriber()
    {
        LLQNotifier.Default.Register(this);
    }

    //给收到通知后要回调的方法加上SubscriberCallback属性
    [SubscriberCallback(typeof(Event1), NotifyPriority.Lowest, ThreadMode.Background)]
    public void Test()
    {
        Debug.WriteLine("->>>>>>>>>>subscriber>>Test");
    }
}

//在某个地方发起事件通知
LLQNotifier.Default.Notify(new Event1() { Flag = "flag" });

这样就可以了,事件接收者和发起事件通知的人互不可见,只要通知一声,所有注册这个事件的函数都会执行,并且可以设置执行的优先级,可以看到上面SubscriberCallback属性有三个参数:

  1. 事件类型,表明只有接到这种事件的通知才会响应。
  2. 优先级,总共有5级,实际运用中可能会碰到注册同一种事件并需要控制执行先后顺序,这时优先级就派上用场。
  3. 线程模式,有三种:主线程,当前线程和后台线程,比方说这个方法里做的是UI相关的就用ThreadMode.Main,如果是计算相关就可以用ThreadMode.Current或ThreadMode.Background。

在EventBus中还有个sticky的概念,粘性,事件在发起后一段时间,本来所有注册者都已经响应过了,这时再有其他注册者进来按道理应该是收不到这个事件通知了,不过有了这个sticky的话就可以让新进来的注册者也能响应到这个事件,不过在LLQNotifier里暂时还没有实现这个功能,个人觉得实用性不是太强。

LLQNotifier里的注册通知的具体实现:

//注册事件,可以看到这个方法主要就是把注册者的响应方法和事件类型加到Dictionary里面,当然要先把注册里用Attribute标记的方法找出来并缓存
public void Register(object subscriber)
{
    if (_syncContext == null && SynchronizationContext.Current != null)
        _syncContext = SynchronizationContext.Current;

    List<Type> subscriptTypes = new List<Type>();
    if (_subscriberDictWithType.ContainsKey(subscriber))
    {
        return;
    }
    //这里把注册者里attrubute标记的方法找出来并封装在subscription里
    IList<Subscription> subscriptionList = SubscriptionHandler.CreateSubscription(subscriber);

    foreach (var subscription in subscriptionList)
    {
        var subscriptionsOfType = _subscriptionDictByType.GetOrAdd(subscription.EventType, new List<Subscription>());
        //这里用了一个锁数组,因为在注册时也有可能会在其他线程在通知事件来遍历,所以需要用锁来保证线程安全
        lock (_locksForSubscription.GetOrAdd(subscription.EventType, new object()))
        {
            subscriptionsOfType.Add(subscription);
            subscriptionsOfType.Sort();
        }

        if (!subscriptTypes.Contains(subscription.EventType))
        {
            subscriptTypes.Add(subscription.EventType);
        }
    }

    _subscriberDictWithType.Add(subscriber, subscriptTypes);
}
//接下来就是通知了
public void Notify(object eventObj)
{
    if (!_subscriptionDictByType.ContainsKey(eventObj.GetType()))
        return;

    var subscriptionsOfType = _subscriptionDictByType[eventObj.GetType()];
    List<Subscription> subList;
    lock (_locksForSubscription.GetOrAdd(eventObj.GetType(), new object()))
    {
        subList = subscriptionsOfType.ToList();
    }

    foreach (var subscription in subList)
    {
        if (subscription.IsSubscriberAlive && _subscriberDictWithType.ContainsKey(subscription.Subscriber))
        {
            DispatchNotification(subscription, eventObj);
        }
    }
}
//还有取消注册,适合用在Dispose时
public void Unregister(object subscriber)
{
    List<Type> types = null;
    if(_subscriberDictWithType.TryGetValue(subscriber, out types))
    {
        foreach(var type in types)
        {
            RemoveSubscription(type, subscriber);
        }
    }
}

垃圾回收

因为注册时会把注册的对象保存起来,强引用的话会导致对象不能被GC回收,表现在应用里就是页面只要打开一次,内存就会被占用,即使页面已经关掉,内存不回收,这就是内存泄露了。所以在Subscription里的_subscriber是作为了一个WeakReference存在,这就避免了subscriber不能回收的情况。不过还有一个问题,subscriber在有些地方是作为Key存在的,WeakReference作key的话,对象被回收了Dictionary不会做改变,这样可能导致Dictionary里垃圾越来越多,Value也不能被回收掉。好在有个专门处理这个问题的集合:ConditionalWeakTable,这个table可以在key被回收后删除这条记录,完美解决上面的问题。LLQNotifier里有两个地方用了到这个table,一个Subscription里对method做的缓存,另一个是_subscriberDictWithType用来存储对象和对象包含的事件。

性能

关于性能我自己测试的结果是单个通知到响应时间小于1毫秒,10万个会在100毫秒以内,不过相信正常情况下是不会有这么多通知的,我在知乎日服和读读日报里大量用到LLQNotifier,没发现任何性能上的影响。

开源

项目开源地址:https://github.com/brookshi/LLQNotifier,欢迎Fork/Star

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏QQ会员技术团队的专栏

深入理解React(二) :数据流和事件原理

在React中,数据流是自上而下单向的从父节点传递到子节点,所以组件是简单且容易把握的,他们只需要从父节点提供的props中获取数据并渲染即可。如果顶层组件的某...

4.8K00
来自专栏腾讯移动品质中心TMQ的专栏

iOS 测试利器:idb

对于android的开发以及测试人员,对adb命令一定会很熟悉,adb工具可谓android测试的入门神器,安装、卸载、抓log、截图等等一应俱全。很多自动化以...

3.2K10
来自专栏QQ空间开发团队的专栏

RxJava && Agera 从源码简要分析基本调用流程(1)

相信很多做Android或是Java研发的同学对RxJava应该都早有耳闻了,尤其是在Android开发的圈子里,RxJava渐渐开始广为流行。同样有很多同学已...

8.6K10
来自专栏QQ空间开发团队的专栏

RxJava && Agera 从源码简要分析基本调用流程(2)

RxJava能够帮助我们对数据流进行灵活的变换,以达到链式结构操作的目的,然而它的强大不止于此。下面我们就来看看它的又一利器,调度器`Scheduler`:就像...

9.3K10
来自专栏应兆康的专栏

遇见 Kotlin 先导篇:Kotlin 相比 Java 好在哪里?

Kotlin 是一种为现代多平台应用而诞生的静态编程语言相比于Java而言,它有很多优点,待会笔者会就几个写一下,并给出实际例子。

51240
来自专栏Bennyhuo

用 Kotlin 写 Android ,难道只有环境搭建这么简单?

话说我们入坑 Kotlin 之后,要怎样才能把它运用到 Android 开发当中呢?我们作为有经验的开发人员,大家都知道 Android 现在基本上都用 gra...

5.8K00
来自专栏刘宁的专栏

Android开发入门的正确姿势

对于从事移动客户端开发者的初学者而言,不论是Android还是iOS开发,对客户端开发有一个整体的认识,然后再逐步深入,这样会有事半功倍的效果。

1.4K00
来自专栏腾讯IVWEB团队的专栏

使用 Xposed 强制 androidwebView 开启 debug 模式

从 《远程调试 Android 设备使用入门》文章中我们可以知道在 android 4.4+可以通过在apk中使用下面的代码开启 webview 的 chrom...

70400
来自专栏腾讯移动品质中心TMQ的专栏

一种 Android 端 Web 多进程情况下支持 Web 自动化测试的方法

本文介绍一种在Web多进程情况下支持Web自动化测试的方案。介绍当前Web自动化的简要原理、Web多进程后的问题、相应的解决方案及使用方法。

41300
来自专栏Matthew

5分钟教你打造一个秒开的 Android App

近日在开发过程中,发现每次点击 app 从桌面启动都有一个在桌面明显的等待时间,机型越低端的越明显,冷启动优化看来已经势在必行,所以怒而一顿研究再解决之。

1.9K10

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励