zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    保持URL不变和数字验证
    centOS ftp key?
    本地环境测试二级域名
    linux 解决You don't have permission to access 问题
    php smarty section loop
    php header Cannot modify header information headers already sent by ... 解决办法
    linux部分命令
    Linux 里面的文件操作权限说明
    用IT网络和安全专业人士视角来裁剪云的定义
    SQL Server 2008 R2炫酷报表"智"作有方
  • 原文地址:https://www.cnblogs.com/brookshi/p/5619242.html
Copyright © 2011-2022 走看看