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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM