LiveData
LiveData是一種持有可被觀察數(shù)據(jù)的類脱盲。和其他可被觀察的類不同的是磕潮,LiveData是有生命周期感知能力的,這意味著它可以在activities, fragments, 或者 services生命周期是活躍狀態(tài)時更新這些組件。那么什么是活躍狀態(tài)呢?上篇文章中提到的STARTED和RESUMED就是活躍狀態(tài)跨新,只有在這兩個狀態(tài)下LiveData是會通知數(shù)據(jù)變化的。
要想使用LiveData(或者這種有可被觀察數(shù)據(jù)能力的類)就必須配合實(shí)現(xiàn)了LifecycleOwner的對象使用坏逢。在這種情況下域帐,當(dāng)對應(yīng)的生命周期對象DESTORY時赘被,才能移除觀察者。這對Activity或者Fragment來說顯得尤為重要肖揣,因為他們可以在生命周期結(jié)束的時候立刻解除對數(shù)據(jù)的訂閱帘腹,從而避免內(nèi)存泄漏等問題。
使用LiveData的優(yōu)點(diǎn)
- UI和實(shí)時數(shù)據(jù)保持一致 因為LiveData采用的是觀察者模式许饿,這樣一來就可以在數(shù)據(jù)發(fā)生改變時獲得通知阳欲,更新UI。
- 避免內(nèi)存泄漏 觀察者被綁定到組件的生命周期上陋率,當(dāng)被綁定的組件銷毀(destory)時球化,觀察者會立刻自動清理自身的數(shù)據(jù)。
- 不會再產(chǎn)生由于Activity處于stop狀態(tài)而引起的崩潰 例如:當(dāng)Activity處于后臺狀態(tài)時瓦糟,是不會收到LiveData的任何事件的筒愚。
- 不需要再解決生命周期帶來的問題 LiveData可以感知被綁定的組件的生命周期,只有在活躍狀態(tài)才會通知數(shù)據(jù)變化菩浙。
- 實(shí)時數(shù)據(jù)刷新 當(dāng)組件處于活躍狀態(tài)或者從不活躍狀態(tài)到活躍狀態(tài)時總是能收到最新的數(shù)據(jù)
- 解決Configuration Change問題 在屏幕發(fā)生旋轉(zhuǎn)或者被回收再次啟動巢掺,立刻就能收到最新的數(shù)據(jù)。
- 數(shù)據(jù)共享 如果對應(yīng)的LiveData是單例的話劲蜻,就能在app的組件間分享數(shù)據(jù)陆淀。這部分詳細(xì)的信息可以參考繼承LiveData
使用LiveData
- 創(chuàng)建一個持有某種數(shù)據(jù)類型的LiveData (通常是在ViewModel中)
- 創(chuàng)建一個定義了onChange()方法的觀察者。這個方法是控制LiveData中數(shù)據(jù)發(fā)生變化時先嬉,采取什么措施 (比如更新界面)轧苫。通常是在UI Controller (Activity/Fragment) 中創(chuàng)建這個觀察者。
- 通過 observe()方法連接觀察者和LiveData疫蔓。observe()方法需要攜帶一個LifecycleOwner類含懊。這樣就可以讓觀察者訂閱LiveData中的數(shù)據(jù),實(shí)現(xiàn)實(shí)時更新衅胀。
創(chuàng)建LiveData對象
LiveData是一個數(shù)據(jù)的包裝岔乔。具體的包裝對象可以是任何數(shù)據(jù),包括集合(比如List)滚躯。LiveData通常在ViewModel中創(chuàng)建雏门,然后通過gatter方法獲取。具體可以看一下代碼:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String 暫時就把MutableLiveData看成是LiveData吧哀九,下面的文章有詳細(xì)的解釋
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
// Rest of the ViewModel...
}
觀察LiveData中的數(shù)據(jù)
通常情況下都是在組件的onCreate()方法中開始觀察數(shù)據(jù)剿配,原因有以下兩點(diǎn):
- 系統(tǒng)會多次調(diào)用onResume()方法。
- 確保Activity/Fragment在處于活躍狀態(tài)時立刻可以展示數(shù)據(jù)阅束。
下面的代碼展示了如何觀察LiveData對象:
public class NameActivity extends AppCompatActivity {
private NameViewModel mModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable final String newName) {
// Update the UI, in this case, a TextView.
mNameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);
}
}
更新LiveData對象
如果想要在UI Controller中改變LiveData
中的值呢呼胚?(比如點(diǎn)擊某個Button把性別從男設(shè)置成女)。LiveData
并沒有提供這樣的功能息裸,但是Architecture Component提供了MutableLiveData這樣一個類蝇更,可以通過setValue(T)
和postValue(T)
方法來修改存儲在LiveData中的數(shù)據(jù)沪编。MutableLiveData是LiveData
的一個子類,從名稱上也能看出這個類的作用年扩。舉個直觀點(diǎn)的例子:
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
});
調(diào)用setValue()
方法就可以把LiveData
中的值改為John Doe
蚁廓。同樣,通過這種方法修改LiveData
中的值同樣會觸發(fā)所有對這個數(shù)據(jù)感興趣的類厨幻。那么setValue()
和postValue()
有什么不同呢相嵌?區(qū)別就是setValue()
只能在主線程中調(diào)用,而postValue()
可以在子線程中調(diào)用况脆。
Room和LiveData配合使用
Room可以返回LiveData的數(shù)據(jù)類型饭宾。這樣對數(shù)據(jù)庫中的任何改動都會被傳遞出去。這樣修改完數(shù)據(jù)庫就能獲取最新的數(shù)據(jù)格了,減少了主動獲取數(shù)據(jù)的代碼看铆。詳細(xì)的例子在前面的文章,不記得可以回去翻翻盛末。
繼承LiveData擴(kuò)展功能
LiveData的活躍狀態(tài)包括:STARTED或者RESUMED兩種狀態(tài)弹惦。那么如何在活躍狀態(tài)下把數(shù)據(jù)傳遞出去呢?下面是示例代碼:
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager mStockManager;
private SimplePriceListener mListener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};
public StockLiveData(String symbol) {
mStockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
mStockManager.requestPriceUpdates(mListener);
}
@Override
protected void onInactive() {
mStockManager.removeUpdates(mListener);
}
}
可以看到onActive()
和onInactive()
就表示了處于活躍和不活躍狀態(tài)的回調(diào)悄但。
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LiveData<BigDecimal> myPriceListener = ...;
myPriceListener.observe(this, price -> {
// Update the UI.
});
}
}
如果把StockLiveData
寫成單例模式棠隐,那么還可以在不同的組件間共享數(shù)據(jù)。代碼如下:
public class StockLiveData extends LiveData<BigDecimal> {
private static StockLiveData sInstance;
private StockManager mStockManager;
private SimplePriceListener mListener = 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) {
mStockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
mStockManager.requestPriceUpdates(mListener);
}
@Override
protected void onInactive() {
mStockManager.removeUpdates(mListener);
}
}
轉(zhuǎn)換LiveData中的值(Transform LiveData)
這么說很容易和上文改變LiveData中的值搞混算墨。這里的變換是指在LiveData的數(shù)據(jù)被分發(fā)到各個組件之前轉(zhuǎn)換值的內(nèi)容宵荒,各個組件收到的是轉(zhuǎn)換后的值,但是LiveData里面數(shù)據(jù)本身的值并沒有改變净嘀。(和RXJava中map的概念很像)Lifecycle包中提供了Transformations來提供轉(zhuǎn)換的功能。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
把原來是包含User的LiveData轉(zhuǎn)換成包含String的LiveData傳遞出去侠讯。
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
和上面的map()
方法很像。區(qū)別在于傳遞給switchMap()
的函數(shù)必須返回LiveData對象膜眠。
和LiveData一樣炸宵,Transformation
也可以在觀察者的整個生命周期中存在捎琐。只有在觀察者處于觀察LiveData狀態(tài)時,Transformation
才會運(yùn)算练慕。Transformation
是延遲運(yùn)算的(calculated lazily)零截,而生命周期感知的能力確保不會因為延遲發(fā)生任何問題哪工。
如果在ViewModel
對象的內(nèi)部需要一個Lifecycle
對象撤嫩,那么使用Transformation
是一個不錯的方法。舉個例子:假如有個UI組件接受輸入的地址,返回對應(yīng)的郵政編碼。那么可以 實(shí)現(xiàn)一個ViewModel
和這個組件綁定:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS (不要這么干)
return repository.getPostCode(address);
}
}
看代碼中的注釋,有個// DON'T DO THIS (不要這么干)
,這是為什么?有一種情況是如果UI組件被回收后又被重新創(chuàng)建们衙,那么又會觸發(fā)一次 repository.getPostCode(address)
查詢宗侦,而不是重用上次已經(jīng)獲取到的查詢馋袜。那么應(yīng)該怎樣避免這個問題呢泽台?看一下下面的代碼:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
private final MutableLiveData<String> addressInput = new MutableLiveData();
public final LiveData<String> postalCode =
Transformations.switchMap(addressInput, (address) -> {
return repository.getPostCode(address);
});
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository
}
private void setInput(String address) {
addressInput.setValue(address);
}
}
postalCode
變量的修飾符是public
和final
,因為這個變量的是不會改變的怀酷。哎稻爬?不會改變?那我輸入不同的地址還總返回相同郵編蜕依?先打住桅锄,postalCode
這個變量存在的作用是把輸入的addressInput
轉(zhuǎn)換成郵編,那么只有在輸入變化時才會調(diào)用repository.getPostCode()
方法样眠。這就好比你用final來修飾一個數(shù)組友瘤,雖然這個變量不能再指向其他數(shù)組,但是數(shù)組里面的內(nèi)容是可以被修改的檐束。繞來繞去就一點(diǎn):當(dāng)輸入是相同的情況下商佑,用了 switchMap()
可以減少沒有必要的請求。并且同樣厢塘,只有在觀察者處于活躍狀態(tài)時才會運(yùn)算并將結(jié)果通知觀察者。(最近一直有大佬反應(yīng)這段話有問題肌幽,感謝各位大佬指正晚碾,我又看了遍英文文檔,確實(shí)有邏輯不通的地方喂急。原文中的話就是:In this case, the postalCode field is public and final, because the field never changes. The postalCode field is defined as a transformation of the addressInput, which means that the repository.getPostCode() method is called when addressInput changes. This is true if there is an active observer, if there are no active observers at the time repository.getPostCode() is called, no calculations are made until an observer is added.翻譯過來就是通過addressInput
做一層緩沖格嘁,只有addressInput
發(fā)生改變時才會真的發(fā)生一次查詢,UI組件回收再被重建后重新設(shè)置了addressInput
中內(nèi)容廊移,但是addressInput
中內(nèi)容和上次(重建前)一樣糕簿,因此不會觸發(fā)真正的查詢探入,在這個意義上來講減少了查詢次數(shù)。所以使用switchMap()
的意義就在于對底層返回的LiveData根據(jù)需要變更其中的內(nèi)容懂诗,同時不會影響生命周期控制邏輯蜂嗽。再次感謝各位大佬的較真,你們的指正是我不斷前進(jìn)的動力Q旰恪V簿伞!如果覺得還有有邏輯問題离唐,歡迎溝通2「健!亥鬓!)
合并多個LiveData中的數(shù)據(jù)
MediatorLiveData是LiveData的子類完沪,可以通過MediatorLiveData
合并多個LiveData來源的數(shù)據(jù)。同樣任意一個來源的LiveData數(shù)據(jù)發(fā)生變化嵌戈,MediatorLiveData
都會通知觀察他的對象覆积。說的有點(diǎn)抽象,舉個例子咕别。比如UI接收來自本地數(shù)據(jù)庫和網(wǎng)絡(luò)數(shù)據(jù)技健,并更新相應(yīng)的UI《韫埃可以把下面兩個LiveData加入到MeidatorLiveData中:
- 關(guān)聯(lián)數(shù)據(jù)庫的LiveData
- 關(guān)聯(lián)聯(lián)網(wǎng)請求的LiveData
相應(yīng)的UI只需要關(guān)注MediatorLiveData就可以在任意數(shù)據(jù)來源更新時收到通知雌贱。
相關(guān)文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)
理解Android Architecture Components系列之WorkManager(八)