?LiveData 是對可觀察數(shù)據(jù)的封裝。不像其他可觀察對象(例如 ObservableField) , LiveData 可以感知到生命周期偿渡。這就意味著它可以關(guān)聯(lián)到其他擁有生命周期的組件上帆谍,比如 Activity、Fragment 或者 Service沥寥。這種感知碍舍,可以確保 LinveData 的更新只發(fā)生在一個組件的活動
狀態(tài)上。如下圖所示:
?對于一個觀察者類而言邑雅,所謂的激活狀態(tài)就是 STARTED或者 RESUMED 狀態(tài)片橡。非激活狀態(tài)并不更新。
?對于 Activity 來說淮野,在 onStart 之后捧书,到 onPause 之前,就是 STARTED骤星;在 onResume 調(diào)用之后经瓷,就是 RESUMED 狀態(tài)。
?通常洞难,我們總是定義一個實(shí)現(xiàn)了 LifecyclerOwner 接口對象作為觀察者舆吮。這種關(guān)系,會使得其在 DESTROY 狀態(tài)時队贱,自動移除對數(shù)的觀察色冀。
LiveData 的優(yōu)勢
使用 LiveData 有以下優(yōu)勢:
- 確保 UI 和當(dāng)前的數(shù)據(jù)狀態(tài)匹配:LiveData 提供了一種觀察者模式。當(dāng)觀察者的生命周期狀態(tài)發(fā)生變化時柱嫌,它會適時更新將數(shù)據(jù)更新到 UI 上锋恬。而并非是任何時候,都會對 UI 進(jìn)行更新编丘。
- 避免內(nèi)存泄漏:觀察者是一個 Lifecycle 對象与学。當(dāng) LiveData 所關(guān)聯(lián)的觀察者被銷毀時,LiveData 會自動清理自己嘉抓。
- 避免因 stop activity 造成的奔潰:當(dāng)觀察者對象處于非活動狀態(tài)時索守,比如 activity 返回到回退棧中,此時抑片,它將無法接收到 LiveData 的數(shù)據(jù)更新事件蕾盯。
- 不用手動處理生命周期:UI 組件觀察相關(guān)的數(shù)據(jù),但是并不會主動停止或者繼續(xù)這種觀察。當(dāng)觀察者生命周期發(fā)生變化時级遭,LiveData 會自動管理自己望拖。
- 總是更新到最新的數(shù)據(jù):當(dāng)組件從 非活動 狀態(tài)轉(zhuǎn)換到 活動 狀態(tài)時,他講更新到最新的數(shù)據(jù)挫鸽。
- 正確的處理 configuration 的變化:當(dāng) activity 或者 fragment 由于 configuration(比如說屏幕旋轉(zhuǎn)) 的變化而被創(chuàng)建時说敏,它會自動接收到最新的可用數(shù)據(jù)。
- 資源共享:我們可以使用單例模式繼承一個 LiveData丢郊,當(dāng)然將它綁定到一個系統(tǒng)服務(wù)中盔沫,這種這個 LiveData 就可以共享了。
LiveData 的使用
- 首先枫匾,創(chuàng)建一個持有數(shù)據(jù)的 LiveData 對象架诞。這一步通常是在 ViewModel 中完成。
- 創(chuàng)建一個 Observer 對象干茉,并定義其 onChange() 方法谴忧。該方法將控制在 LiveData 所持有的數(shù)據(jù)發(fā)生變化時,觀察者將發(fā)生怎樣的變化角虫。我們通常創(chuàng)建在 UI controller 中創(chuàng)建 Observer沾谓。而這類 UI controller 諸如 activity 和 fragment。
- 通過 observe() 方法戳鹅,將 Observer(觀察者)和 LiveData(被觀察者)綁定在一起均驶。這樣以來,當(dāng) LiveData 數(shù)據(jù)發(fā)生變化時枫虏,只要 Observer 處于 活動 狀態(tài)妇穴,將自動通知 Observer 。
創(chuàng)建 LiveData 對象
?LiveData 可以包裹任何數(shù)據(jù)隶债,包括集合類腾它,比如 List。LiveData 通常存儲在 ViewModel 中燃异,通過 getter 方法提供給觀察者。
public class UserViewModel extends ViewModel {
MutableLiveData<String> userName;
UserViewModel(){
userName = new MutableLiveData<>();
}
public LiveData<String> getUserName(){
return userName;
}
public void setUserName(String name){
userName.setValue(name);
}
}
?綜上继蜡,我們看到 UI controller回俐,比如 activity 或者 fragment 僅僅負(fù)責(zé)顯示數(shù)據(jù),而不再管理數(shù)據(jù)狀態(tài)稀并。如此一來仅颇,將大大避免了 UI controller 的臃腫。
訂閱 LiveData 對象
?通常碘举,組件的 onCreate() 方法忘瓦,是個合適的地方以建立對 LiveData 的觀察或者說是訂閱,理由如下:
- onCreate() 方法在創(chuàng)建的時候引颈,只會調(diào)用一次耕皮。
- 確保 UI controller 處于 活動 狀態(tài)時境蜕,能夠有數(shù)據(jù)顯示。
?LiveData 只會在數(shù)據(jù)變化凌停,同時觀察者處于 活動 狀態(tài)時粱年,才會通知觀察者更新。當(dāng)然罚拟,第一次初始顯示數(shù)據(jù)除外台诗,數(shù)據(jù)被初始化,直接通知處于 活動狀態(tài)的 UI controller 進(jìn)行數(shù)據(jù)更新赐俗。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
mainViewModel.getEncryptedFileNum().observe(this, num -> {
encryptedFileNumText.setText(String.format("文件 %d 個", num));
});
}
?當(dāng) observe() 方法調(diào)用后拉队,onChange() 方法被立即調(diào)用,為 encyptedFileNumText 提供最新的值阻逮。隨后粱快,只有 mainViewModel 中的 encryptedFileNum 發(fā)生變化,且該 UI controller 處于 活動 狀態(tài)夺鲜,encyptedFileNumText 才會更新相應(yīng) UI皆尔。
更新 LiveData 對象
?LiveData 本身沒有公開可用的方法用以更新數(shù)據(jù)。MultableLiveData 則暴露了 setValue(T) 和 postValue(T) 方法來更新 LiveData 中的數(shù)據(jù)币励。注意慷蠕,setValue 方法用于在主線程中更新值,而 postValue 則用于在工作線程中更新值食呻。
private MutableLiveData<String> addressName ;
public void setAddressName(String name) {
addressName.setValue(name);
}
one-way data binding VS two-way data binding
?在單向綁定中流炕,我們通過改變 LiveData 中的值,來更新 UI 仅胞。通常每辟,我們還需要當(dāng)用戶對 UI 進(jìn)行了操作之后,所帶了的變化能反饋到 LiveData 的值上干旧,即自動更新 LiveData 中的值渠欺。這一點(diǎn),在 LiveData 中很容易做到椎眯。
單向綁定:
<CheckBox
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/checkbox_style"
android:button="@null"
android:checked="@{pickerBean.selected}"
android:visibility="@{pickerBean.checkVisible? View.VISIBLE:View.GONE,default=visible}"/>
雙向綁定:
<CheckBox
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/checkbox_style"
android:button="@null"
android:checked="@={pickerBean.selected}"
android:visibility="@{pickerBean.checkVisible? View.VISIBLE:View.GONE,default=visible}"/>
?注意挠将,單向綁定和雙向綁定在 XML 中的唯一區(qū)別,就是 android:checked="@={pickerBean.selected}" 中 @ 后面是否有等號编整。
使用自定義屬性進(jìn)行雙向綁定
?上個代碼塊中舔稀,我們對 checked 屬性使用了雙向綁定。那么掌测,如果是我們自定義的屬性該如何處理内贮?
?為了達(dá)到這個目的,需要使用 @InverseBindingAdapter 和 @InverseBindingMethod 注解。
?以為 MyView 綁定設(shè)置 時間 為例夜郁。首先什燕,需要使用 @BindingAdapter
@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
// Important to break potential infinite loops.
if (view.time != newValue) {
view.time = newValue;
}
}
?然后,使用 @InverseBindingAdapter
注解拂酣,告訴它當(dāng) MyView 的屬性發(fā)生變化時秋冰,該調(diào)用哪個方法:
@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
return view.getTime();
}
?應(yīng)當(dāng)注意,當(dāng)使用雙向綁定時婶熬,不要發(fā)生的無限調(diào)用的陷阱剑勾。當(dāng)用戶改變了 View 的屬性,@InverseBindingAdapter
被調(diào)用了赵颅。LiveData 中的值發(fā)生了變化虽另,這將導(dǎo)致 @BindingAdapter
所注解的方法被調(diào)用。如此一來饺谬,可能會在 @InverseBindingAdapter
和 @BindingAdapter
兩個注解方法中無限循環(huán)下去捂刺。為了防止這種事情發(fā)生,可以參考上述 setTime
方法中的應(yīng)用募寨。
應(yīng)用場景
?觀察者模式的應(yīng)用場景本身就很豐富族展。訂閱-發(fā)布,通過消息
或者說事件
將組件之間拔鹰,組件和數(shù)據(jù)之間關(guān)聯(lián)起來仪缸,這種應(yīng)用體驗(yàn)非常友好。業(yè)務(wù)邏輯將更加清楚列肢;同時恰画,將少大量的冗余代碼,使開發(fā)者更加關(guān)注和處理業(yè)務(wù)邏輯瓷马。以下拴还,記錄一些實(shí)例,做一些展開說明欧聘。
在 Room 中使用
?Room 是 Google 提供的組件庫之一片林,是對 SQLite 的封裝。它對 LiveData 的支持怀骤,使得操作數(shù)據(jù)庫的數(shù)據(jù)费封,可以直接反應(yīng)到為用戶提供的 UI 展示上。進(jìn)一步說晒喷,它的查詢方法可以返回一個 LiveData 對象孝偎,這個對象的泛型可以是基礎(chǔ)類型的包裝類访敌,例如 Integer 凉敲、Boolean、String、Long 這些包裝類爷抓,也可以是 List势决。
@Query(" SELECT " +
" a.* ," +
" b.transStatus , " +
" b.fileLength , " +
" b.progress , " +
" b.needDecrypted , " +
" b.id as transId, " +
" b.uuid as transUuid, " +
" b.localFilePath as transPath , " +
" MAX(b.date) as transDate " +
" FROM FileShareEntity a " +
" LEFT JOIN FileTransEntity b " +
" ON a.uuid = b.uuid " +
" WHERE a.isRec == 1 AND a.gid=:gid" +
" group by a.uuid order by a.date desc"
)
LiveData<List<FileShareSendItem>> getFileShareSendItems(String gid);
?通過查詢,得到了一個 LiveData 對象蓝撇,然后通過 ViewModel果复,將其和上層 UI 綁定在一起。
public class ShareSendModule extends AndroidViewModel {
...
LiveData<List<FileShareSendItem>> getFileShareSendItems(String gid) {
return shareDao.getFileShareSendItems(gid);
}
...
}
?最后渤昌,在 Fragment 中完成綁定(訂閱):
module.getFileShareSendItems(gid).observe(this, adapter::setData);
?此時虽抄,當(dāng) List 數(shù)據(jù)發(fā)生任何變化,如果 Fragment 處于活動
狀態(tài)独柑,就會被更新迈窟。注意到這里的 setData 方法,將更改 adapter 中的數(shù)據(jù)忌栅,結(jié)合 DiffUtil.Callback
车酣,RecyclerView 的使用將變得非常非常清爽。
在 RecyclerView 中使用
?其實(shí)上面已經(jīng)提到了 Room 和 RecyclerView 的結(jié)合索绪。我們可以做進(jìn)一步的綁定湖员。將 List 中的數(shù)據(jù)和每個 Item 綁定在一起。直接操作數(shù)據(jù)變化瑞驱,不在單獨(dú)處理 UI 展示娘摔。
public AddressAdapter(AppCompatActivity activity) {
addressModel = new AddressModel();
addressModel.getAddresses().observe(activity, addressEntities -> {
if (mItems.size() != 0) {
AddressDiffCallback postDiffCallback = new AddressDiffCallback(mItems, addressEntities);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(postDiffCallback, true);
transformEntities2Beans(addressEntities, mItems);
diffResult.dispatchUpdatesTo(this);
// notifyDataSetChanged();
} else {
transformEntities2Beans(addressEntities, mItems);
notifyDataSetChanged();
}
});
setHasStableIds(true); // this is required for swiping feature.
mItems = new ArrayList<>();
}
@NonNull
@Override
public AddressViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
ActivityAddressItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.activity_address_item, parent, false);
binding.setLifecycleOwner((LifecycleOwner) parent.getContext());
return new AddressViewHolder(binding);
} else {
View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_address_item_add, null);
return new AddressViewHolder(header);
}
}
@Override
public void onBindViewHolder(@NonNull AddressViewHolder holder, int position) {
AddressBean item = mItems.get(position);
holder.bind(item);
}
@Override
public int getItemCount() {
return mItems.size();
}
class AddressViewHolder extends RecyclerView.ViewHolder {
ActivityAddressItemBinding binding;
private boolean isHeader;
AddressViewHolder(View root) {
super(root);
this.root = root;
isHeader = true;
}
AddressViewHolder(ActivityAddressItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
isHeader = false;
}
void bind(AddressBean bean) {
if (isHeader) {
bindHeader();
} else {
bindItem(bean);
}
}
void bindHeader() {
.....
}
void bindItem(AddressBean bean) {
binding.setAddressBean(bean);
......
}
}
一些小技巧
?在使用過程中,還有一些小技巧钱烟,記錄在此晰筛。
和方法的綁定
public class AddressBean extends ViewModel {
...
public void onDelete(View view){
...
}
...
}
// 在 xml 中
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#cfcfcf"
android:text="刪除"
android:textSize="12sp"
android:textColor="@color/white"
android:gravity="center"
android:onClick="@{addressBean::onDelete}"/>
View 可見性綁定
<data>
<variable
name="phoneBean"
type="com.yuegs.AddressPhoneBean" />
<import type="android.view.View" />
</data>
<CheckBox
android:layout_width="17dp"
android:layout_height="17dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="12dp"
android:background="@drawable/checkbox_style"
android:button="@null"
android:checked="@={phoneBean.selected}"
android:visibility="@{phoneBean.checkVisible? View.VISIBLE:View.GONE,default=visible}"/>
總結(jié)
?綁定的基礎(chǔ),是觀察者模式拴袭。只不過读第,這種觀察者模式的細(xì)節(jié)實(shí)現(xiàn),由這類 LiveData 和 ViewModel 幫助我們實(shí)現(xiàn)了拥刻。
參考
LiveData Overview
LiveData beyond the ViewModel?—?Reactive patterns using Transformations and MediatorLiveData
Android Architecture Patterns Part 3:
Model-View-ViewModel
AndroidViewModel vs ViewModel
MediatorLiveData
Advanced Data Binding: Binding to LiveData (One- and Two-Way Binding)
Two-way data binding