Model-View-Presenter(MVP),即模型-視圖-表示層,架構(gòu)被廣泛應(yīng)用于 Android 應(yīng)用程序,通過引入表示層將視圖與表示邏輯和模型分離。Model-View-ViewModel(MVVM),即模型-視圖-視圖模型,與 MVP 非常相似,視圖模型充當(dāng)增強(qiáng)的表示層,使用數(shù)據(jù)綁定器保持視圖模型和視圖同步。通過將視圖綁定到視圖模型屬性上,數(shù)據(jù)綁定程序可以處理視圖更新而無需手動更改數(shù)據(jù)來設(shè)置視圖(例如,不用再設(shè)置控件 TextView 的setTest() 或者 setVisibility() 屬性)。與 MVP 中的表示層一樣,視圖模型可以很容易地進(jìn)行單元測試。本文介紹了數(shù)據(jù)綁定庫和 MVVM 架構(gòu)模式,以及它們在 Android 上協(xié)同工作方式。
數(shù)據(jù)綁定
什么是數(shù)據(jù)綁定?
數(shù)據(jù)綁定是一種把數(shù)據(jù)綁定到用戶界面元素(控件)的通用機(jī)制。通常,數(shù)據(jù)綁定會將數(shù)據(jù)從本地存儲或者網(wǎng)絡(luò)綁定到顯示層,其特征是數(shù)據(jù)的改變會自動在數(shù)據(jù)源和用戶界面之間同步。
數(shù)據(jù)綁定庫的好處
TextView textView = (TextView) findViewById(R.id.label);
EditText editText = (EditText) findViewById(R.id.userinput);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress);
editText.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void afterTextChanged(Editable s) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
model.setText(s.toString());
}
});
textView.setText(model.getLabel());
progressBar.setVisibility(View.GONE);
如上述代碼所示,大量的 findViewById() 調(diào)用之后,又是一大堆 setter/listener 之類的調(diào)用。 即使使用 ButterKnife 注入庫也沒有使情況改善。而數(shù)據(jù)綁定庫就能很好地解決這個(gè)問題。
在編譯時(shí)創(chuàng)建一個(gè)綁定類,它為所有視圖提供一個(gè) ID 字段,因此不再需要調(diào)用 findViewById() 方法。實(shí)際上,這種方式比調(diào)用 findViewById() 方法快數(shù)倍,因?yàn)閿?shù)據(jù)綁定庫創(chuàng)建代碼僅需要遍歷視圖結(jié)構(gòu)一次。
綁定類中也實(shí)現(xiàn)了視圖文件的綁定邏輯,因此所有 setter 會在綁定類中被調(diào)用,你無須為之操心??傊茏屇愕拇a變得更簡潔。
如何設(shè)置數(shù)據(jù)綁定?
android {
compileSdkVersion 25
buildToolsVersion "25.0.1"
...
dataBinding {
enabled = true
}
...
}
首先在 app 的 build.gradle 中添加 dataBinding { enabled = true }。之后構(gòu)建系統(tǒng)會收到提示對數(shù)據(jù)綁定啟用附加處理,如,從布局文件創(chuàng)建綁定類。
...
接下來,在
如何綁定數(shù)據(jù)?
android:padding="@{vm.bigPadding ? @dimen/paddingBig : @dimen/paddingNormal}"
android:text='@{vm.text ?? @string/defaultText + "Additional text."}' />
視圖屬性上的數(shù)據(jù)綁定指令以@開頭,以大括號結(jié)束。你可以使用任何變量在數(shù)據(jù)段中導(dǎo)入你之前聲明的變量。這些表達(dá)式基本支持你在代碼中的所有操作,例如算術(shù)運(yùn)算符或字符串連接。
Visibility 屬性中還支持 if-then-else 三元運(yùn)算符。還提供了合并運(yùn)算符 ??,如果左邊的值為空,則返回右操作數(shù)。在上述代碼中,你可以像在正常布局中一樣訪問資源,因此你可以根據(jù)布爾變量的取值選擇不同的 dimension 資源,也可以使用 padding 屬性查看這些資源。
即使你在代碼中使用 getters 和 setters,你所聲明的變量的屬性也可以用字段訪問語法的形式訪問。你可以在 slide 上的文本屬性中看到此部分,其中 vm.text 調(diào)用視圖模型的 getText() 方法。最后,一些小的限制也適用,例如,不能創(chuàng)建新對象,但是數(shù)據(jù)綁定庫仍然非常強(qiáng)大。
哪些屬性是可以綁定的?
android:text="@{vm.text}"
android:visibility="@{vm.visibility}"
android:paddingLeft="@{vm.padding}"
android:layout_marginBottom="@{vm.margin}"
app:adapter="@{vm.adapter}"
實(shí)際上,標(biāo)準(zhǔn)視圖的大多數(shù)屬性已經(jīng)被數(shù)據(jù)綁定庫支持。在數(shù)據(jù)綁定庫內(nèi)部,當(dāng)你使用數(shù)據(jù)綁定時(shí),庫按照視圖類型查找屬性名稱的 setter。例如,當(dāng)你把數(shù)據(jù)綁定到 text 屬性時(shí),綁定庫會在視圖類中使用合適的參數(shù)類型查找 setText() 方法,上述示例是 String。
當(dāng)沒有對應(yīng)的布局屬性時(shí),你也可以使用數(shù)據(jù)綁定的 setter。例如,你可以在 xml 布局中的 recycleler 視圖上使用 app:adapter 屬性,以利用數(shù)據(jù)綁定設(shè)置適配器參數(shù)。
對于標(biāo)準(zhǔn)屬性,不是所有的都在 View 上有對應(yīng)的 setter 方法。例如,paddingLeft 情況下,數(shù)據(jù)綁定庫支持自定義的 setter,以便將綁定轉(zhuǎn)移到 padding 屬性上。但是,遇到 layout_marginBottom 的情況,當(dāng)綁定庫沒有提供自定義 setter 時(shí)我們要怎么處理呢?
自定義 Setter
@BindingAdapter("android:layout_marginBottom")
public static void setLayoutMarginBottom(View v, int bottomMargin) {
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) v.getLayoutParams();
if (layoutParams != null) {
layoutParams.bottomMargin = bottomMargin;
}
}
對于上述情況,自定義 setter 可以被重寫。Setter 是使用 @BindingAdapter 注解來實(shí)現(xiàn)的,布局屬性使用參數(shù)命名,使得綁定適配器被調(diào)用。上面示例提供了一個(gè)用于綁定 layout_marginBottom 的適配器。
方法必須是 public static void ,而且必須接受綁定適配器調(diào)用的首個(gè)視圖類型作為參數(shù),然后將數(shù)據(jù)強(qiáng)綁定到你需要的類型。在這個(gè)例子中,我們使用一個(gè) int 類型為類型 View(子類型)定義一個(gè)綁定適配器。最后,實(shí)現(xiàn)綁定適配器接口。對于 layout_marginBottom,我們需要獲取布局參數(shù),并且設(shè)置底部間隔:
@BindingAdapter({"imageUrl", "placeholder"})
public static void setImageFromUrl(ImageView v, String url, int drawableId) {
Picasso.with(v.getContext().getApplicationContext())
.load(url)
.placeholder(drawableId)
.into(v);
}
也可能需要設(shè)置多種屬性以綁定適配器調(diào)用。為了達(dá)到此目的,MMVM 會提供你的屬性名稱列表并用于 @BindingAdapter 實(shí)現(xiàn)注解。另外,在現(xiàn)有方法中,每個(gè)屬性都有自己的名稱。只有在所有聲明的屬性被設(shè)置后,這些 BindingAdapter 才會被調(diào)用。
在加載圖片過程中,我想為加載圖片定義一個(gè)綁定適配器來綁定 URL 與 placeHolder。如你所見,通過使用 Picasso image loading library,綁定適配器非常容易實(shí)現(xiàn)。你可以在自定義綁定適配器中使用任何你想要的方法。
在代碼中使用綁定
MyBinding binding;
// For Activity
binding = DataBindingUtil.setContentView(this, R.layout.layout);
// For Fragment
binding = DataBindingUtil.inflate(inflater, R.layout.layout, container, false);
// For ViewHolder
binding = DataBindingUtil.bind(view);
// Access the View with ID text_view
binding.textView.setText(R.string.sometext);
// Setting declared variables
binding.set(variable);
現(xiàn)在我們在 xml 文件中定義了綁定,并且編寫了自定義 setter,那我們?nèi)绾卧诖a中使用綁定呢? 數(shù)據(jù)綁定庫通過生成綁定類為我們完成所有的工作。要獲取布局的相應(yīng)綁定類的實(shí)例,就要用到庫提供的輔助方法。Activity 對應(yīng)使用 DataBindingUtil.setContentView(),fragment 對應(yīng)使用 inflate(),視圖擁有者請使用 bind()。 如前所述,綁定類為定義 final 字段的 ID 提供了所有視圖。同樣,您可以在綁定對象的布局文件中設(shè)置你所聲明的變量。
自動更新布局
如果使用數(shù)據(jù)綁定,在數(shù)據(jù)發(fā)生變化時(shí),庫代碼可以控制布局自動更新。然而,庫仍然需要獲得關(guān)于數(shù)據(jù)變化的通知。如果綁定的變量實(shí)現(xiàn)了 Observable 接口(不要跟 RxJava 的 Observable混淆了)就能解決這個(gè)問題。
對于像 int 和 boolean 這樣的簡單數(shù)據(jù)類型,庫已經(jīng)提供了合適的實(shí)現(xiàn) Observable 的類型,比如 ObservableBoolean。還有一個(gè) ObservableField 類型用于其它對象,比如字符串。
public class MyViewModel extends BaseObservable {
private Model model = new Model();
public void setModel(Model model) {
this.model = model;
notifyChange();
}
public void setAmount(int amount) {
model.setAmount(amount);
notifyPropertyChanged(BR.amount);
}
@Bindable public String getText() { return model.getText(); }
@Bindable public String getAmount() { return Integer.toString(model.getAmount()); }
}
在更復(fù)雜的情況下,比如視圖模型,有一個(gè) BaseObservable 類提供了工具方法在變化時(shí)通知布局。就像上面在 setModel() 方法中看到那樣,我們可以在模型變化之后通過調(diào)用 notifyChange() 來更新整個(gè)布局。
再看看 setAmount(),你會看到模型中只有一個(gè)屬性發(fā)生了變化。這種情況下,我們不希望更新整個(gè)布局,只更新用到了這個(gè)屬性的部分。為達(dá)此目的,可以在屬性對應(yīng)的 getter 上添加 @Bindable 注解。然后 BR 類中會產(chǎn)生一個(gè)字段,用于傳遞給 notifyPropertyChanged() 方法。這樣,綁定庫可以只更新確實(shí)依賴變化屬性的部分布局。
匯總
? 在布局文件中申明變量并將之與視圖中的屬性綁定。
? 在代碼中創(chuàng)建綁定來設(shè)置變量。
? 確保你的變量類型實(shí)現(xiàn)了 Observable 接口 —— 可以從 BaseObservable 繼承 —— 這樣數(shù)據(jù)變化時(shí)會自動反映到布局上。
現(xiàn)在來看看 MVVM 架構(gòu),以及它的三個(gè)組成部分是如何一起工作的。
視圖是用戶界面,即布局。在 Android 中通常是指 Activity、Fragment 或者 ViewHolder 以及配合它們使用的 XML 布局文件。
模型就是業(yè)務(wù)邏輯層,提供方法與數(shù)據(jù)進(jìn)行互動。
視圖模型就像是視圖和模型的中間人,它既能訪問模型的數(shù)據(jù),又包含 UI 狀態(tài)。它也定義了一些命令可以被事件,比如單擊事件調(diào)用。視圖模型包含了應(yīng)用中的呈現(xiàn)邏輯。
在 MVVM 架構(gòu)模式中,模型和視圖模型主要通過數(shù)據(jù)綁定來進(jìn)行互動。理想情況下,視圖和視圖模型不必相互了解。綁定應(yīng)該是視圖和視圖模型之間的膠水,并且處理兩個(gè)方向的大多數(shù)東西。然而,在Anroid中它們不能真實(shí)的分離:
你要保存和恢復(fù)狀態(tài),但現(xiàn)在狀態(tài)在視圖模型中。
你需要讓視圖模型知道生命周期事件。
你可能會遇到需要直接調(diào)用視圖方法的情況。
在這些情況下,視圖和視圖模型應(yīng)該實(shí)現(xiàn)接口,然后在需要的時(shí)候通過命令通信。視圖模型的接口在任何情況都是需要的,因?yàn)閿?shù)據(jù)綁定庫會處理與視圖的交互,并在上下文需要的時(shí)候使用自定義組件。
視圖模型還會更新模型,比如往數(shù)據(jù)庫添加新的數(shù)據(jù),或者更新一個(gè)現(xiàn)有數(shù)據(jù)。它也用于從模型獲取數(shù)據(jù)。理想情況下,模型也應(yīng)該在變化的時(shí)候通知視圖模型,但這取決于實(shí)現(xiàn)。
一般來說,視圖和視圖模型的分離會讓呈現(xiàn)邏輯易于測試,也有助于維持長期運(yùn)行。與數(shù)據(jù)綁定庫一起會帶來更少更簡潔的代碼。
示例
使用 MVVM 的時(shí)候,布局只引用一個(gè)變量,即這個(gè)視圖的視圖模型,在這個(gè)示例中是 MyViewModel。在視圖模型中,你需要提供布局所需要的屬性,其簡單復(fù)雜程度取決于你的用例。
public class MyViewModel extends BaseObservable {
private Model model = new Model();
public void setModel(Model model) {
this.model = model;
notifyChange();
}
public boolean shouldShowText() {
return model.isTextRequired();
}
public void setText(String text) {
model.setText(text);
}
public String getText() {
return model.getText();
}
public void onButtonClick(View v) {
// Save data
}
}
這里有一個(gè) text 屬性。將 EditText 用于用戶輸入的時(shí)候,可以使用雙向綁定,同時(shí),數(shù)據(jù)綁定庫將輸入反饋回視圖模型。為此,我們創(chuàng)建一個(gè) setter 和 getter 并將屬性綁定到 EditText 的 text 屬性,這時(shí)候大括號前面的 = 號標(biāo)志著我們要在這里進(jìn)行雙向綁定。
另外,我們只想在模型需要輸入 text 的時(shí)候顯示 EditText。這種情況下,我們會在視圖模型中提供一個(gè)布爾屬性將其與 visibility 屬性綁定。為了讓它工作,我們還要創(chuàng)建一個(gè)綁定適配器(BindingAdapter),在值為 false 的時(shí)候設(shè)置 visibility 為 GONE,在值為 true 的時(shí)候設(shè)置為 VISIBLE。
@BindingAdapter("android:visibility")
public static void setVisibility(View view, boolean visible) {
view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
最后,我們想在點(diǎn)擊 Button 時(shí)存儲信息,于是,在視圖模型中創(chuàng)建一個(gè) onButtonClick() 命令,它負(fù)責(zé)處理與模型的交互。在布局中,我們通過對該方法引用將命令綁定到 Button 的 onClick 屬性上。為了使它直接工作,我們需要在方法中引入一個(gè) View 的單個(gè)參數(shù),類似于 OnClickListener。如果你不想使用 View 參數(shù),你也可以直接在布局中使用 lambda 表達(dá)式。
為方便測試,我們需要在視圖模型中展示邏輯處理,但要盡量避免將邏輯處理直接放入其中。當(dāng)然,你也可以自定義綁定適配器,這種方法更簡單。
生命周期和狀態(tài)
在實(shí)現(xiàn) MVVM 架構(gòu)的時(shí)候要考慮的另外一件事情是,在應(yīng)用中如何處理生命周期和狀態(tài)。首先,我建議你為視圖模型創(chuàng)建一個(gè)基類用于處理這類問題。
public abstract class BaseViewModel extends BaseObservable {
private V view;
@CallSuper public void attachView(V view, Bundle sis) {
this.view = view;
if(sis != null) { onRestoreInstanceState(sis); }
}
@CallSuper public void detachView() {
this.view = null;
}
protected void onRestoreInstanceState(Bundle sis) { }
protected void onSaveInstanceState(Bundle outState) { }
protected final V view() { return view; }
}
Activity 和 Fragment 中都有生命周期回調(diào)。現(xiàn)在它們都放在視圖模型中來處理。因此,我們需要傳遞生命周期回調(diào)。我建議使用兩個(gè)回調(diào),它們能滿足大多數(shù)需要:標(biāo)志著視圖被創(chuàng)建出來的 attachView() 和標(biāo)志著視圖被銷毀的 detachView()。在 attachView() 中,傳入視圖接口,用于在必要時(shí)向視圖發(fā)送命令。attachView() 通常在 Fragment 的 onCreate() 或 onCreateView() 中調(diào)用,detachView() 則是在 onDestory() 和 onDestoryView() 中調(diào)用。
現(xiàn)在 Activity 和 Fragment 也提供回調(diào),用于在系統(tǒng)銷毀組件或配置發(fā)生變化時(shí)保存狀態(tài)。我們把狀態(tài)保存在視圖模型中,還需要將這些回調(diào)傳遞給視圖模型。我建議把 savedInstanceState 直接傳遞至 attachView(),以便在這里自動恢復(fù)狀態(tài)。另一個(gè) onSaveInstanceState() 方法需要用于保存狀態(tài),這個(gè)方法必須在 Activity 和 Fragment 的相關(guān)回調(diào)中調(diào)用。如果有 UI 狀態(tài),可為每個(gè)視圖模型創(chuàng)建單獨(dú)的狀態(tài)類,當(dāng)這個(gè)類實(shí)現(xiàn) Parcelable 時(shí),保存和恢復(fù)狀態(tài)都很容易,因?yàn)槟阒恍枰4婊蚧謴?fù)一個(gè)對象。
視圖
public abstract class BaseActivity
extends AppCompatActivity implements MvvmView {
protected B binding;
@Inject protected V viewModel;
protected final void setAndBindContentView(@LayoutRes int layoutResId, @Nullable Bundle sis) {
binding = DataBindingUtil.setContentView(this, layoutResId);
binding.setVariable(BR.vm, viewModel);
viewModel.attachView((MvvmView) this, sis);
}
@Override @CallSuper protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(viewModel != null) { viewModel.onSaveInstanceState(outState); }
}
@Override @CallSuper protected void onDestroy() {
super.onDestroy();
if(viewModel != null) { viewModel.detachView(); }
binding = null;
viewModel = null;
}
}
現(xiàn)在,讓我們討論下視圖的細(xì)節(jié)。上面例子是創(chuàng)建 activity 基類。View 模型可通過注入用于基類,以便初始化架構(gòu)配置。然后你只需要在 activity 的 onCreate() 或 fragment 的 onCreateView() 中調(diào)用這個(gè)方法即可。
上面代碼使用了 setAndBindContentView() 方法處理,和通常的 setContentView() 調(diào)用不同,它可以在 onCreate() 中調(diào)用。此方法能設(shè)置內(nèi)容視圖并創(chuàng)建綁定,在綁定上設(shè)置視圖模型變量,并將視圖附加到視圖模型上,同時(shí)還提供保存的示例狀態(tài)。
如你所見,onSaveInstanceState() 和 detachView() 回調(diào)也可以在基類中實(shí)現(xiàn)。 onSaveInstanceState() 將回調(diào)轉(zhuǎn)發(fā)到視圖模型中,onDestroy() 則在視圖模型上調(diào)用 detachView() 接口。
通過這樣設(shè)置基類后,你就可以使用 MVVM 架構(gòu)編寫 APP 了。
其他考慮項(xiàng)
了解 MVVM 架構(gòu) Android 應(yīng)用的基礎(chǔ)后,還需對應(yīng)用程序架構(gòu)做進(jìn)一步完善。
依賴注入
使用依賴注入可以非常容易地將組件注入到視圖模型中,并將組件很好的聯(lián)合在一起,如使用 Dagger 2 依賴注入框架。
依賴注入可以進(jìn)一步解耦代碼,讓代碼更簡單也更容易測試。同時(shí),也大大增強(qiáng)了代碼的可維護(hù)性。更重要的是,依賴接口能真正實(shí)現(xiàn)解耦。
業(yè)務(wù)邏輯
注意:視圖模型只包含呈現(xiàn)邏輯,所以不要把業(yè)務(wù)邏輯放在視圖模型中。創(chuàng)建模型類的存儲接口并選擇的存儲方式將其實(shí)現(xiàn):
public interface ModelRepo {
Single> findAll();
Single findById(int id);
void save(Model model);
void delete(Model model);
}
對于網(wǎng)絡(luò),則使用 Retrofit 創(chuàng)建網(wǎng)絡(luò)相關(guān)的代碼來實(shí)現(xiàn)定義的接口。
public interface ModelRepo {
@GET("model")
Single> findAll();
@GET("model/{id}")
Single findById(@Path("id") int id);
@PUT("model")
Completable create(@Body Model model);
}
對于像查找、創(chuàng)建這樣的基本操作,可以將存儲庫注入到視圖模型中以獲取和操作數(shù)據(jù)。對于其它更復(fù)雜的情況,比如校驗(yàn),則需要創(chuàng)建獨(dú)立的組件來實(shí)現(xiàn)這些行為,并將其注入到視圖模型中。
導(dǎo)航
Android 中另一個(gè)重要內(nèi)容是導(dǎo)航,因?yàn)槟阈枰晥D提供組件,它可能是啟動 Activity 的 Context,也可能是替換 Fragment 的 FragmentManager。同時(shí),使用視圖接口來調(diào)用導(dǎo)航命令只會讓架構(gòu)變得更復(fù)雜。
因此,我們需要一個(gè)獨(dú)立的組件來處理應(yīng)用中的導(dǎo)航。Navigator 接口定義了一些公共方法用于啟動 Activity,處理 Fragment 并將它們注入視圖模型中。你可以直接在視圖模型中進(jìn)行導(dǎo)航,而不需要 Context 或者 FragmentManager,因?yàn)檫@些都是由導(dǎo)航器的實(shí)現(xiàn)來處理的。
public interface Navigator {
String EXTRA_ARGS = "_args";
void finishActivity();
void startActivity(Intent intent);
void startActivity(String action);
void startActivity(String action, Uri uri);
void startActivity(Class extends Activity> activityClass);
void startActivity(Class extends Activity> activityClass, Bundle args);
void replaceFragment(int containerId, Fragment fragment, Bundle args);
void replaceFragmentAndAddToBackStack(int containerId, @NonNull Fragment fragment,
Bundle args, String backstackTag);
...
}
視圖持有者可以在視圖模型中使用導(dǎo)航器進(jìn)行導(dǎo)航,十分方便。比如,點(diǎn)擊回收視圖的某張卡片可以啟動新的 Activity。
單元測試
最后,我們了解一下視圖模型和單元測試。正如前面提到的,MVVM 架構(gòu)能簡化測試呈現(xiàn)邏輯。我更一般使用 Mockito,它讓我可以模擬視圖接口和其它注入視圖模型和組件。當(dāng)然,你也可以使用 PowerMock 來進(jìn)行要求更高的測試,它使用字節(jié)碼控制,可以模擬靜態(tài)方法。
public class MyViewModelUnitTest {
@Mock ModelRepo modelRepo;
@Mock Navigator navigator;
@Mock MvvmView myView;
MyViewModel myViewModel;
@Before public void setup() {
MockitoAnnotations.initMocks(this);
myViewModel = new MyViewModel(modelRepo, navigator);
myViewModel.attachView(myView, null);
}
@Test public void buttonClick_submitsForm() {
final Model model = new Model();
doReturn(model).when(modelRepo).create();
myViewModel.onButtonClick(null);
verify(modelRepo).save(model);
verify(navigator).finishActivity();
}
}
在 setup() 方法中初始化 mock,創(chuàng)建視圖模型,同時(shí)注入 mock 對象并將視圖接口附加到視圖模型。寫測試用例的時(shí)候,若有必要,先通過 Mockito 的 doReturn().when() 語法指定 mock 對象的行為。 然后在視圖模型中調(diào)用測試方法。最后使用斷言和 verify() 方法檢查返回值是否正確,檢查 mock 的方法是否按預(yù)期進(jìn)行調(diào)用。
? 關(guān)于按照 ModelViewViewModel 模式使用數(shù)據(jù)綁定庫組織 app 架構(gòu),總結(jié)如下:
? 視圖模型是視圖和模型之間的中間介。
? 視圖通過數(shù)據(jù)綁定自動更新視圖模型的屬性。
? 視圖事件可調(diào)用視圖模型中的命令。
? 視圖模型也可在視圖上調(diào)用命令。
? 在 Android 中,視圖模型可以處理基本的生命周期回調(diào)和狀態(tài)保存及恢復(fù)。
? 依賴注入有助于測試和獲得更整潔的代碼。
? 不要在視圖模型中放置業(yè)務(wù)邏輯,它們只包含展示邏輯。另外,要使用存儲庫進(jìn)行數(shù)據(jù)訪問。
? 在 Android App 中導(dǎo)航請使用導(dǎo)航器組件。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。