LiveData && ViewModel使用詳解

warmcheng發表於2019-04-07

前言

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

什麼是 LiveData

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

LiveData 的優勢

  1. 確保UI和資料狀態匹配。
    當資料發生改變的時候,會自動通知UI進行更新。

  2. 避免記憶體洩漏
    Observers 是繫結到 Lifecycle 物件上的,當與其關聯的 lifecycle 被銷燬的時候,它們會自動被清理。

  3. 避免了由於 Activity 停止而導致的閃退
    當 Observer 所繫結的 Lifecycle 處於非活躍狀態時,比如處於返回棧中的 Activity,它將不會收到任何 LiveData 事件。

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

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

  6. 系統配置更改時,進行資料的儲存和恢復,及 UI 的恢復。
    當 Activity 或者 Fragment 由於配置更改而重新建立時(比如旋轉螢幕等),它將收到最新的可用資料。這裡簡單提一點,這個有點是需要配合 ViewModel 使用的,嚴格來說,它主要是 ViewModel 的優點

  7. 資源共享
    我們可以使用單例模式來擴充套件 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 observer),第一個引數是 LifecycleOwner物件,這也是 LiveData 能監聽生命週期的能力來源。第二個引數就是我們的監聽器物件 Observer 。

新增 LiveData 和 ViewModel 的依賴:

1    implementation "android.arch.lifecycle:extensions:1.1.1"
複製程式碼

當然,你也可以分別單獨整合 LiveData 和 ViewModel:

1implementation "android.arch.lifecycle:livedata:1.1.1"
複製程式碼
1implementation "android.arch.lifecycle:viewmodel:1.1.1"
複製程式碼

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

 1public class MainActivity extends AppCompatActivity implements View.OnClickListener {
2
3    private static final String TAG = "MainActivity";
4
5    private MutableLiveData<Integer> mNumberLiveData;
6    private TextView mTvNumber;
7    private Button mBtnStart;
8
9    @Override
10    protected void onCreate(Bundle savedInstanceState) {
11        super.onCreate(savedInstanceState);
12        setContentView(R.layout.activity_main);
13        mTvNumber = findViewById(R.id.tv_number);
14        mBtnStart = findViewById(R.id.btn_start);
15        mBtnStart.setOnClickListener(this);
16
17
18        mNumberLiveData = new MutableLiveData<>();
19
20        mNumberLiveData.observe(thisnew Observer<Integer>() {
21            @Override
22            public void onChanged(@Nullable Integer integer) {
23                mTvNumber.setText("" + integer);
24                Log.d(TAG, "onChanged: " + integer);
25            }
26        });
27    }
28
29    @Override
30    public void onClick(View v) {
31        new Thread() {
32            @Override
33            public void run() {
34                super.run();
35                int number = 0;
36                while (number < 5) {
37                    try {
38                        Thread.sleep(3000);
39                    } catch (InterruptedException e) {
40                        e.printStackTrace();
41                    }
42                    number++;
43                    mNumberLiveData.postValue(number);
44                }
45            }
46        }.start();
47    }
48}
複製程式碼

這裡,我們在 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
-w785

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

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

 1public class MutableLiveData<Textends LiveData<T{
2    @Override
3    public void postValue(T value) {
4        super.postValue(value);
5    }
6
7    @Override
8    public void setValue(T value) {
9        super.setValue(value);
10    }
11}
複製程式碼

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

擴充套件 LiveData

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

 1public class StockLiveData extends LiveData<BigDecimal{
2    private StockManager stockManager;
3
4    private SimplePriceListener listener = new SimplePriceListener() {
5        @Override
6        public void onPriceChanged(BigDecimal price) {
7            setValue(price);
8        }
9    };
10
11    public StockLiveData(String symbol) {
12        stockManager = new StockManager(symbol);
13    }
14
15    @Override
16    protected void onActive() {
17        stockManager.requestPriceUpdates(listener);
18    }
19
20    @Override
21    protected void onInactive() {
22        stockManager.removeUpdates(listener);
23    }
24}
複製程式碼

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

 1public class StockLiveData extends LiveData<BigDecimal{
2    private static StockLiveData sInstance;
3    private StockManager stockManager;
4
5    private SimplePriceListener listener = new SimplePriceListener() {
6        @Override
7        public void onPriceChanged(BigDecimal price) {
8            setValue(price);
9        }
10    };
11
12    @MainThread
13    public static StockLiveData get(String symbol) {
14        if (sInstance == null) {
15            sInstance = new StockLiveData(symbol);
16        }
17        return sInstance;
18    }
19
20    private StockLiveData(String symbol) {
21        stockManager = new StockManager(symbol);
22    }
23
24    @Override
25    protected void onActive() {
26        stockManager.requestPriceUpdates(listener);
27    }
28
29    @Override
30    protected void onInactive() {
31        stockManager.removeUpdates(listener);
32    }
33}
複製程式碼

轉換 LiveData

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

原始的程式碼為:

1        mNumberLiveData = new MutableLiveData<>();
2
3        mNumberLiveData.observe(thisnew Observer<Integer>() {
4            @Override
5            public void onChanged(@Nullable Integer integer) {
6                mTvNumber.setText("" + integer);
7                Log.d(TAG, "onChanged: " + integer);
8            }
9        });
複製程式碼

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

 1        mNumberLiveData = new MutableLiveData<Integer>();
2
3        Transformations.map(mNumberLiveData, new Function<Integer, String>() {
4            @Override
5            public String apply(Integer integer) {
6                return "" + integer;
7            }
8        }).observe(thisnew Observer<String>() {
9            @Override
10            public void onChanged(@Nullable String s) {
11                mTvNumber.setText(s);
12                Log.d(TAG, "onChanged: " + s);
13            }
14        });
複製程式碼

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

但有時候我們並不只是需要簡單的把資料由一種型別轉為另一種型別。我們可能需要的更高階一點。

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

1MutableLiveData<String> userIdLiveData = new MutableLiveData<>();
2
3LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, new Function<String, LiveData<User>>() {
4    @Override
5    public LiveData<User> apply(String userId) {
6         // 根據 userId 返回一個 LiveData<User>,可以通過Room來獲取
7        return getUser(userId);
8    }
9});
複製程式碼

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

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

 1public class Transformations {
2
3    private Transformations() {
4    }
5
6
7    @MainThread
8    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
9            @NonNull final Function<X, Y> func)
 
{
10        final MediatorLiveData<Y> result = new MediatorLiveData<>();
11        result.addSource(source, new Observer<X>() {
12            @Override
13            public void onChanged(@Nullable X x) {
14                result.setValue(func.apply(x));
15            }
16        });
17        return result;
18    }
19
20
21    @MainThread
22    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
23            @NonNull final Function<X, LiveData<Y>> func)
 
{
24        final MediatorLiveData<Y> result = new MediatorLiveData<>();
25        result.addSource(trigger, new Observer<X>() {
26            LiveData<Y> mSource;
27
28            @Override
29            public void onChanged(@Nullable X x) {
30                LiveData<Y> newLiveData = func.apply(x);
31                if (mSource == newLiveData) {
32                    return;
33                }
34                if (mSource != null) {
35                    result.removeSource(mSource);
36                }
37                mSource = newLiveData;
38                if (mSource != null) {
39                    result.addSource(mSource, new Observer<Y>() {
40                        @Override
41                        public void onChanged(@Nullable Y y) {
42                            result.setValue(y);
43                        }
44                    });
45                }
46            }
47        });
48        return result;
49    }
50}
複製程式碼

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

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

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

 1public class MediatorLiveData<Textends MutableLiveData<T{
2    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
3
4    @MainThread
5    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
6        Source<S> e = new Source<>(source, onChanged);
7        Source<?> existing = mSources.putIfAbsent(source, e);
8        if (existing != null && existing.mObserver != onChanged) {
9            throw new IllegalArgumentException(
10                    "This source was already added with the different observer");
11        }
12        if (existing != null) {
13            return;
14        }
15        if (hasActiveObservers()) {
16            e.plug();
17        }
18    }
19
20
21    @MainThread
22    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
23        Source<?> source = mSources.remove(toRemote);
24        if (source != null) {
25            source.unplug();
26        }
27    }
28
29    @CallSuper
30    @Override
31    protected void onActive() {
32        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
33            source.getValue().plug();
34        }
35    }
36
37    @CallSuper
38    @Override
39    protected void onInactive() {
40        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
41            source.getValue().unplug();
42        }
43    }
44
45    private static class Source<Vimplements Observer<V{
46        final LiveData<V> mLiveData;
47        final Observer<V> mObserver;
48        int mVersion = START_VERSION;
49
50        Source(LiveData<V> liveData, final Observer<V> observer) {
51            mLiveData = liveData;
52            mObserver = observer;
53        }
54
55        void plug() {
56            mLiveData.observeForever(this);
57        }
58
59        void unplug() {
60            mLiveData.removeObserver(this);
61        }
62
63        @Override
64        public void onChanged(@Nullable V v) {
65            if (mVersion != mLiveData.getVersion()) {
66                mVersion = mLiveData.getVersion();
67                mObserver.onChanged(v);
68            }
69        }
70    }
71}
複製程式碼

這裡順便提一句,如果想在資料更新的時候讓 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使用:

 1public class MyViewModel extends ViewModel {
2    private MutableLiveData<List<User>> users;
3    public LiveData<List<User>> getUsers() {
4        if (users == null) {
5            users = new MutableLiveData<List<User>>();
6            loadUsers();
7        }
8        return users;
9    }
10
11    private void loadUsers() {
12        // Do an asynchronous operation to fetch users.
13    }
14}
複製程式碼

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

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

 1public class MyActivity extends AppCompatActivity {
2    public void onCreate(Bundle savedInstanceState) {
3        // Create a ViewModel the first time the system calls an activity's onCreate() method.
4        // Re-created activities receive the same MyViewModel instance created by the first activity.
5
6        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
7        model.getUsers().observe(this, users -> {
8            // update UI
9        });
10    }
11}
複製程式碼

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

viewmodel-lifecycle
viewmodel-lifecycle

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

Fragment 間進行資料共享

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

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

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

1ViewModelProviders.of(getActivity()).get(***ViewModel.class);
複製程式碼

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

 1public class SharedViewModel extends ViewModel {
2    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
3
4    public void select(Item item) {
5        selected.setValue(item);
6    }
7
8    public LiveData<Item> getSelected() {
9        return selected;
10    }
11}
12
13
14public class MasterFragment extends Fragment {
15    private SharedViewModel model;
16    public void onCreate(Bundle savedInstanceState) {
17        super.onCreate(savedInstanceState);
18        // 傳入 activity
19        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
20        itemSelector.setOnClickListener(item -> {
21            model.select(item);
22        });
23    }
24}
25
26public class DetailFragment extends Fragment {
27    public void onCreate(Bundle savedInstanceState) {
28        super.onCreate(savedInstanceState);
29        // 傳入 activity
30        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
31        model.getSelected().observe(this, { item ->
32           // Update the UI.
33        });
34    }
35}
複製程式碼

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 分開講解,相比較官網更加容易理解。但如果想對二者進行詳細瞭解,還是建議把官方文件認真的多閱讀幾遍。

歡迎關注公眾號來獲取最新訊息。

相關文章