原始碼請點選:github.com/shuaijia/Js…
您還可以關注我的微信公眾號——安卓乾貨營,與我交流和獲取更多精彩內容。
概述
說到Android MVVM,就會聯想到DataBinding框架。然而兩者的概念是不一樣的,不能混為一談。MVVM是一種架構模式,而DataBinding是一個實現資料和UI繫結的框架,是構建MVVM模式的一個工具。
網上關於MVVM框架的搭建和使用的文章很少,大多提到MVVM框架,就是在介紹DataBinding的使用。對於MVVM中各模組之間如何劃分,如何定義,又是如何配合實現高度解耦的文章更是少之又少。大家看完後還是一頭霧水,只是對MVVM有個大概的瞭解,並不很清楚如何上手。
接下來,我們先認識什麼是MVVM,然後再一步一步來設計整個MVVM框架。
MVC、MVP簡介
MVC、MVP和MVVM都是在安卓開發中經常使用的模式,我們在認識MVVM之前先回顧一下MVC和MVP。
MVC
- View:xml佈局
- Model:資料層,負責資料互動、儲存和實體類定義
- Controller:業務處理層
Android開發本身還是比較符合MVC架構的,但是Android中純粹作為View的XML檢視功能太弱,我們大量處理View的邏輯只能寫在Activity中,這樣Activity就充當了View和Controller兩個角色,直接導致Activity中的程式碼臃腫、混亂,導致閱讀困難、重用困難和維護困難。相信大多數Android開發者都遇到過一個Acitivty數以千行的程式碼情況吧!所以,更貼切的說法是,這個MVC結構最終其實只是一個Model-View(Activity:View&Controller)的結構。
MVP
- View:xml檔案及對應的Activity或Fragment,負責介面展示和互動
- Model:資料層,負責資料互動、儲存和實體類定義
- Presenter:負責View層和Model層之間的邏輯處理
前面我們說,Activity充當了View和Controller兩個角色,MVP就能很好地解決這個問題,其核心理念是通過一個抽象的View介面(不是真正的View層)將Presenter與真正的View層進行解耦。Persenter持有該View介面,對該介面進行操作,而不是直接操作View層。這樣就可以把檢視操作和業務邏輯解耦,從而讓Activity成為真正的View層。
不足的是,MVP模式中定義了大量的介面,使得程式碼結構變大和複雜;MVP是UI和事件驅動,需要手動呼叫大量的方法來進行實現,缺乏自動性。
所以我們迎來了MVVM框架,當然得首先感謝google爸爸提供得DataBinding,真的是很強大!
MVVM簡介
在MVVM模式中,將程式結構分為三層——View-ViewModel-Model,接下來我們一起來認識它們:
View:
View層負責和UI相關的工作,我們只在XML、Activity和Fragment寫View層的程式碼,View層不進行業務處理,也就是我們在Activity不寫業務邏輯和業務資料相關的程式碼。
更新UI通過資料繫結實現,儘量在ViewModel裡面做,Activity要做的事就是初始化一些控制元件(如RecyclerView設定LayoutManager或者控制元件的顯隱),View層可以通過資料來驅動更改UI,UI事件通過Command來繫結。
簡而言之:View層不做任何業務邏輯、不涉及運算元據,UI和資料嚴格的分開。 **UI更新和事件相應全部使用資料繫結,也就是DataBinding來實現。**這就是MVVM和MVP、MVC很明顯的不同之處。
ViewModel
ViewModel層做的事情剛好和View層相反,ViewModel只負責業務邏輯,不做任何和UI相關的事情。
同時DataBinding框架已經支援雙向繫結,讓我們可以通過雙向繫結獲取View層反饋給ViewModel層的資料,並對這些資料上進行操作。
事件的處理,我們也希望能把這些事件處理繫結到控制元件上,並把這些事件的處理統一化,為此我們通過使用BindingAdapter對一些常用的事件做封裝,把一個個事件封裝成一個個Command,對於每個事件我們用一個ReplyCommand去處理就行了,ReplyCommand會把你可能需要的資料帶給你,這使得我們在ViewModel層處理事件的時候只需要關心處理資料就行了,具體見MVVM Light Toolkit 使用指南的Command部分。
Model
Model層不僅包括實體類的定義,還需要對資料進行處理和讀寫。例如:使用Retrofit或okHttp進行網路請求,或著如資料庫操作等等。
優點:
- 資料驅動
- 低耦合
- 主執行緒更新UI
- 可複用性
- 方便單元測試
我們再來看下這張圖:
簡述下資料流走向:
View中使用DataBinding的Command來繫結事件和響應事件,觸發網路請求;ViewModel進行分析處理,呼叫Model的資料請求方法;Model將收到的請求引數等資訊封裝,呼叫網路請求庫;網路庫(Retrofit等)與伺服器進行互動;
伺服器將json資料返回Retrofit等網路庫,再返回到Model層中,ViewModel在回撥中收到返回的實體類物件;
因為xml與實體類物件實現了雙向繫結,實體類更新,使得UI更新!
ok!接下來我們就用活生生的例子來實現MVVM吧
A、實體類定義
/**
* Description:
* Created by jia on 2017/11/3.
* 人之所以能,是相信能
*/
public class NewslistBean extends BaseObservable {
private String ctime;
private String title;
private String description;
private String picUrl;
private String url;
public NewslistBean(String ctime, String title, String description, String picUrl, String url) {
this.ctime = ctime;
this.title = title;
this.description = description;
this.picUrl = picUrl;
this.url = url;
}
public String getCtime() {
return ctime;
}
public void setCtime(String ctime) {
this.ctime = ctime;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@BindingAdapter("bind:imageUrl")
public static void loadImage(ImageView imageView, String picUrl) {
Glide.with(imageView.getContext())
.load(picUrl)
.into(imageView);
}
public void onItemClick(View pView) {
Toast.makeText(pView.getContext(), title, Toast.LENGTH_SHORT).show();
}
}
複製程式碼
這和平時寫的實體類是不是沒啥區別!
是的,所有的屬性我們依舊如原來原來一樣定義和設定get、set方法。但是,有一點不同的是實體類繼承了BaseObservable,稍後我們再說。
B、Model類
/**
* Description: 新聞網路請求model
* Created by jia on 2017/11/3.
* 人之所以能,是相信能
*/
public class NewsModel {
public void getNews(final OnCallBack onCallBack) {
HttpMethod.getInstance().getNews(new Subscriber<News>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
onCallBack.onFaile(e.toString());
}
@Override
public void onNext(News news) {
onCallBack.onSuccess(news);
}
});
}
public interface OnCallBack {
void onSuccess(News news);
void onFaile(String errorInfo);
}
}
複製程式碼
這裡呢,我使用的是自己封裝過的Retrofit+RxJava的網路請求庫,上面的Model用來進行新聞實體類News的網路請求;
也定義了一個CallBack介面:此回撥可以讓接下的ViewModel獲得Model請求回來的實體類。
每個專案的網路請求庫和方法都會不同,符合自己的就是最好的!(●ˇ∀ˇ●)
C、View
xml中
先看示例:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="news"
type="com.jia.jsmvvm.home.viewmodel.NewslistBean" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".home.view.MainActivity">
<android.support.v7.widget.CardView
android:id="@+id/cv_tuijian"
android:layout_width="match_parent"
android:layout_height="130dp"
android:layout_margin="15dp"
android:background="#ffffff"
android:elevation="5dp"
android:onClick="@{news.onItemClick}"
app:cardElevation="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp">
<ImageView
android:id="@+id/iv_tuijian"
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_margin="15dp"
android:scaleType="fitXY"
app:imageUrl="@{news.picUrl}" />
<TextView
android:id="@+id/tv_tuijian_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/iv_tuijian"
android:layout_toRightOf="@id/iv_tuijian"
android:ellipsize="end"
android:maxLines="2"
android:text="@{news.title}"
android:textColor="#111111"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_tuijian_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:ellipsize="end"
android:lines="3"
android:singleLine="true"
android:text="@{news.ctime}"
android:textColor="#777777"
android:textSize="14sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
</layout>
複製程式碼
大家可定已經發現了:佈局的編寫和往常比還是又較大變化的。
熟悉DataBinding的朋友可以直接跳過這趴。由於本人對DataBinding也不是特別熟練,所以也只能和大家分享自己瞭解的一點使用方法。DataBinding擁有非常強大的功能,想深入瞭解的可以網上搜尋,當然,本人不久也會把自己瞭解的DataBinding的知識整理成一篇部落格,敬請期待!
- 我們使用 layout 作為佈局檔案的跟節點
- layout中包含data節點和普通的佈局
- data節點中建立variable
- variable中有兩個“屬性”:name和type
- type宣告實體類,格式為 包名.類名
- name為type中的實體類定義“名字”,供以下佈局中使用
- 定義了data屬性後,就相當於xml佈局已和實體類繫結
- 在控制元件中引用實體類屬性的格式為: @{實體類.屬性名}
- 在控制元件中引用實體類方法的格式為: @{實體類.方法名}
- 涉及到圖片載入:在實體類中使用@BindingAdapter註解圖偏載入方法,在佈局中引用url即可
因為本篇文章重點在於講述MVVM框架的使用,所以DataBinding只進行粗略簡介,如有錯誤,還望大家及時提出,我們一起進步!
Activity中
在Activity中設定佈局,我們不再使用Activity的setContentView方法,取而代之的是:DataBindingUtil.setContentView
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
複製程式碼
所返回的變數型別怎麼來的呢?
將使用了DataBinding的佈局名字,去掉所有下劃線,將所有單詞首字母大寫,直接進行拼接,最後加上 Binding即可!
View層中這樣就可以了!哈哈!
D、ViewModel
ViewModel層大家比較不熟悉,他和MVC的Controller、MVP的Presenter到底有什麼區別呢?
ViewModel類應該怎麼寫呢?
先看下程式碼:
/**
* Description: 新聞ViewModel類
* Created by jia on 2017/11/3.
* 人之所以能,是相信能
*/
public class NewsViewModel {
public Activity activity;
public ActivityMainBinding activityMainBinding;
public NewslistBean news;
public NewsModel model;
private int num=1;
public NewsViewModel(Activity activity, final ActivityMainBinding activityMainBinding) {
this.activity = activity;
this.activityMainBinding = activityMainBinding;
model=new NewsModel();
model.getNews(new NewsModel.OnCallBack() {
@Override
public void onSuccess(News news) {
activityMainBinding.setNews(news.getNewslist().get(0));
}
@Override
public void onFaile(String errorInfo) {
news=new NewslistBean("error","error","error","error","error");
activityMainBinding.setNews(news);
}
});
}
}
複製程式碼
看看裡邊有些啥:
- Context或Activity物件(這個應該好理解把)
- 在Activity中建立的Binding物件
- 實體類物件
- Model層物件
- ChildViewModel(例如Activity中巢狀多個Fragment的情況)
將實體類物件通過setXXX方法,設定給Binding物件。
當事件觸發時,Model進行網路請求,在回撥中更新實體類,便可對應的更新UI介面。
總結
例項中只是一個簡單的功能的展示,大家在熟悉了MVVM後可再深度封裝。
本文主要講解了一些本人再開發過程中總結的Android MVVM構建思想,更多是理論上各個模組如何分工、程式碼如何設計。雖然在現實生產中用Android MVVM模式開發還比較少,但是隨著DataBinding 1.0的釋出,相信在Android MVVM 這一領域會更多的人來嘗試。
由於時間有限,能力有限,文中不免有錯誤或不足的地方,還請大家提出,我們一同進步!
原始碼請點選:github.com/shuaijia/Js…
您還可以關注我的微信公眾號——Android機動車,獲取更多精彩內容!