丟掉EventBus,ViewModel+LiveData用起來

需求背景

ViewModel和LiveData是google官方架構(gòu)JetPack系列的一個響應式開發(fā)框架。ViewModel和LiveData主要用于搭建MVVM架構(gòu),能監(jiān)聽組件的生命周期變化,這樣一來只會更新處于活躍狀態(tài)的組件。
在我們工程中,ViewModel和LiveData的使用已經(jīng)有一段時間,結(jié)合這兩者的封裝,我們在推動項目從MVP到MVVM的過渡。而在之前的過渡過程中,我們還只是用ViewModel來代替Presenter,用LiveData來代替Callback回調(diào)。
但最近的項目開發(fā)中,遇到了這樣一個需求:一個activity的viewpager中有2個Fragment,其中第一個FragmentA支持上下滑動,每次滑動之后要通知到FragmentB刷新數(shù)據(jù),這是個類似抖音的交互過程。其實在之前的開發(fā)中,我們就遇到過類似的需求,說白了就是一個頁面甚至跨頁面的多個UI組件的數(shù)據(jù)聯(lián)動,這種場景相信在大家的日常開發(fā)中也經(jīng)常遇到。比如在頁面的彈框里給影片點擊“想看”,那么這個頁面甚至所有的“想看”狀態(tài)都要刷新。而之前的做法無非兩種:

  1. 使用回調(diào),將操作回調(diào)給Fragment或者activity,再下發(fā)給需要更新的組件。這種做法如果只有一兩層的回調(diào)還好,如果層級多了可能陷入“回調(diào)地獄”。 如果是多層次的UI,甚至有用到RecyclerView,是從ViewHolder里發(fā)出事件,穿越一層層去到另一個ViewHolder,那就幾乎會破壞每一層的封裝結(jié)構(gòu);
  2. 使用EventBus類似機制,或者自己實現(xiàn)的靜態(tài)全局回調(diào);這種機制通過一種類似全局總線的機制來解決問題。這種觀察者模式需要自己去維護監(jiān)聽與移除,如果注冊或者更新時機不對有可能造成內(nèi)存泄漏甚至崩潰;但是EventBus或者類似的機制也存在濫用的痛點,一旦工程中使用太多,有可能造成event滿天飛,到處都是收發(fā)的地方,給后期的維護帶來困難;

用法與原理簡介

現(xiàn)在我們有了ViewModel+LiveData,多了一種新思路來解決這個問題,這里首先要介紹一下ViewModel的用法,一個典型的ViewModel的使用示例:

//構(gòu)建ViewModel實例
final FilmViewModel filmViewModel = ViewModelProviders.of(this).get(FilmViewModel class);

//觀察ViewModel中數(shù)據(jù)的變化,并實時展示
filmViewModel.getFilmInfo().observe(this, new Observer<FilmInfo>() {
    @Override
    public void onChanged(FilmInfo filmInfo) {
        // reload
    }
});

findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        //點擊按鈕  更新Film
        filmViewModel.switchFilm();
    }
});

上面的示例可以看到,viewmode的對象不是new出來的,而是通過ViewModelProviders的get方法get出來的,下面看一下兩個方法的內(nèi)部實現(xiàn):

@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    //檢查application是否為空,不為空則接收
    Application application = checkApplication(activity);
    if (factory == null) {
        //構(gòu)建一個ViewModelProvider.AndroidViewModelFactory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(ViewModelStores.of(fragment), factory);
}

ViewModelProvider只是個包裝類,真正的viewmode緩存是在ViewModelSotre中,而ViewModelStore的獲取方法實現(xiàn)如下:

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }

只要是support包26.1.0之后的Fragment和FragmentActivity,都實現(xiàn)了ViewModelStoreOwner接口,而實現(xiàn)的方式也很簡單,就是持有了一個ViewModelStore,那這個東西又是什么呢?看下源碼就知道:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

其實沒有什么特別的,就可以理解成一個HashMap的封裝,而我們的ViewModel會用類名等參數(shù)做為主key被存儲在這里。所以挖了大概三層方法,看似很長,結(jié)論其實很簡單,就是在ViewModelProviders.of(a).getViewModel(XXXViewModel.class) 這樣一個調(diào)用中,如果你傳入的a是一樣的,獲取同樣的XXXViewModel永遠是同一個對象,除非activity或者fragment被onDestroy銷毀重建;

解決問題

利用這樣的特性,我們就可以用來作為同一個activity或者同一個fragment中的通信,比如以下這些場景:

  1. 同一個Fragment上不同UI組件的通信,都通過當前fragment去獲取viewmodel,然后操作viewmodel中的數(shù)據(jù)即可做到一處更改,處處更新;
  2. 同一個activity上fragment之間的通信,都通過當前activity去獲取viewmodel,然后操作viewmodel中的數(shù)據(jù);
    這種用法,既避免了一層層的回調(diào)地獄,也不用定義一個個event,只需要修改數(shù)據(jù)。而且最重要的是,這一切建立在ViewModel+LiveData基礎上,意味著都是對組件的生命周期敏感的,作為開發(fā)者我們一不用考慮各種各樣的register和unregister場景,避免內(nèi)存泄漏,也不用管因為生命周期引起的銷毀重建導致的數(shù)據(jù)不一致問題。
    貼一段網(wǎng)上關于LiveData的特點介紹可以幫助理解:

LiveData的特點:
1)采用觀察者模式,數(shù)據(jù)發(fā)生改變,可以自動回調(diào)(比如更新UI)。
2)不需要手動處理生命周期,不會因為Activity的銷毀重建而丟失數(shù)據(jù)。
3)不會出現(xiàn)內(nèi)存泄漏。
4)不需要手動取消訂閱,Activity在非活躍狀態(tài)下(pause、stop、destroy之后)不會收到數(shù)據(jù)更新信息。

深層思考

這里要更深挖一層,對于ViewModel+LiveData的合理使用有一個更深的理解。以往的使用中,我們將ViewModel作為MVP下Presenter的替代,只是將邏輯換了一種寫法,但其實ViewModel的封裝應該更進一步。比如之前的MVP模式下,P層很少會將數(shù)據(jù)緩存下來,而是View層調(diào)用P層接口,P層通過M層完成數(shù)據(jù)獲取或更新后,直接把數(shù)據(jù)在回調(diào)回V層,P層的每個方法其實是獨立的。但在ViewModel的實現(xiàn)中,應該更進一步的將數(shù)據(jù)封裝在ViewModel中,而數(shù)據(jù)的增查改刪都由ViewModel開放接口給V層調(diào)用,數(shù)據(jù)改變之后直接setValue到LiveData中通知到各個觀察者。
一個具體的例子,還是影片信息的頁面,之前的Presenter或者原來我們寫ViewModel的時候會把FilmInfo的load,修改其中某個狀態(tài)(點贊、想看)抽成一個個相互獨立的接口,然后View層調(diào)用接口,根據(jù)接口的成功失敗來做頁面的刷新。但其實真正的MVVM里,V層應該更薄,所有的數(shù)據(jù)處理都在ViewModel中完成,ViewModel在拿到M層的接口數(shù)據(jù)后,不是簡單的處理下就回調(diào)給View,而是直接修改FilmInfo數(shù)據(jù),而View層的各個組件只管監(jiān)聽FilmInfo的變化就可以做到實時更新。

可能的坑

把LiveData當做event使用也是有一些可能的坑的,下面是通過自己試驗和網(wǎng)上資料整理的風險點,但需要說明的是這些都是特定情境下不滿足某種需求,需要根據(jù)業(yè)務的實際需要來決定是否值得考慮:

  1. setValue()在放入User的時候必須在主線程,否則會報錯,而postValue則沒有這個檢查,而是會把數(shù)據(jù)傳入到主線程。
  2. 使用LiveData監(jiān)聽數(shù)據(jù)變化,LifeCycleOwner在inactive狀態(tài)下,不會觀察到value變化(除非使用observeForever替代observe,但是使用observeForever需要自己管理observer,手動remove),在頁面回到active狀態(tài)后,會收到剛剛變化的值的最后一個value,中間的變化過程會丟失;
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容