Android LiveData詳解
官方文件翻譯
1.LiveData概述
LiveData是一個observable資料持有類。與常規observable不同,LiveData是生命週期感知的,這意味著它跟隨其他應用程式元件(如activities, fragments, or services)的生命週期。這種感知能力確保LiveData只更新處於活躍生命週期狀態的應用程式元件。
注意:在Android工程中匯入LiveData元件,請看Adding Components to your Project
LiveData與一個Observer關聯,如果觀察者的生命週期處於
您可以註冊一個observer並與實現了LifecycleOwner介面的物件配對。這種關係允許當相應的Lifecycle物件的狀態改變為DESTROYED時,觀察者被移除。這對於activities and fragments尤其有用,因為它們可以安全地觀察LiveData物件,而不用擔心洩漏——當activities 和 fragments的生命週期被銷燬時,它們會立即取消訂閱。
有關如何使用LiveData的更多資訊,請參見
1.1 使用LiveData的優點
使用LiveData具有以下優點:
- 確保UI與資料狀態匹配
LiveData遵循觀察者模式。當生命週期狀態改變時,LiveData通知Observer物件。您可以合併程式碼來更新這些觀察者物件中的UI。觀察者可以在資料每次有更新時更新UI。
- 沒有記憶體洩漏
觀察者繫結到生命週期物件,並在其關聯的生命週期是destroyed時自行清理。
- 停止activities造成的crash問題
如果觀察者的生命週期是不活動的,比如在堆疊下面的activity,那麼它接收不到任何LiveData事件。
- 不再手動管理生命週期
UI元件僅僅觀察相關資料,不停止或恢復觀察。LiveData自動管理所有這一切,因為在觀察的時候它能感知到相關的生命週期狀態變化。
- 始終保持最新資料
如果生命週期變得不活動,則在再次啟用時接收最新資料。例如,後臺中的activity在返回到前臺後立即接收最新資料。
- 及時響應配置改變
如果由於配置改變(如裝置旋轉)而重新創activity或fragment,則它立即接收最新的可用資料。
- 資源共享
您可以使用Sigelon模式整合LiveData物件來包裝系統服務,以便它們可以在您的應用程式中共享。一旦LiveData物件連線到系統服務,然後需要用到該資源的任何observer都可以觀察到LiveData物件。有關更多資訊,請參見擴充套件LiveData。
1.2 使用LiveData物件
按照以下步驟使用LiveData物件:
建立一個LiveData例項來儲存某種型別的資料。這通常是在ViewModel類中完成的。
建立一個Observer物件,該物件定義onChanged()方法,該方法響應LiveData物件中資料更改時發生的變化。通常在UI控制器中建立一個Observer物件,例如activity或fragment。
使用observe()方法將觀察者物件與LiveData物件關聯到一塊。observe()方法使用LifecycleOwner物件。這將Observer物件向LiveData物件訂閱,以便通知其更改。通常在UI控制器中新增Observer物件,例如activity 或者 fragment。
注意:您可以使用observeForever(Observer)方法註冊一個沒有關聯LifecycleOwner物件的觀察者。在這種情況下,觀察者被認為總是活躍的,因此總是被通知更新。您可以通過呼叫removeObserver(Observer)方法刪除這些觀察者。
當更新LiveData
物件中儲存的值時,只要所依附LifecycleOwner
處於活動狀態,就會觸發所有已註冊的觀察者。
LiveData允許UI控制器觀察者訂閱更新。當LiveData
物件儲存的資料發生變化時,UI會自動響應更新。
1.2.1 建立LiveData物件
LiveData是一種可以與任何資料一起使用的包裝器,包括實現Collections的物件,如List。LiveData物件通常儲存在ViewModel物件中,並通過getter方法訪問,如下面的示例所示:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
// Rest of the ViewModel...
}
最初,LiveData物件中的資料沒有設定。
注意:請確保將更新UI的LiveData物件儲存在ViewModel物件中,而不是activity 或者 fragment中,原因如下:
避免臃腫的activities和fragments。現在這些UI控制器負責顯示資料,但不儲存資料狀態。
將LiveData例項與特定activity 或者 fragment例項解耦, 並允許LiveData物件在配置更改時存活。
您可以ViewModel嚮導中閱讀更多關於ViewModel類的優點和用法。
1.2.2 觀察LiveData物件
在大多數情況下,應用程式元件的onCreate()方法是開始觀察LiveData物件的正確位置,原因如下:
以確保系統不從activity或fragment的onResume()方法中進行多餘呼叫。
以確保activity或fragment具有可在其活躍後立即顯示的資料。一旦應用程式元件處於STARTED狀態,它就會從正在觀察的LiveData物件接收最新的值。只有在LiveData物件被設定為可觀察狀態時才會發生。
通常,LiveData只在資料更改時才提供更新,並且只對活動的observers提供更新。這種行為的一個例外是,觀察者在從非活動狀態轉變為活動狀態時也會收到更新。此外,如果觀察者第二次從非活動狀態改變為活動狀態,它僅接收到一個更新,如果自上一次變為活動狀態的值發生了改變。
下面的示例程式碼說明如何開始觀察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);
}
}
使用作為引數傳遞的nameObserver呼叫observe()之後,立即呼叫onChanged()以提供儲存在mCurrentName中的最新值。如果LiveData物件沒有在mCurrentName中設定值,則不呼叫onChanged()。
1.2.3 更新LiveData物件
LiveData沒有公開可用的方法來更新儲存的資料。MutableLiveData類公開了setValue(T)和postValue(T)方法,如果需要編輯LiveData物件中儲存的值,則必須使用這些方法。通常在ViewModel中使用MutableLiveData,然後ViewModel只向observers公開不可變的LiveData物件。
在建立了觀察者關係之後,然後可以更新LiveData物件的值,如下面的示例所示,當用戶點選按鈕時觸發所有觀察者:
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
});
呼叫例項中的setValue(T)並傳遞John Doe作為引數會導致觀察者呼叫它們的onChanged()方法。該示例顯示了按下按鈕,但是由於各種原因,可以呼叫setValue()
或postValue()
來更新mName,包括響應網路請求或資料庫載入完成;在所有情況下,對setValue()或postValue()的呼叫都會觸發觀察者並更新UI。
注意:您必須呼叫setValue(T)
方法來從主執行緒更新LiveData物件。如果在工作執行緒中執行程式碼,則可以使用postValue(T)
方法來更新LiveData物件。
1.2.4 在Room中使用LiveData
Room永續性庫支援可觀察的查詢,這些查詢返回LiveData物件。Observable查詢是作為資料庫訪問物件(DAO)的一部分編寫的。
當資料庫被更新時,Room生成所有必要的程式碼來更新LiveData物件。生成的程式碼在需要時非同步地在後臺執行緒上執行查詢。此模式對於保持UI中顯示的資料與儲存在資料庫中的資料同步是有用的。您可以閱讀更多關於Room和DAOs在Room持久庫指南。
1.3 繼承LiveData
如果觀察者的生命週期處於STARTED或RESUMED狀態,則LiveData認為觀察者處於活動狀態。
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);
}
}
本例中價格listener的實現包括以下重要方法:
當LiveData物件具有活動的觀察者時, 呼叫onActive()
方法。這意味著你需要從這個方法開始觀察股票價格的更新。
當LiveData
物件沒有任何活動的觀察者時呼叫onInactive()
方法。由於沒有觀察者在監聽,所以沒有理由保持與StockManager
服務的連線。
setValue(T)
方法更新LiveData例項的值,並將變化通知給任何活動的觀察者。
您可以使用StockLiveData類如下:
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LiveData<BigDecimal> myPriceListener = ...;
myPriceListener.observe(this, price -> {
// Update the UI.
});
}
}
observe()方法將作為LifecycleOwner例項的fragment作為第一個引數傳遞。這樣做意味著這個觀察者被繫結到與所有者相關聯的Lifecycle物件,意思是:
如果生命週期物件不處於活動狀態,則即使值改變,也不會呼叫observer。
在生命週期物件被銷燬後,觀察者將被自動移除。
LiveData物件是生命週期感知的,這意味著您可以在多個activities, fragments, 和 services之間共享它們。為了保持示例簡單,可以將LiveData類作為單例的實現如下:
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);
}
}
你可以在片段中使用它如下:
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
StockLiveData.get(getActivity()).observe(this, price -> {
// Update the UI.
});
}
}
多個fragments和activities可以觀察MyPriceListener例項。LiveData只連線系統服務,如果其中一個或多個系統可見和啟用。
1.4 LiveData變換
在將LiveData物件分發給觀察者之前,您可能需要對儲存在LiveData物件中的值進行更改,或者您可能需要基於另一個LiveData物件的值返回不同的LiveData例項。Lifecycle包提供轉換類,其中包含支援這些方案的幫助方法。
對儲存在LiveData物件中的值應用函式,並將結果傳播到下游。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
類似於map(),將函式應用到LiveData物件中儲存的值中,並解包並將結果分發到下游。傳遞到switchMap()的函式必須返回一個LiveData物件,如下面的示例所示:
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
可以使用transformation方法在觀察者的生命週期中傳遞資訊。除非觀察者正在觀看返回的LiveData物件,否則這些轉換不會被計算。因為轉換是惰性計算的,所以與生命週期相關的行為是隱式傳遞的,而不需要額外的顯式呼叫或依賴。
如果您認為在ViewModel物件中需要一個Lifecycle物件,轉換可能是更好的解決方案。例如,假設您有一個UI元件,它接受一個地址並返回該地址的郵政編碼。可以通過以下示例程式碼說明該元件的初級的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);
}
}
然後,UI元件需要從以前的LiveData
物件中登出註冊,並在每次呼叫getPostalCode()
時註冊到新例項。此外,如果UI元件被重新建立,它將觸發對repository.getPostCode()
方法的另一個呼叫,而不是使用前一個呼叫的結果。
相反,您可以實現postalcode 查詢作為地址輸入的轉換,如下面的示例所示:
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
的轉換,這意味著在addressInput更改時呼叫repository.getPostCode()
方法。如果存在活動觀察者,則這是正確的,如果在呼叫repository.getPostCode()
時沒有活動觀察者,則在新增觀察者之前不進行任何計算。
該機制允許應用程式的較低級別建立按需求計算的LiveData
物件。ViewModel
物件可以很容易地獲得對LiveData物件的引用,然後在它們上面定義轉換規則。
1.4.1 建立新的轉換
在你的應用程式中有十幾種不同的特定轉換,但預設情況下它們不被提供。為了實現您自己的轉換,您可以使用MediatorLiveData
類,該類偵聽其他LiveData物件並處理它們發出的事件。MediatorLiveData
正確地將其狀態傳播到Source LiveData物件。若要了解此模式的更多資訊,請參見Transformations的參考文件。
1.5 合併多個LiveData資料來源
MediatorLiveData是LiveData的子類,允許您合併多個LiveData源。然後,每當原始LiveData源物件改變時,就會觸發MediatorLiveData物件的觀察者。
例如,如果在UI中有一個可以從本地資料庫或網路更新的LiveData物件,則可以向MediatorLiveData物件新增以下源:
與資料庫中儲存的資料相關聯的LiveData物件。
與從網路訪問的資料相關聯的LiveData物件。
您的activity只需要觀察MediatorLiveData物件以從兩個源接收更新。有關詳細示例,請參閱附錄:App體系結構指南中公開網路狀態部分。
1.6 額外資源
LiveData是一個Android Jetpack架構元件。在Sunflower demo應用程式中使用它。