初探安卓MVVM框架設計#
一. 什么是MVVM?
MVVM是近幾年流行的一種設計框架,基于該框架設計的應用程序具有良好的解耦和可擴展性,大幅降低了維護成本,提高了程序員的開發(fā)效率.在了解MVVM框架之前,我們有必要回顧一下其他設計框架.
1. MVC模式
MVC模式的意思是,軟件可以分成三個部分.
視圖(View):用戶界面
控制器(Controller):業(yè)務邏輯
模型(Model):數(shù)據(jù)保存
各部分之間的通信方式如下.
1.View傳送指令到Controller
2.Controller完成業(yè)務邏輯后,要求Model改變狀態(tài)
3.Model將新的數(shù)據(jù)發(fā)送到View,用戶得到反饋
所有通信都是單向的.我們傳統(tǒng)的Android開發(fā)都是基于這種模式.每一層可以代表我們常用的如下組件:
Model層: sqlite數(shù)據(jù)庫, JavaBean, SharedPreference, sdcard,獲取網(wǎng)絡數(shù)據(jù)的api等
View層: xml布局文件,自定義控件等
Controller層: Activity等
此處需要注意的是,在傳統(tǒng)的MVC設計模式中,
Activity屬于Controller層而不是View層,因為Activity即承擔了數(shù)據(jù)調用,也承擔了界面展示,相當于View和Model中間的協(xié)調器.很多初學者都會誤認為Activity屬于View層.當然,這種說法僅限用MVC模式,換做其他模式就不一定了哦!
2. MVP模式
MVC模式普及了一段時間之后,逐漸暴露出一些問題.比如我們發(fā)現(xiàn),
Activity中寫的代碼太多,有時候一個Activity甚至達到了四五千行代碼,維護起來極為不便.原因也很明顯,就是Activity既參與api訪問和數(shù)據(jù)調用,又參與了界面的更新,職能劃分不明確,沒有完全實現(xiàn)解耦.我們的想法是,能不能讓Activity只做界面響應和更新,其他業(yè)務邏輯全部由另外一個單獨模塊來完成?于是MVP誕生了.
MVP模式將Controller改名為Presenter,同時改變了通信方向.
1.各部分之間的通信,都是雙向的.
2.View與Model不發(fā)生聯(lián)系,都通過Presenter傳遞.
3.View非常薄,不部署任何業(yè)務邏輯,稱為"被動視圖"(Passive View),即沒有任何主動性,而Presenter非常厚,所有邏輯都部署在那里.
當這樣調整了之后, Activity就純粹屬于View層了,所有業(yè)務邏輯全由Presenter來完成.當View界面被用戶操作時(比如按鈕點擊), View層就會調用Presenter完成相關業(yè)務邏輯,而Presenter完成了之后,就會將結果以回調的形式傳遞給View層,由View層完成界面刷新.具體代碼如何實現(xiàn)我就不多說了,因為我們今天的重點是MVVM,如果有興趣研究的話可以在網(wǎng)上搜索MVP相關的例子程序,我也找了一個,僅供參考:
http://blog.csdn.net/vector_yi/article/details/24719873
3. MVVM模式
當我們采用MVP模式之后,發(fā)現(xiàn)Activity幾乎沒啥事可做了,我們的項目代碼層級也清晰了,也好維護了.但是MVP也有缺點,比如,為了實現(xiàn)MVP,我們需要額外增加好多接口和類,比如,一個Activity需要對應一個Presenter類和Presenter接口,同時為了方便Activity和Presenter進行通信,還得再定義一個回調接口IView,也就是說,每一個Activity都需要額外增加兩個接口和一個類,無疑提高了代碼量.而MVVM的誕生,就解決了這個問題!
MVVM模式將Presenter改名為ViewModel,基本上與MVP模式完全一致.
唯一的區(qū)別是,它采用雙向綁定(data-binding):View的變動,自動反映在ViewModel,反之亦然.
有沒有注意到, MVVM和MVP幾乎是一樣的,唯一的不同就在于View和ViewModel之間的那根線, MVP是兩根,表示View調用Presenter執(zhí)行邏輯,Presenter調用View來返回數(shù)據(jù),更新界面;MVVM中只有一根線兩個箭頭,代表的是View和ViewModel雙向綁定,自動同步數(shù)據(jù),無需手動調用相關方法進行通信,從而減少了代碼量.而這種雙向綁定的機制,都歸功于谷歌推出的DataBinding的新功能.下面我們來研究一下到底什么是DataBinding.
二. 使用DataBinding構建MVVM框架
2.1 什么是DataBinding
2015 Google IO大會帶來的DataBinding庫使得Android開發(fā)者可以方便的實現(xiàn)MVVM架構模式.使用DataBinding可以改善應用程序的開發(fā),使代碼更加干凈優(yōu)雅.
DataBinding的使用教程在網(wǎng)上已經(jīng)很多了,我在這里只是簡單提一下最基本的用法,大家體驗一下就好.如果想更深入學習的話,建議查看谷歌官方文檔:https://developer.android.com/topic/libraries/data-binding/index.html
2.2 DataBinding環(huán)境配置
1.由于新版Android Studio已經(jīng)內置了DataBinding的功能,為了方便開發(fā),請確保使用AndroidStudio 1.3及以上的版本.
2.在app的build.gradle文件中添加下面的內容:
android {
....
dataBinding {
enabled =true
}
}
3.重新編譯項目,配置完成.
2.3 DataBinding的基本使用
1.布局文件
根標簽使用layout,在layout標簽下用data標簽來配置數(shù)據(jù),例子如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="cn.itcast.mvvmdemo.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>
<variable name="user" type="cn.itcast.mvvmdemo.User"/>
這句話代表,聲明了一個user變量,類型是cn.itcast.mvvmdemo.User,當然這個User要提前定義.
public class User {
private String firstname;
private String lastname;
public User(String firstname, Stringlastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(Stringfirstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(Stringlastname) {
this.lastname = lastname;
}
}
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstname}"/>
控件布局寫法和以前一樣,唯一不同之處在于控件內容的賦值部分.以前我們都會寫一個默認值,然后再在代碼中動態(tài)修改控件的值.此時已經(jīng)不需要了. @{user.firstname}代表當前TextView的值取自于user對象中的firstname字段.
2. Activity代碼
public class MainActivity extends AppCompatActivity {
private User user;
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding =DataBindingUtil.setContentView(this, R.layout.activity_main);
user = new User("尼古拉斯凱奇", "趙四");
binding.setUser(user);
}
}
ActivityMainBinding是DataBinding自動根據(jù)布局文件生成的類,不需要手動創(chuàng)建.該類的命名方式取自于布局文件的名稱.比如布局文件名叫activity_main,那么生成的類名就叫ActivityMainBinding.
當使用DataBinding時,需要用DataBindingUtil來設置Activity的布局.
binding.setUser(user);表示將user對象和布局文件綁定在了一起,
user對象的所有屬性值都可以同步映射到布局文件的控件中.
3. 運行效果
你會發(fā)現(xiàn),我們沒有像往常那樣在activity中findViewById,找到控件后給動態(tài)賦值,而是通過DataBinding的方式直接將對象的值作用在了布局文件中,從而使我們的代碼更加優(yōu)雅和簡潔.
2.3 DataBinding響應點擊事件
1.首先,寫一個事件處理器MyHandler
public class MyHandler {
public void onButtonClick(View view){
System.out.println("按鈕被點擊了");
}
}
這是一個普通的類,在onButtonClick中處理按鈕點擊后應該執(zhí)行的操作.
2.在之前布局文件的基礎上,添加一個按鈕
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="cn.itcast.mvvmdemo.User"/>
<variable
name="handler"
type="cn.itcast.mvvmdemo.MyHandler"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<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}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{handler.onButtonClick}"
android:text="點擊我"
/>
</LinearLayout>
</layout>
在data中聲明handler,類型是MyHandler,在Button的onClick中定義要執(zhí)行的操作.
android:onClick="@{handler.onButtonClick}"
3.在Activity中,將MyHandler設置給Binding對象.
binding.setHandler(new MyHandler());
4.運行后看效果
2.4 數(shù)據(jù)變化后同步更新界面
點擊按鈕之后,我們想修改一下firstname和lastname的值,然后更新界面.如果采用DataBinding的話,我們會怎么做?
1.將用戶對象傳遞給MyHandler
public class MyHandler {
private User user;
public MyHandler(User user) {
this.user = user;
}
public void onButtonClick(View view){
System.out.println("按鈕被點擊了");
user.setFirstname("蒙拉麗莎");
user.setLastname("鴨蛋");
}
}
在按鈕點擊的時候,修改了user的firstname和lastname.如果放在往常,你肯定就立馬想找到那兩個TextView對象來重新設置數(shù)據(jù),而現(xiàn)在,你什么都不用做,只要數(shù)據(jù)變了,界面就會立即同步更新.有這么神奇?其實你得提前做好準備,才會有這樣的效果.
2.我們需要把User類調整一下:
public class User extends BaseObservable {
private String firstname;
private String lastname;
public User(String firstname, Stringlastname) {
this.firstname = firstname;
this.lastname = lastname;
}
@Bindable
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
notifyPropertyChanged(BR.firstname);
}
@Bindable
public String getLastname() {
return lastname;
}
public void setLastname(String lastname){
this.lastname = lastname;
notifyPropertyChanged(BR.lastname);
}
}
在getFirstname和getLastname兩個方法中加注解@Bindable,這樣的話DataBinding會自動在BR文件中生成這兩個字段的id. BR文件類似于R文件,是DataBinding特有的用于維護id的一個文件. BR文件由編譯器自動生成.
在setFirstname和setLastname的方法中添加notifyPropertyChanged方法,同時將你要更新的字段id傳遞過去.此方法用于通知系統(tǒng)數(shù)據(jù)已經(jīng)變化,需要更新界面.
3.我們案例的最終項目結構如下圖所示:
三. 總結
在學習MVVM框架時我一直有一個糾結:MVC和MVP結構很清晰,我很容易能分清楚哪個組件屬于哪個模塊,但到了MVVM我就有點暈了,因為網(wǎng)上所有介紹MVVM的文章幾乎都指向了DataBinding,并沒有講到具體每一層對應哪些組件.目前就我的初步了解,我大概會這么劃分:
View層: xml, Activity,自定義控件等;
Model層: sqlite數(shù)據(jù)庫, JavaBean,SharedPreference, sdcard,獲取網(wǎng)絡數(shù)據(jù)的api等
ViewModel層:獨立的業(yè)務邏輯處理模塊,部分參與業(yè)務邏輯的JavaBean
在我們的例子項目中,MainActivity, activity_main.xml屬于View層; User屬于Model層; MyHandler屬于ViewModle層.
不過后來我又想了一下,我們真有必要劃分清楚誰是View,誰是ViewModel,誰是Model嗎?程序設計本來就很復雜,難免會碰到一些模棱兩可的模塊,各個層都參與一下,但又不屬于任何一層.我們開發(fā)應用程序是為了實現(xiàn)功能,我們進行框架設計是為了提高擴展性并降低維護成本,在這種大前提下,我們的細節(jié)如何處理就已經(jīng)無關緊要了.事實上,當你采用了DataBinding來構建你的程序時,你其實就已經(jīng)在用MVVM框架了.
當然DataBinding的用法還有很多,此文介紹的只是冰山一角,比如如何在ListView和RecyclerView中使用DataBinding,布局文件中關于DataBinding的高級用法等等,此文都沒有提及.如果你想了解更多,就請關注官方文檔.
關于MVVM和DataBinding的資料和博客,網(wǎng)上已經(jīng)有很多了,由于MVVM內容確實繁雜,所以網(wǎng)上的文章沒有特別全面的,側重點都有所不同.當然,此文是從另一個角度來重新解讀了一下MVVM模式,如果能從此文中獲取對你有益的內容,會讓我倍感欣慰.
Demo附件下載鏈接: http://pan.baidu.com/s/1pLligyf