放棄官方,擁抱自我,自定義 LiveData 才是最合適的

血色王冠發表於2019-05-06

標題起的有點大啊,不要在意

好白...

我想我不是應該不是第一個自己寫 LiveData 的,18年初開始使用 AAC 的這些元件,我覺得 viewModule 不實用,拿 viewModule 當 P 用不是能很好滿足需求,LiveData 用了一段時間,尼瑪坑死我了,反倒是讓我費了不少事,死來想起還是自己寫一個 LiveData 吧,自己寫的才是最合適自己的

AAC 元件裡最有意義的我認為是 LiveData , LiveData 在繼 RX 之後深刻的告訴了我們資料流的概念,讓我們看到了資料和流最佳的結合方式,這種變化是非常適應時下發展潮流的,但是奈何 LiveData 有些坑讓我棄用他了

LiveData 的坑: - 我就不上程式碼了,用過的都知道

  • 必須在主執行緒傳送資料
  • 註冊觀察者必須要傳 Lifecycle,有時候我們並不是都在頁面級別使用的,比如說拿 LiveData 當 RXbus 用,雖然有不用傳 Lifecycle 的方法 observeForever,但是穿與不穿 Lifecycle 居然是2個 api 我覺得用著不爽
  • LiveData 最大的問題,同時也是我最不能接受的:LiveData 無法判斷值是不是新值,只要 LiveData 設定過資料了,那麼不管你是不是第一次註冊觀察者或者頁面重新啟用顯示時,都會收到訊息沒,這就不能忍了,這不就是亂髮射資料嘛,完全不能準確的表達我發射資料的動作,老的資料也發,很多時候我並不需要你現在的資料,這讓我很為難,為了處理這個問題,浪費了不少精力,反倒是麻煩了,就像我討厭有的 Google API 的命名,什麼垃圾玩意,什麼名都敢起,有的時候查字典看翻譯我都不知道是幹嘛的

自定義 LiveData 簡單思路

其實自己寫個 LiveData 出來非常簡單,easy+輕鬆,核心就是用 PublishSubject 做熱發射,熱發射不熟悉的朋友可以看我的文章:我學 rxjava 2(3)- 熱發射

在註冊的時候使用者要是傳 Lifecycle ,那麼就在 Lifecycle 身上註冊一個觀察者,頁面 onDes 關閉時使用管道解綁,同時一提供一個 map 儲存管道,用於使用者自行解綁

什麼時候響應資料這是使用者的自己問題,使用者自行判斷要不要啟用操作,我們都來到 Lifecycle 的時代了, Lifecycle 自身就提供了頁面狀態的 API,判斷起來也不麻煩

    // 獲取頁面狀態
    lifecycle.currentState  
    
    // lifecycle 類裡有提供狀態的列舉
    public enum State {
        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;
    }
複製程式碼

LiveData 提供變換,但是變換隻能變換一個,並且變換得到的 LiveData 不能發射資料,必要使用原始的 LiveData 才行,並且變換 API 不是在 LiveData 身上的,而是一個輔助類,這就用著很不爽了,我們都熟悉了 Rxjava 這麼久了,不是鏈式的 API 我們都 diss 他,這點 Google 有點落後了

// Transformations.map()
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

// Transformations.switchMap()
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
複製程式碼

這裡針對變換的問題,我覺得既然 Rxjava 已經實現的很好了,何必在多一手呢,再說再怎麼寫也肯定不如 rx 不是,所以提供一個方法直接把 subject 丟擲來,缺點是就沒有自動解綁的功能了,優點是不影響我們傳送資料,我們還是用的 subject 發射資料,我測試過了沒問題的


自定義 LiveData 程式碼

為了結構規整,我設計了3層 API,根介面,abs 抽象基類,具體實現,為啥這麼麻煩呢,一是為了練手培養程式碼規範,二是這樣設計方便擴充套件不是

放棄官方,擁抱自我,自定義 LiveData 才是最合適的

  • 根介面 - 就是設定,獲取,發射資料,使用泛型接受資料型別
/**
 * 作者 : BloodCrown
 * 時間 : 2019-05-05 16:03
 * 描述 : 自定義 LiveData 跟介面
 *
 *  1. 提供獲取設定資料的介面
 *  2. 傳送資料的介面
 */
interface IMyLiveData<T> {

    /**
     * 獲取資料
     */
    fun getValue(): T?

    /**
     * 設定資料
     */
    fun setValue(t: T)

    /**
     * 傳送
     */
    fun sendValue(t: T)
}
複製程式碼
/**
 * 作者 : BloodCrown
 * 時間 : 2019-05-05 16:09
 * 描述 : 自定義 LiveData 的抽象基類
 *
 *  1. 實現根介面,提供資料儲存,獲取功能
 *  2. 傳送資料應該是具體實現關心的
 *
 */
abstract class AbsMyLiveData<T> : IMyLiveData<T> {

    // 資料物件
    private var mValue: T? = null

    override fun getValue(): T? {
        return mValue
    }

    override fun setValue(t: T) {
        this.mValue = t
    }
}
複製程式碼
  • 具體實現 - 沒啥說的,很簡單,看就完了,沒有看不懂的,看不懂的喊我,我立馬機票飛你那去...
/**
 * 作者 : BloodCrown
 * 時間 : 2019-05-05 15:58
 * 描述 :
 * 1. 自定義的 LiveData,為了是去掉 LiveData 一些不合時宜的設定
 * 2. 自己寫的才能百分百按照自己的設想去做
 *
 * 成員變數描述:
 * 1. subject 對外提供的 PublishSubject 用於熱發射
 * 2. disposableList map 集合,用來儲存管道物件,因為有的訂閱沒有頁面級別的生命週期
 *
 * 功能:
 * 1. sendValue 傳送資料
 * 2. addObserver 註冊觀察者
 *      lifecycle != null -> 會在註冊觀察者的同時,在 Lifecycle.Event.ON_DESTROY 時會解除繫結
 *      tag != null -> 會把管道物件儲存到 map 集合裡,用於自助解除註冊
 */
class MyLiveData<T> : AbsMyLiveData<T>() {

    // 核心資料資料被觀察者
    var subject = PublishSubject.create<T>()
    // 儲存管道的 map 集合
    var disposableList: MutableMap<String, Disposable> = mutableMapOf()

    /**
     * 傳送資料
     */
    override fun sendValue(data: T) {
        if (data == null) return
        setValue(data)
        subject.onNext(data)
    }

    /**
     * 註冊觀察者,考慮了沒有頁面級別的生命週期的情況
     *
     *      lifecycle != null -> 會在註冊觀察者的同時,在 Lifecycle.Event.ON_DESTROY 時會解除繫結
     *      tag != null -> 會把管道物件儲存到 map 集合裡,用於自助解除註冊
     */
    fun addObserver(tag: String? = null, lifecycle: Lifecycle? = null, observer: (data: T) -> Unit) {
        var disposable = subject.subscribe {
            observer(it)
        }
        if (tag != null) disposableList.put(tag, disposable)
        if (disposable != null) lifecycle?.addObserver(object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun destroy() {
                if (!disposable.isDisposed) disposable.dispose()
                disposableList.remove(tag)
               if (tag != null) disposableList.remove(tag)
            }
        })
    }

    /**
     * 手動解除註冊,只適用於在註冊時沒有傳入 lifecycle 的朋友
     */
    fun removeOberver(tag: String) {
        if (tag == null) return

        var disposable = disposableList.get(tag)
        if (disposable == null) return

        if (!disposable?.isDisposed) disposable?.dispose()
        disposableList.remove(tag)
    }

    /**
     * 用於使用者自行變換擴充套件,不過這樣就不能自行解綁了,需要使用者手動進行解綁操作
     */
    fun getObservable(): PublishSubject<T> {
        return subject
    }
}
複製程式碼
// 建立 MyLiveData 物件
var liveData = MyLiveData<String>()

// 註冊多個監視器
liveData.addObserver("AA", this.lifecycle) {
    Log.d("AA", "MyLiveData 接受到資料11: $it")
}

liveData.addObserver("AA", this.lifecycle) {
    Log.d("AA", "MyLiveData 接受到資料22: $it")
}

// 發射資料
liveData.sendValue("AA")

// 手動解綁
liveData.removeOberver("AA")
複製程式碼

最後

最後沒啥說的了,由需求大家自己擴充套件吧,

放棄官方,擁抱自我,自定義 LiveData 才是最合適的

相關文章