前言:
本文主要是對常見設(shè)計(jì)模式的一些分析凝果,以及講述在Android項(xiàng)目中實(shí)現(xiàn)Mvvm模式的兩種方式祝迂。通過Databinding或者ViewModel+LiveData+Repository如何實(shí)現(xiàn)Mvvm的相關(guān)設(shè)計(jì)以及他們各自優(yōu)缺點(diǎn)的一些比較。
作為一名移動開發(fā)者豆村,在項(xiàng)目開發(fā)的過程中液兽,總會遇到一些問題骂删。比如掌动,在現(xiàn)在的項(xiàng)目開發(fā)過程中,就遇到一個(gè)類中或者說一個(gè)模塊的代碼邏輯過多的問題宁玫,尤其是在Activity/Fragment/View中動輒上千行代碼粗恢,各種數(shù)據(jù)請求邏輯(網(wǎng)絡(luò),數(shù)據(jù)庫)欧瘪,數(shù)據(jù)處理邏輯(排序眷射,分類),UI渲染(View層本職工作)全部混雜在其中佛掖。寫的時(shí)候非常瀟灑妖碉,思路很清晰,想到一步寫一步芥被。然而等到再過一段時(shí)間需要修改這些功能的時(shí)候欧宜,發(fā)現(xiàn)自己已經(jīng)無法找回原來自己的思路了。UI與數(shù)據(jù)邏輯耦合嚴(yán)重拴魄,導(dǎo)致后期代碼維護(hù)和功能擴(kuò)展時(shí)會異常艱難冗茸∶希基于上述的考慮和往常經(jīng)驗(yàn)祭犯,沒錯(cuò),設(shè)計(jì)模式可以幫助我們解決這些問題吊圾。那么下面顶捷,我們先回顧下這些設(shè)計(jì)模式以及他們各自的優(yōu)缺點(diǎn)挂绰。
設(shè)計(jì)模式組圖摘自: MVC,MVP 和 MVVM 的圖示
MVC模式:
視圖(View):用戶界面服赎。
控制器(Controller):業(yè)務(wù)邏輯
模型(Model):數(shù)據(jù)獲取模塊
1葵蒂、View 傳送指令到 Controller
2芳室、Controller 完成業(yè)務(wù)邏輯后,要求 Model 改變狀態(tài)
3刹勃、Model 將新的數(shù)據(jù)發(fā)送到 View堪侯,用戶得到反饋
MVP模式:
MVP 模式將 Controller 改名為 Presenter,同時(shí)改變了通信方向荔仁。
1伍宦、 各部分之間的通信,都是雙向的乏梁。
2次洼、View 與 Model 不發(fā)生聯(lián)系,都通過 Presenter 傳遞遇骑。
3卖毁、View 非常薄,不部署任何業(yè)務(wù)邏輯落萎,稱為"被動視圖"(Passive View)亥啦,即沒有任何主動性,而 Presenter非常厚练链,所有邏輯都部署在那里翔脱。
基于上述MVC模式的優(yōu)點(diǎn),在實(shí)際開發(fā)中媒鼓,MVP模式被更多的應(yīng)用在項(xiàng)目開發(fā)過程中了届吁。
MVVM模式:
MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致绿鸣。
唯一的區(qū)別是疚沐,它采用雙向綁定(data-binding):View的變動,自動反映在 ViewModel潮模,反之亦然亮蛔。
各模式在Android項(xiàng)目中的應(yīng)用:
MVC模式:
View:對應(yīng)于xml布局文件
Model:數(shù)據(jù)獲取模塊
Controllor:對應(yīng)于Activity業(yè)務(wù)邏輯,數(shù)據(jù)處理和UI處理
在Android開發(fā)過程中再登,乍一看Activity還是比較符合MVC的設(shè)計(jì)模式的尔邓。Xml文件對應(yīng)View層,負(fù)責(zé)UI的呈現(xiàn)锉矢,數(shù)據(jù)獲取模塊對應(yīng)model層梯嗽,而Activity作為Controller,負(fù)責(zé)處理數(shù)據(jù)邏輯之后沽损,將數(shù)據(jù)交給UI層去展示灯节。問題在于在此模式下,Xml作為View層的職責(zé)實(shí)在太弱,比如涉及到背景切換炎疆,文字大小顏色變化等等卡骂,都不能直接在xml中進(jìn)行設(shè)置和修改,還是需要Activity中進(jìn)行相應(yīng)的切換形入。這樣就導(dǎo)致全跨,Activity作為View層+Controller層,其功能涵蓋UI渲染和數(shù)據(jù)邏輯獲取及處理亿遂,導(dǎo)致邏輯和UI 耦合嚴(yán)重浓若,甚至經(jīng)常出現(xiàn)上千行甚至幾千行代碼的情況,不利于代碼的維護(hù)和后續(xù)擴(kuò)展蛇数。
MVP模式:
View:對應(yīng)于Activity/Fragment/自定義View挪钓,主要負(fù)責(zé)UI渲染。
Model:數(shù)據(jù)獲取模塊
Presenter: 負(fù)責(zé)數(shù)據(jù)處理以及View和Model的交互等耳舅,持有Model和View的引用碌上。
在MVP模式中,View層只負(fù)責(zé)UI渲染浦徊,不再需要處理對應(yīng)的業(yè)務(wù)邏輯馏予,View層的量級大大輕化。而Presenter在獲取到Model的數(shù)據(jù)辑畦,并進(jìn)行處理后吗蚌,通過View層暴露的接口調(diào)用去更新UI腿倚,這樣View層和
Model層不互相持有引用纯出,保證是隔離和解耦的。這樣整個(gè)業(yè)務(wù)邏輯處理和視圖的渲染是隔離的敷燎,就不會再出現(xiàn)上文提到的Activity/Fragment/View臃腫和混亂的問題暂筝。
但是MVP模式也會存在一系列的缺點(diǎn):
1、Presenter層要處理的業(yè)務(wù)邏輯過多硬贯,復(fù)雜的業(yè)務(wù)邏輯會使P層非常龐大和臃腫焕襟。
2、Presenter通過接口方式持有View層引用饭豹,接口及接口中聲明的方法粒度無法把握鸵赖,可能需要聲明大量接口以及接口中需要聲明太多方法,而其中有些方法是否會用到以及是否會增加或刪減還需要后續(xù)進(jìn)一步確認(rèn)拄衰。
3它褪、Activity中需要聲明大量跟UI相關(guān)的方法,而相應(yīng)的事件通過Presenter調(diào)用相關(guān)方法來實(shí)現(xiàn)翘悉。兩者互相引用和調(diào)用茫打,存在耦合。一旦View層的UI視圖發(fā)生改變,接口中的方法就需要改變老赤,View層和P層同時(shí)都需要修改轮洋。
MVVM模式:
View:對應(yīng)于Activity/Fragment/自定義View,主要負(fù)責(zé)UI渲染抬旺。
Model:數(shù)據(jù)獲取模塊
ViewModel: 負(fù)責(zé)業(yè)務(wù)邏輯處理弊予,負(fù)責(zé)View和Model的交互。和View層雙向綁定开财。
Mvvm模式是通過將View層和ViewModel層進(jìn)行雙向綁定块促, View層的變化會自動通知給ViewModel層,而ViewModel層的數(shù)據(jù)變化也會通知給View層進(jìn)行相應(yīng)的UI的更新床未。這樣竭翠,Model層只負(fù)責(zé)暴露獲取數(shù)據(jù)的方法,View層只負(fù)責(zé)監(jiān)聽數(shù)據(jù)的變化更新薇搁,而ViewModel負(fù)責(zé)接收View層的事件指令以及獲取并處理數(shù)據(jù)斋扰。從而實(shí)現(xiàn)業(yè)務(wù)邏輯和Ui的隔離。
使用MVVM模式的優(yōu)點(diǎn):
1啃洋、低耦合度:
在MVVM模式中传货,數(shù)據(jù)處理邏輯是獨(dú)立于UI層的。ViewModel只負(fù)責(zé)提供數(shù)據(jù)和處理數(shù)據(jù)宏娄,不會持有View層的引用问裕。而View層只負(fù)責(zé)對數(shù)據(jù)變化的監(jiān)聽,不會處理任何跟數(shù)據(jù)相關(guān)的邏輯孵坚。在View層的UI發(fā)生變化時(shí)粮宛,也不需要像MVP模式那樣,修改對應(yīng)接口和方法實(shí)現(xiàn)卖宠,一般情況下ViewModel不需要做太多的改動巍杈。
2、數(shù)據(jù)驅(qū)動:
MVVM模式的另外一個(gè)特點(diǎn)就是數(shù)據(jù)驅(qū)動扛伍。UI的展現(xiàn)是依賴于數(shù)據(jù)的筷畦,數(shù)據(jù)的變化會自然的引發(fā)UI的變化,而UI的改變也會使數(shù)據(jù)Model進(jìn)行對應(yīng)的更新刺洒。ViewModel只需要處理數(shù)據(jù)鳖宾,而View層只需要監(jiān)聽并使用數(shù)據(jù)進(jìn)行UI更新。
3逆航、異步線程更新Model:
Model數(shù)據(jù)可以在異步線程中發(fā)生變化鼎文,此時(shí)調(diào)用者不需要做額外的處理,數(shù)據(jù)綁定框架會將異步線程中數(shù)據(jù)的變化通知到UI線程中交給View去更新纸泡。
4漂问、方便協(xié)作:
View層和邏輯層幾乎沒有耦合赖瞒,在團(tuán)隊(duì)協(xié)作的過程中,可以一個(gè)人負(fù)責(zé)Ui 一個(gè)人負(fù)責(zé)數(shù)據(jù)處理蚤假。并行開發(fā)栏饮,保證開發(fā)進(jìn)度。
5磷仰、易于單元測試:
MVVM模式比較易于進(jìn)行單元測試袍嬉。ViewModel層只負(fù)責(zé)處理數(shù)據(jù),在進(jìn)行單元測試時(shí)灶平,測試不需要構(gòu)造一個(gè)fragment/Activity/TextView等等來進(jìn)行數(shù)據(jù)層的測試伺通。同理View層也一樣,只需要輸入指定格式的數(shù)據(jù)即可進(jìn)行測試逢享,而且兩者相互獨(dú)立罐监,不會互相影響。
6瞒爬、數(shù)據(jù)復(fù)用:
ViewModel層對數(shù)據(jù)的獲取和處理邏輯弓柱,尤其是使用Repository模式時(shí),獲取數(shù)據(jù)的邏輯完全是可以復(fù)用的侧但。開發(fā)者可以在不同的模塊矢空,多次方便的獲取同一份來源的數(shù)據(jù)。同樣的一份數(shù)據(jù)禀横,在版本功能迭代時(shí)屁药,邏輯層不需要改變,只需要改變View層即可柏锄。
Databinding的使用:
(對databinding已經(jīng)比較熟悉的同學(xué)可以直接忽略本章)
Google之前推出Android Mvvm模式的框架酿箭,Databinding。先來看下Databinding是如何實(shí)現(xiàn)Mvvm模式的數(shù)據(jù)綁定框架的绢彤。
build.gradle中添加:
android {
....
dataBinding {
enabled = true
}
}
先來看下布局文件:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
其中需要注意的是七问,Databinding的布局文件中是以layout為根節(jié)點(diǎn)的,然后通過
<data>
<variable name="user" type="com.example.User"/>
</data>
這種方式來聲明類中需要使用的數(shù)據(jù)Model茫舶。
在layout中綁定數(shù)據(jù)時(shí),
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
綁定的數(shù)據(jù)實(shí)體
使用@{}語句來將User的firstName屬性和TextView進(jìn)行綁定刹淌。
其中進(jìn)行數(shù)據(jù)綁定的數(shù)據(jù)Model的屬性是不可變的饶氏。
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
在databinding時(shí),上述兩種寫法是等價(jià)的有勾。android:text="@{user.firstName}" 通過這種方式疹启,android:text就會和User的firstName屬性以及getFirstName()方法綁定。當(dāng)然如果存在firstName()方法也是可以的蔼卡。
數(shù)據(jù)綁定
默認(rèn)情況下喊崖,databinding會根據(jù)layout文件的名字生成一個(gè)binding class。比如如果布局文件是main_activity.xml,就會生成一個(gè)MainActivityBinding的class荤懂。MainActivityBinding知道布局文件中所有的View屬性以及他們的綁定關(guān)系(比如上文中的User)茁裙,還有如何通過binding表達(dá)式對他們進(jìn)行賦值。下面有個(gè)簡單的示例:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
通過上面的方式就完成了User和View的綁定节仿。很明顯晤锥,你也可以通過下述方式獲取整個(gè)layout的View。
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你是在ListView 或者RecycleView的Adapter中bind Item廊宪,你可以通過如下方式獲确:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件綁定
Databinding支持你寫表達(dá)式的方式來處理從View分發(fā)出的事件(比如android:onClick)。事件的屬性名字是有Listener的方法名決定的箭启,需要保持一致壕翩。比如View.OnLongClickListener就有一個(gè)onLongClick()的方法,那么這個(gè)事件的屬性就是android:onLongClick傅寡。
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onLongClickFriend(View view){...}
}
Binding表達(dá)式可以為View添加一個(gè)ClickListener:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onLongClick="@{handlers::onLongClickFriend}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
需要注意的是表達(dá)式中的方法簽名需要和Listener中的方法簽名保持一直戈泼,否則是會報(bào)錯(cuò)的。
事件綁定是有兩種方式的赏僧,Method References和Listener Bindings 大猛。他們之間最大的差別就是,Method References的Listener實(shí)現(xiàn)創(chuàng)建是在數(shù)據(jù)被綁定時(shí)就完成的淀零,而Listener Bindings 則是在相應(yīng)事件觸發(fā)的時(shí)候才會執(zhí)行綁定表達(dá)式的操作挽绩。這里不再做過多介紹,感興趣的可以直接去官方文檔查看驾中。
相應(yīng)類的import
databinding支持像Java一樣唉堪,直接引用其他的類。
<data>
<import type="com.example.MyStringUtils"/>
<import type="android.view.View"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
/>
Databinding支持表達(dá)式:
可以在layout中 像Java一樣使用如下的一些表達(dá)式:
數(shù)學(xué)表達(dá)式 + – / * %
字符串鏈接 +
邏輯操作符 && ||
二元操作符 & | ^
一元操作符 + – ! ~
Shift >> >>> <<
比較 == > < >= <=
instanceof
Grouping ()
Literals – character, String, numeric, null
Cast
函數(shù)調(diào)用
值域引用(Field access)
通過[]訪問數(shù)組里面的對象
三元操作符 ?:
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
Data Objects
POJO(plain old Java object)可以被用于databinding肩民。但只是改變一個(gè)POJO并不會使UI發(fā)生變化唠亚。databinding是通過在數(shù)據(jù)變化時(shí)給出相應(yīng)的通知來告知Ui層進(jìn)行更新的。共有三種數(shù)據(jù)變化的機(jī)制持痰,Observable objects, observable fields, and observable collections.
當(dāng)他們中的任何一個(gè)與UI進(jìn)行綁定灶搜,并且其數(shù)據(jù)屬性發(fā)生變化時(shí),都會通知到UI進(jìn)行更新工窍。
Observable Objects:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
注意割卖,此時(shí)User需要繼承 BaseObservable ,相應(yīng)屬性獲取方法需要添加注解@Bindable患雏,而且在setValue時(shí)需要調(diào)用notifyPropertyChanged(BR.propertyName)鹏溯。
ObservableFields:
如果你的Entity不想繼承BaseObservable 以及做上述的那些操作,你也可以使用ObservableFields淹仑。
android.databinding.ObservableField
android.databinding.ObservableBoolean,
android.databinding.ObservableByte,
android.databinding.ObservableChar,
android.databinding.ObservableShort,
android.databinding.ObservableInt,
android.databinding.ObservableLong,
android.databinding.ObservableFloat,
android.databinding.ObservableDouble,
android.databinding.ObservableParcelable. ObservableFields
等等丙挽。使用時(shí)肺孵,在Data Class中創(chuàng)建一個(gè)Public final 的屬性即可。
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
通過下述方式就可以設(shè)置并得到Data 屬性的值颜阐。
user.firstName.set("Google");
int age = user.age.get();
使用set方法時(shí)平窘,會實(shí)時(shí)通知到UI進(jìn)行相應(yīng)更新。
Observable Collections:
當(dāng)key是引用類型瞬浓,比如String時(shí)初婆, android.databinding.ObservableArrayMap 是比較有用的。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout中猿棉,map可以通過String類型的Key獲取到對應(yīng)的Value
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
key是整數(shù)時(shí)磅叛,可以使用android.databinding.ObservableArrayMap
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
自定義綁定
相信這時(shí)候大家就會有這個(gè)疑問了,那如果我給一些View添加了自定義的方法或者屬性(官方不支持的方法)萨赁,該如何進(jìn)行綁定呢弊琴?是的,databinding當(dāng)然也是支持自定義進(jìn)行綁定杖爽。
我們先找一個(gè)官方支持的屬性看時(shí)如何實(shí)現(xiàn)的敲董,比如android:paddingLeft,是可以單獨(dú)進(jìn)行設(shè)置的慰安,但是View的方法中腋寨,是沒有setPaddingLeft的方法的,只有setPadding(left, top, right, bottom)方法化焕。那么此時(shí)單獨(dú)綁定paddingLeft就如下:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding adapters在其他類型的定制上是很有用的萄窜。當(dāng)存在沖突時(shí),開發(fā)者自己創(chuàng)建的binding adapter會覆蓋默認(rèn)的adpter撒桨。你也可以創(chuàng)建接收多個(gè)參數(shù)的adapter查刻。
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
這個(gè)adapter只會在ImageView存在String類型的url值和Drawable類型的error值傳入時(shí)才會觸發(fā)執(zhí)行。
當(dāng)一個(gè)Listener有兩個(gè)方法時(shí)凤类,我們需要將他們拆成兩個(gè)接口穗泵。比如 View.OnAttachStateChangeListener有兩個(gè)方法,onViewAttachedToWindow() 和 onViewDetachedFromWindow() 我們需要需要為其創(chuàng)建兩個(gè)不同的接口和Handler 方法谜疤。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因?yàn)楦淖円粋€(gè)Listener就會影響到另一個(gè)佃延,所以我們需要建三個(gè)Binding Adapters,以便他們的方法可以被單獨(dú)設(shè)置,也可以一起同時(shí)被設(shè)置茎截。
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
Databinding的缺點(diǎn):
綜上我們介紹了Mvvm模式的優(yōu)點(diǎn)苇侵,databinding是實(shí)現(xiàn)Mvvm模式的一種數(shù)據(jù)綁定框架,自然擁有上述優(yōu)點(diǎn)企锌,那么databinding有哪些缺點(diǎn)呢?
1于未、databinding的View層UI顯示是在xml文件中實(shí)現(xiàn)的撕攒,當(dāng)出現(xiàn)問題時(shí)陡鹃,無法確定是Model的問題還是xml的問題,而且xml中也不方便調(diào)試抖坪。
2萍鲸、基于數(shù)據(jù)驅(qū)動的databinding ,在比較大的模塊時(shí),Model層數(shù)據(jù)可能會比較大擦俐。而且在釋放的時(shí)候不能像其他模式一樣脊阴,不同View的數(shù)據(jù)無法根據(jù)各自View的生命周期來進(jìn)行釋放,而是在整個(gè)頁面銷毀時(shí)統(tǒng)一釋放蚯瞧,這樣就會占用更大內(nèi)存嘿期。
3、View的復(fù)用埋合。我們在項(xiàng)目中經(jīng)常用到View的復(fù)用备徐。而在databinding中 View在布局文件中可以綁定不同的model,在復(fù)用時(shí)除了要考慮View的復(fù)用之外甚颂,還需要考慮model的問題蜜猾。
4、當(dāng)然還有一個(gè)缺點(diǎn)振诬。對于做前端開發(fā)的開發(fā)者來說蹭睡,在布局文件中寫對應(yīng)的綁定數(shù)據(jù)和事件的邏輯是很正常的操作,但是對于移動開發(fā)者而言赶么,這會是一個(gè)全新的體驗(yàn)肩豁,也許有人就無法接受這種情況。
ViewModel+LiveData+Repository實(shí)現(xiàn)MVVM的設(shè)計(jì)模式:
Google于I/O大會上提出了Android Architecture Components禽绪,里面提到的架構(gòu)化組件可以更好的幫助我們?nèi)プ鲆恍┛蚣艿墓ぷ鞅途取6鳹iewModel的概念和 LiveData的可被觀察性則讓我看到了在Activity/Fragment/View(而不是xml文件)中實(shí)現(xiàn)雙向綁定的可能性。
需要了解Android架構(gòu)化組件的同學(xué)印屁,可以查看:
Android Architecture Components
Android Architecture Components官方文檔
那么使用ViewModel+LiveData+Repository 如何構(gòu)建一個(gè)MVVM模式的項(xiàng)目呢循捺?
下圖為各模塊關(guān)系圖:
View層:
View層由Activity/Fragment/自定義View組成。在View層中雄人,View不做具體的數(shù)據(jù)和邏輯處理操作从橘,View層只會將數(shù)據(jù)和事件操作和ViewModel進(jìn)行綁定。View 監(jiān)聽數(shù)據(jù)的變化础钠,當(dāng)ViewModel中的LiveData數(shù)據(jù)發(fā)生變化時(shí)恰力,進(jìn)行Ui的更新操作。同時(shí)旗吁,View層將事件綁定以命令的方式交給ViewModel去處理踩萎。總之很钓,View層只做UI相關(guān)操作香府,邏輯處理數(shù)據(jù)獲取等交給ViewModel董栽。
ViewModel:
ViewModel是邏輯處理類,負(fù)責(zé)數(shù)據(jù)處理和View層與Model層的交互企孩。ViewModel通過數(shù)據(jù)倉庫Repository獲取數(shù)據(jù)來源锭碳,處理來自View的事件命令,同時(shí)更新數(shù)據(jù)勿璃。
Repository:
數(shù)據(jù)倉庫: 一個(gè)數(shù)據(jù)倉庫負(fù)責(zé)獲取同類型的數(shù)據(jù)來源擒抛。比如圖書數(shù)據(jù)倉庫能夠獲取各種條件篩選的圖書數(shù)據(jù),這份數(shù)據(jù)可以來自網(wǎng)絡(luò)(Retrofit + OKHttp)补疑,本地Database(Room)歧沪,緩存 (HashMap)等等,ViewModel在從Repository獲取數(shù)據(jù)時(shí)癣丧,不需關(guān)注數(shù)據(jù)具體是怎么來的槽畔。
Model :
因?yàn)橛辛薘epository的概念,所以這里的Model的定義相對簡單胁编,就是JavaBean厢钧,即網(wǎng)絡(luò)請求對應(yīng)的實(shí)體數(shù)據(jù)。
ViewModel的組成:
1嬉橙、Context:
ViewModel因?yàn)樾枰ㄟ^Repository來異步獲取相關(guān)數(shù)據(jù)早直,持有Context可以在異步請求結(jié)束時(shí),判斷Context是否已經(jīng)銷毀來決定是否取消回調(diào)市框。而ViewModel需要處理邏輯霞扬,一些工具類本身也需要Context。
2枫振、Data Field 數(shù)據(jù)綁定(Observable Model):
在Databinding中喻圃,ViewModel會持有ObservableField等的數(shù)據(jù),View監(jiān)聽ObservableField的數(shù)據(jù)變化粪滤,而當(dāng)ViewModel中ObservableField數(shù)據(jù)變化時(shí)即會通知UI進(jìn)行相應(yīng)更新斧拍。在不使用databinding時(shí),就可以通過LiveData起到相同的作用杖小。
3肆汹、事件綁定:
View層的一些事件,比如onClick事件予权,OnRefresh事件等等昂勉,在事件觸發(fā)時(shí),通過ReplyCommand的方式扫腺,將事件傳遞給ViewModel層進(jìn)行對應(yīng)的邏輯操作岗照。
事件綁定操作通過RxJava來實(shí)現(xiàn)。相對于使用Listener來說,使用RxJava更便捷谴返,而且不會出現(xiàn)Listener回調(diào)方法中可能會返回View的引用煞肾。這樣的話咧织,就無法避免在ViewsModel中使用View的引用直接操作UI的問題嗓袱。
4、數(shù)據(jù)倉庫:
數(shù)據(jù)倉庫負(fù)責(zé)獲取數(shù)據(jù)來源习绢。比如網(wǎng)絡(luò)請求渠抹,數(shù)據(jù)庫請求,本地文件等的數(shù)據(jù)的獲取通過Repository來得到闪萄。
5梧却、Model :
這里的model即為所需數(shù)據(jù)對應(yīng)實(shí)體,不包含獲取數(shù)據(jù)的接口方法和實(shí)現(xiàn)等败去。獲取數(shù)據(jù)交給Repository放航,處理數(shù)據(jù)交給ViewModel。
6圆裕、childViewModel:
在一些情況下广鳍,ViewModel會持有childViewModel。比如Activity中嵌套Fragment吓妆,Activty對應(yīng)ViewModel赊时,而Fragment也有一個(gè)ViewModel。那么Activity的ViewModel是可以持有FragmentViewModelde 引用的行拢。
如下為一個(gè)ViewModel的部分代碼實(shí)現(xiàn):
ViewModel中主要通過Repository來獲取數(shù)據(jù)祖秒,并根據(jù)View層傳遞過來的事件指令操作數(shù)據(jù),數(shù)據(jù)發(fā)生改變后舟奠,View層監(jiān)聽到變化并更新UI竭缝。通過這種方式,完成雙向綁定和MVVM的設(shè)計(jì)流程沼瘫。
關(guān)于ViewModel和Repository管理的相關(guān)設(shè)計(jì)類圖如下:
ViewModelManger :
主要是對ViewModel的生命周期進(jìn)行管理抬纸,一個(gè)CustomContext對應(yīng)一個(gè)BaseViewModel,而一個(gè)CustomContext同時(shí)對應(yīng)一個(gè)Activity/Fragment/View的生命周期晕鹊。當(dāng)View層生命周期結(jié)束時(shí)松却,就將Map中指定Key的BaseViewModel移除,并調(diào)用其onDestroy方法來完成相應(yīng)數(shù)據(jù)銷毀工作溅话。
BaseRepository:數(shù)據(jù)倉庫實(shí)現(xiàn)類晓锻。主要用于獲取數(shù)據(jù),包括但不限于網(wǎng)絡(luò)數(shù)據(jù)飞几,數(shù)據(jù)庫砚哆,本地文件等等的數(shù)據(jù)來源。
BaseViewModel : 每個(gè)Activity/Fragment/View 對應(yīng)一個(gè)BaseViewModel屑墨。從BaseRepository獲取數(shù)據(jù)并根據(jù)View層Command事件處理數(shù)據(jù)并改變數(shù)據(jù)的值躁锁。用于View層和 Repository Model的交互以及數(shù)據(jù)邏輯的處理纷铣。
和Databinding相比,其優(yōu)點(diǎn)在于:
1战转、Mvvm模式的優(yōu)點(diǎn)其都具備搜立。
2、Databinding不便于調(diào)試槐秧,而ViewModel+LiveData因?yàn)槠潆p向綁定的操作都在Activity/Fragment/View中進(jìn)行啄踊,不會出現(xiàn)像在xml中不便調(diào)試的問題。
3刁标、會對ViewModel進(jìn)行統(tǒng)一管理颠通,在頁面銷毀時(shí),銷毀對應(yīng)的ViewModel以避免造成內(nèi)存占用膀懈。
4顿锰、還有一點(diǎn),對于移動開發(fā)者而言启搂,可能更易接受這種Mvvm模式的綁定方式硼控,而不是在布局文件中直接進(jìn)
行數(shù)據(jù)和事件的綁定操作。
本文通過對比幾種不同的設(shè)計(jì)模式狐血,并詳細(xì)介紹了MVVM模式的兩種實(shí)現(xiàn)方式淀歇,供大家了解和參考。不同的設(shè)計(jì)模式會有各自的優(yōu)缺點(diǎn)匈织,不同的項(xiàng)目應(yīng)該按照各自的實(shí)際情況和使用體驗(yàn)來進(jìn)行對應(yīng)的設(shè)計(jì)浪默。在選擇的過程中需要考慮諸多問題,比如性能問題缀匕,使用便捷程度纳决,單元測試,是否相互獨(dú)立等乡小。當(dāng)然還有一點(diǎn)很重要的就是阔加,共同開發(fā)的同事是否認(rèn)同并喜歡這種開發(fā)方式,畢竟這會影響到他們的開發(fā)流程满钟,習(xí)慣和體驗(yàn)胜榔。如何說服他們認(rèn)可這種設(shè)計(jì)模式,就需要你提供便捷和健壯的封裝框架湃番,使他們很爽很開心很便捷地使用這種設(shè)計(jì)模式夭织,并且能夠滿足項(xiàng)目代碼的可擴(kuò)展性,解耦和相關(guān)獨(dú)立吠撮。做到這些尊惰,就能滿足調(diào)用者,項(xiàng)目代碼健壯性,模塊獨(dú)立性和可擴(kuò)展性的需要了弄屡。
1题禀、Databinding
2、MVC膀捷,MVP 和 MVVM 的圖示
3迈嘹、如何構(gòu)建Android MVVM應(yīng)用程序