LiveData && ViewModel 使用詳解

前言

在之前的文章中,我們講了Android Architecture components 中的 Lifecycle 組件的詳細使用以及源碼解析。本篇將介紹另外AAC中另外兩個組件:LiveData 和 ViewModel,它們的實現也都是利用了 Lifecycle。

什麼是 LiveData

LiveData 是一個可觀測的數據持有類,但是不同於通常的被觀察者,LiveData 具有生命週期感知能力。通俗點說,LiveData 就是具有 “Live” 能力的 “Data” 持有類。當它所持有的數據發生改變的時候,並且 Lifecycle 對象(比如 Activity 或者 Fragment 等)處於活躍狀態(STARTED 或者 RESUMED),LiveData 將立即通知觀察者數據發生了變化。也就是說,比普通觀察者多了個生命週期感知能力。

LiveData 的優勢

  1. 確保UI和數據狀態匹配。

當數據發生改變的時候,會自動通知UI進行更新。

  1. 避免內存泄漏

Observers 是綁定到 Lifecycle 對象上的,當與其關聯的 lifecycle 被銷燬的時候,它們會自動被清理。

  1. 避免了由於 Activity 停止而導致的閃退

當 Observer 所綁定的 Lifecycle 處於非活躍狀態時,比如處於返回棧中的 Activity,它將不會收到任何 LiveData 事件。

  1. 不再需要手動處理生命週期

UI 組件只需要對相關的數據進行監聽,不需要關心是否應該暫停或者恢復監聽。LiveData 具有生命週期感知能力,它會自動對這些進行管理。

  1. 數據總處於最新狀態

如果一個 Lifecycle 處於非活躍狀態,那當它由非活躍狀態變爲活躍狀態的時候,它將收到最新的數據。比如一個 Activity 由後臺轉爲前臺,這時候它將立即收到最新的數據

  1. 系統配置更改時,進行數據的保存和恢復,及 UI 的恢復。

當 Activity 或者 Fragment 由於配置更改而重新創建時(比如旋轉屏幕等),它將收到最新的可用數據。這裏簡單提一點,這個有點是需要配合 ViewModel 使用的,嚴格來說,它主要是 ViewModel 的優點

  1. 資源共享

我們可以使用單例模式來擴展 LiveData,這樣就能達到數據變化的時候,通知所有的觀察者。

爲了便於理解,關於 LiveData 和 ViewModel 的關係,我這裏先說結論:

LiveData 的作用是在使得數據能具有生命週期感知能力,在 Activity 等變爲活躍狀態的時候,自動回調觀察者中的回調方法。也就是說對數據的變化進行實時監聽。而 ViewModel 的作用則是,當因系統配置發生改變導致 Activity 重建的時候(比如旋轉屏幕),能對 LiveData 進行正確的保存和恢復。僅此而已。

LiveData 的使用

一般來講,LiveData 是需要配合 ViewModel 來使用的,但千萬不要覺得 LiveData 就一定結合 ViewModel。上面也說道二者只是功能互補。這裏爲了便於理解,我們先單獨學習下 LiveData 的使用。

LiveData 的使用分三步:

  1. 創建一個 LiveData 的實例,讓它持有一種特定的數據類型,比如 String 或者 User .通常是將 LiveData 放在ViewModel中使用的(這裏我們先單獨使用)。
  2. 創建一個 Observer 對象,並實現其 onChanged(...) 方法,在這裏定義當 LiveData 持有的數據發生改變的時候,應該做何操作。可以在這進行UI的更新,一般 Observer 是在 UI controller 中創建,比如 Activity 或者 Fragment 。
  3. 通過創建的 LiveData 實例的 observe(...)方法,將 Observer 對象添加進 LiveData 中。方法的原型爲observe( LifecycleOwner owner, Observer<? super T> observer),第一個參數是 LifecycleOwner對象,這也是 LiveData 能監聽生命週期的能力來源。第二個參數就是我們的監聽器對象 Observer 。

添加 LiveData 和 ViewModel 的依賴:

    implementation "android.arch.lifecycle:extensions:1.1.1"

當然,你也可以分別單獨集成 LiveData 和 ViewModel:

implementation "android.arch.lifecycle:livedata:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"

接下來就對照上面講的三步走戰略,創建如下代碼:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";

    private MutableLiveData<Integer> mNumberLiveData;
    private TextView mTvNumber;
    private Button mBtnStart;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvNumber = findViewById(R.id.tv_number);
        mBtnStart = findViewById(R.id.btn_start);
        mBtnStart.setOnClickListener(this);


        mNumberLiveData = new MutableLiveData<>();

        mNumberLiveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer integer) {
                mTvNumber.setText("" + integer);
                Log.d(TAG, "onChanged: " + integer);
            }
        });
    }

    @Override
    public void onClick(View v) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                int number = 0;
                while (number < 5) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    number++;
                    mNumberLiveData.postValue(number);
                }
            }
        }.start();
    }
}

這裏,我們在 onCreate 方法中創建了一個 MutableLiveData 類型的變量 mNumberLiveData ,並將其泛型指定爲 Integer,通過其observe(...)方法把 this 傳進去(this爲 AppCompatActivity,實現了 LifecycleOwner 接口,支持包爲 28.0.0),並傳進去一個 Observer,在其onChanged(...)方法中,我們將變化後的數據 integer 設置給 TextView 顯示。爲了便於觀察,我們同時在控制檯打印一行對應的日誌。

Demo 的界面很簡單,就是一個按鈕,一個 TextView ,點擊按鈕,開啓一個子線程,每過3秒通過postValue(...)修改 LiveData 中的值(如果是在UI線程,可以直接通過 setValue(...)來修改)。

這裏我們點擊開始,並在數字還沒變爲 5 的時候,就按Home鍵進入後臺,等過一段時間之後,在進入頁面,會發現頁面最終顯示爲數字 “5”,但是打印的結果並不是連續的1~5,而是有中斷:

-w785

這也證明了當程序進入後臺,變爲 inactive 狀態時,並不會收到數據更新的通知,而是在重新變爲 active 狀態的時候纔會收到通知,並執行onChanged(...)方法。

上面可以看到,我們使用 LiveData 的時候,實際使用的是它的子類 MutableLiveData,LiveData 是一個接口,它並沒有給我們暴露出來方法供我們對數據進行修改。如果我們需要對數據修改的時候,需要使用它的具體實現類 MutableLiveData,其實該類也只是簡單的將 LiveData 的 postValue(...)setValue(...)暴露了出來:

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

MutableLiveData<T>其實是對數據進行了一層包裹。在它的泛型中可以指定我們的數據類。可以存儲任何數據,包括實現了 Collections 接口的類,比如 List 。

擴展 LiveData

有時候我們需要在 observer 的 lifecycle 處於 active 狀態時做一些操作,那麼我們就可以通過繼承 LiveData 或者 MutableLiveData,然後覆寫其onActive()onInactive()方法。這兩個方法的默認實現均爲空。像下面這樣:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

LiveData 具有生命週期感知能力,能在 Activity 銷燬的時候自動取消監聽,這也意味着它可以用來在多個 Activity 間共享數據。我們可以藉助單例來實現,這裏直接飲用官方 Demo :

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

轉換 LiveData

有時候,我們需要在將 LiveData 中存儲的數據分發給 Observer 之前進行一些修改。比如我們例子中拿到的是 Integer 類型的返回值,我們設置進 TextView 的時候,直接使用mTvNumber.setText(integer)會報錯,需要使用mTvNumber.setText("" + integer)這種形式,但我想在這裏直接拿到已經處理過的 String 數據,拿到就能直接用,而不需要再在這裏手動拼。我們可以通過Transformations類的 map 操作符來實現這個功能。

原始的代碼爲:

        mNumberLiveData = new MutableLiveData<>();

        mNumberLiveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer integer) {
                mTvNumber.setText("" + integer);
                Log.d(TAG, "onChanged: " + integer);
            }
        });

使用 Transformations.map(...)改造之後的代碼:

        mNumberLiveData = new MutableLiveData<Integer>();

        Transformations.map(mNumberLiveData, new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return "" + integer;
            }
        }).observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                mTvNumber.setText(s);
                Log.d(TAG, "onChanged: " + s);
            }
        });

這就實現了將一種類型的數據轉化爲另一種類型的數據。map 操作符會返回一個改造之後的 LiveData,直接對這個 LiveData 進行監聽即可。這裏的map操作符類似於 RxJava 的map

但有時候我們並不只是需要簡單的把數據由一種類型轉爲另一種類型。我們可能需要的更高級一點。

比如,我們一方面需要一個存儲 userId 的 LiveData,另一方面又需要維護一個存儲 User 信息的 LiveData,而後者的 User 則是根據 userId 來從數據庫中查找的,二者需要對應。這時候我們就可以使用Transformations類的switchMap(...)操作符。

MutableLiveData<String> userIdLiveData = new MutableLiveData<>();

LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, new Function<String, LiveData<User>>() {
    @Override
    public LiveData<User> apply(String userId) {
         // 根據 userId 返回一個 LiveData<User>,可以通過Room來獲取
        return getUser(userId);
    }
});

這裏,我們在覆寫的apply(...)方法中,每次 userId 發生變化之後,會自動通過 getUser(userId) 去獲取一個封裝有 User 對象的 LiveData。如果是從數據庫獲取的話,使用 Google 推出的配套的數據庫組件 Room 會比較爽,因爲它能直接返回一個 LiveData。關於 Room,有時間的話之後再寫文章講解。

從上面可以看出,LiveData 包中提供的 Transformations 非常有用,能讓我們的整個調用過程變成鏈式。但 Transformations 只提供了map(...)switchMap(...)兩個方法,如果我們有其他更復雜的需求,就需要自己通過MediatorLiveData類來創建自己的transformations。話說回來,其實上面兩個方法的內部,就是通過MediatorLiveData來實現的,通過 MediatorLiveData 進行了一次轉發。這裏貼出Transformations的源碼:

public class Transformations {

    private Transformations() {
    }


    @MainThread
    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
            @NonNull final Function<X, Y> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(func.apply(x));
            }
        });
        return result;
    }

    
    @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
            @NonNull final Function<X, LiveData<Y>> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }
}

源碼比較簡單,不再詳細講解。

它裏面其實主要用的就是MediatorLiveData,通過該類我們能組合多個 LiveData 源。當任何一個 LiveData 源發生改變的時候,MediatorLiveData的 Observers 都會被觸發,這點比較實用。比如我們有兩個 LiveData,一個是從數據庫獲取,一個是從網絡獲取。通過MediatorLiveData就能做到,當二者任何一個獲取到最新數據,就去觸發我們的監聽。

順便也貼下MediatorLiveData的源碼,它繼承自MutableLiveData

public class MediatorLiveData<T> extends MutableLiveData<T> {
    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();

    @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }

    
    @MainThread
    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
        Source<?> source = mSources.remove(toRemote);
        if (source != null) {
            source.unplug();
        }
    }

    @CallSuper
    @Override
    protected void onActive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().plug();
        }
    }

    @CallSuper
    @Override
    protected void onInactive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().unplug();
        }
    }

    private static class Source<V> implements Observer<V> {
        final LiveData<V> mLiveData;
        final Observer<V> mObserver;
        int mVersion = START_VERSION;

        Source(LiveData<V> liveData, final Observer<V> observer) {
            mLiveData = liveData;
            mObserver = observer;
        }

        void plug() {
            mLiveData.observeForever(this);
        }

        void unplug() {
            mLiveData.removeObserver(this);
        }

        @Override
        public void onChanged(@Nullable V v) {
            if (mVersion != mLiveData.getVersion()) {
                mVersion = mLiveData.getVersion();
                mObserver.onChanged(v);
            }
        }
    }
}

這裏順便提一句,如果想在數據更新的時候讓 Observer立即得到通知,也就是說忽略生命週期狀態,這時候我們可以使用 LiveData 的observeForever(Observer<T> observer)方法。

LiveData 往往是需要結合 ViewModel才能發揮出更大的威力。下面就接着介紹 ViewModel 的知識,以及二者的搭配使用。

什麼是 ViewModel

簡單來講,ViewModel 是一種用來存儲和管理UI相關數據的類。但不同的是,它支持在系統配置發生改變的時候自動對數據進行保存。當然,這要配合 LiveData。

我們知道,在屏幕旋轉的時候,會導致Activity/Fragment重繪,會導致我們之前的數據丟失。就比如,如果我們使用EditText,在裏面輸入了內容,但是屏幕旋轉的時候,會發現其中的text內容被清空了。如果你發現沒清空,可能使用的是 support 包下的控件,或者 Activity 繼承自 AppCompatActivity,並且給該控件添加了 id。系統對一些簡單的數據進行了恢復(其實是在EditText的父類TextView進行的恢復)。

對於一些簡單的數據,我們可以通過在Activity的 onSaveInstanceState()方法中存儲,然後在onCreate()中進行恢復,但是這種方式只適合存儲少量的數據,並且是能被序列化和反序列化的數據。而對那些大量的數據則不適用,比如一個 User 或者 Bitmap 的 List。

此外,它也使得 View 的數據持有者和 UI controller 邏輯更加分離,便於解耦和測試。

LiveData 結合 ViewModel 使用

之前我們是單獨使用 LiveData,這裏配合ViewModel使用:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

可以看到,這裏我們創建一個類,繼承自ViewModel,然後在裏面存儲我們需要的MutableLiveData字段。注意,getUsers()方法返回的類型是LiveData而非 MutableLiveData,因爲我們一般不希望在ViewModel 外面對數據進行修改,所以返回的是一個不可變的 LiveData 引用。如果想對數據進行更改,我們可以暴露出來一個setter方法。

接下來可以按照如下的方式獲取 ViewModel:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

我們在onCreate()方法中通過ViewModelProviders.of(this).get(MyViewModel.class);這行代碼來獲取一個MyViewModel實例。之後又通過該實例暴露出來的getter方法獲取LiveData 實例。這裏要注意,當Activity重建的時候,雖然 onCreate() 方法會重新走一遍,但是這個MyViewModel實例,仍然是第一次創建的那個實例,在ViewModelProviders.of(this).get(***.class)中的get方法中進行了緩存。之後進行源碼解析的時候會詳細講解。先看下下面的一張圖,瞭解下 ViewModel 的整個生命週期:

viewmodel-lifecycle

ViewModel 最終消亡是在 Activity 被銷燬的時候,會執行它的onCleared()進行數據的清理。

Fragment 間進行數據共享

Fragment 間共享數據比較常見。一種典型的例子是屏幕左側是一個 Fragment,其中存儲了一個新聞標題列表,我們點擊一個 item,在右側的 Fragment 中顯示該新聞的詳細內容。這種場景在美團等訂餐軟件中也很常見。

通過 ViewModel 將使得數據在各 Fragment 之間的共享變得更加簡單。

我們需要做的僅僅是在各 Fragment 的 onCreate() 方法中通過:

ViewModelProviders.of(getActivity()).get(***ViewModel.class);

來獲取 ViewModel ,注意of(...)方法中傳入的是二者所在的activity。具體可以參考如下官方代碼:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 傳入 activity
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 傳入 activity
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

Android 3.0 中引入了 Loader 機制,讓開發者能輕鬆在 Activity 和 Fragment 中異步加載數據。但事實上用的人並不多。現在,它幾乎可以退出歷史舞臺了。ViewModel配合Room數據庫以及LiveData,完全可以替代Loader,在SDK28裏,也越來越多的用Loader也越來越多的被替代。

但要注意,ViewModel能用來替換Loader,但是它卻並不是設計用來替換onSaveInstanceState(...)的。關於數據持久化以及恢復UI狀態等,可以參考下Medium上的這篇文章,講的簡直不能再好了:ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders

總結

通常 LiveData 是需要配合 ViewModel 使用的。ViewModel 負責在系統配置更改時保存和恢復 LiveData,而 LiveData 則負責在生命週期狀態發生改變的時候,對數據的變化進行監聽。

寫到這裏算是把 LiveData 和 ViewModel 的使用講完了。這裏我在開篇故意單獨把 LiveData 和 ViewModel 分開講解,相比較官網更加容易理解。但如果想對二者進行詳細瞭解,還是建議把官方文檔認真的多閱讀幾遍。

歡迎關注公衆號來獲取最新消息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章