用一個小栗子描述MVC、MVP和MVVM之間的關係
項目結構:
這是一個非常簡單的例子,點擊按鈕,查詢現在廣州的天氣信息。
MVC
什麼是MVC?(來自百度百科的回答)
MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典範,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯聚集到一個部件裏面,在改進和個性化定製界面及用戶交互的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用於映射傳統的輸入、處理和輸出功能在一個邏輯的圖形化用戶界面的結構中。
在Android中,Model、View和Controller的描述:
- 模型層 Model
針對業務模型,建立的數據結構和相關的類,提供接口操作數據和處理業務邏輯。 - 視圖層 View
顯示數據和用戶交互的界面,一般採用xml文件進行界面描述,可以理解爲Android APP中的View,Activity或者Fragment。 - 控制層 Controller
作爲Model和View之間的橋樑,來控制View層和Model層之間的通訊,從而達到分離視圖顯示和業務邏輯層。但是通常這部分都寫在了Activity之上,所以Activity裏面的代碼會非常臃腫。
當View需要更新時,首先去找Controller,然後Controller去找Model獲取數據,Model獲取數據之後,通知View更新UI。
例子說明
先定義一個獲取天氣預報的接口
public interface IWeatherModel {
void getWeather(OnWeatherListener onWeatherListener);
}
需要定義個接口監聽天氣信息是否獲取成功
public interface OnWeatherListener
{
void onSuccess(WeatherBean weather);
void onError();
}
實現獲取天氣預報接口
public class WeatherModelImpl implements IWeatherModel
{
@Override
public void getWeather(final OnWeatherListener onWeatherListener) {
String url = "http://www.weather.com.cn/data/sk/101280101.html";
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
onWeatherListener.onError();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = response.body().string();
Gson gson = new Gson();
WeatherBean weather = gson.fromJson(body, WeatherBean.class);
onWeatherListener.onSuccess(weather);
}
});
}
}
到這裏,Model層已經寫好了,提供了getWeather()方法獲取天氣信息。
View層
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".mvc.MvcActivity">
<TextView
android:id="@+id/tv_weather_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textSize="28sp"
/>
<Button
android:id="@+id/bt_query"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/tv_weather_info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/bt_query_text"
/>
</android.support.constraint.ConstraintLayout>
Controller層, 這裏的Controller就是weatherModel.getWeather(this)
@OnClick(R.id.bt_query)
public void onQueryClick() {
weatherModel.getWeather(this);
}
View & Controller 全部代碼
package com.act.androidarchtest.mvc;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import android.widget.Toast;
import com.act.androidarchtest.R;
import com.act.androidarchtest.base.BaseActivity;
import com.act.androidarchtest.bean.WeatherBean;
import com.act.androidarchtest.mvc.model.IWeatherModel;
import com.act.androidarchtest.mvc.model.OnWeatherListener;
import com.act.androidarchtest.mvc.model.WeatherModelImpl;
import butterknife.BindView;
import butterknife.OnClick;
public class MvcActivity extends BaseActivity implements OnWeatherListener {
@BindView(R.id.tv_weather_info)
TextView mTvWeatherInfo;
private static final int QUERY_WEATHER_SUCCESS = 1;
private static final int QUERY_WEATHER_FAIL = 0;
private IWeatherModel weatherModel;
private WeatherBean weather;
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case QUERY_WEATHER_FAIL:
Toast.makeText(getApplicationContext(), "獲取天氣信息失敗", Toast.LENGTH_SHORT).show();
break;
case QUERY_WEATHER_SUCCESS:
mTvWeatherInfo.setText(weather.toString());
break;
}
return false;
}
});
@Override
protected void initActivity() {
weatherModel = new WeatherModelImpl();
}
@OnClick(R.id.bt_query)
public void onQueryClick() {
weatherModel.getWeather(this);
}
@Override
public void onSuccess(WeatherBean weather) {
this.weather = weather;
mHandler.sendEmptyMessage(QUERY_WEATHER_SUCCESS);
}
@Override
public void onError() {
mHandler.sendEmptyMessage(QUERY_WEATHER_FAIL);
}
@Override
protected int bindActivityLayout() {
return R.layout.activity_mvc;
}
}
當用戶點擊查詢按鈕,這裏的Activity既作爲Controller去找到WeatherModel,並調用它的getWeather()方法去獲取天氣信息,這裏的Activity又作爲View,當WeatherModel處理完成之後,通過接口OnWeatherListener通知View視圖數據獲取成功,並更新數據顯示。
到這裏,整個MVC架構就可以體現出來了。
MVP
爲什麼要用MVP呢?
從上面的Activity代碼中可以看出,Model層可以直接更新View層,而且Activity既充當了View層,又充當了Controller層,如果Controller層業務變得越來越多,那麼Activity引入的代碼量是比較大的,後期難於維護,因此引入了MVP架構,其實MVP更是一種思想,完全解耦Model層與View層之間的聯繫。
所謂MVP就是 Model、View和Presenter。注意這裏引入了Presenter。
- Model
跟MVC的中的Model一樣,負責存儲、檢索、操縱數據(有時也實現一個Model interface用來降低耦合) - View
跟MVC中的View一樣,負責界面顯示和用戶交互 - Presenter
作爲View與Model交互的中間紐帶,處理用戶交互帶來的業務。
當View需要更新數據時,首先去找Presenter,然後Presenter去找Model請求數據,Model獲取數據之後通知Presenter,Presenter再通知View更新數據。這樣Model和View就不會直接交互了,所有的交互由Presenter進行,Presenter充當橋樑角色。因此,Presenter必須持有View和Model對象的引用,才能在這兩者之間進行通信。
將上面的例子改成MVP架構
改一下IWeatherModel接口,取消getWeather()方法裏面的參數,增加一個onCancel()取消方法。
public interface IWeatherModel {
void getWeather();
void onCancel();
}
改一下IWeatherModel接口實現類WeatherModelImpl
public class WeatherModelImpl implements IWeatherModel
{
private static final int QUERY_WEATHER_SUCCESS = 1;
private static final int QUERY_WEATHER_FAIL = 0;
private OnWeatherListener onWeatherListener;
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (null == onWeatherListener) return false;
switch (msg.what) {
case QUERY_WEATHER_SUCCESS:
WeatherBean weather = (WeatherBean) msg.obj;
onWeatherListener.onSuccess(weather);
break;
case QUERY_WEATHER_FAIL:
onWeatherListener.onError();
break;
}
return false;
}
});
public WeatherModelImpl(OnWeatherListener onWeatherListener) {
this.onWeatherListener = onWeatherListener;
}
@Override
public void getWeather() {
String url = "http://www.weather.com.cn/data/sk/101280101.html";
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Message message = new Message();
message.what = QUERY_WEATHER_FAIL;
mHandler.sendMessage(message);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = response.body().string();
Gson gson = new Gson();
WeatherBean weather = gson.fromJson(body, WeatherBean.class);
Message message = new Message();
message.what = QUERY_WEATHER_SUCCESS;
message.obj = weather;
mHandler.sendMessage(message);
}
});
}
@Override
public void onCancel() {
mHandler.removeMessages(mHandler.obtainMessage().what);
}
}
使用OnWeatherListener接口回調,如果獲取天氣信息成功,回調onWeatherListener.onSuccess(weather),請求失敗回調onWeatherListener.onError();實現onCancel(),取消Handler裏面的任務,防止Activity被回收掉了,請求數據將變得沒有任何意義。
創建一個IWeatherView接口,用來更新View上的天氣信息
public interface IWeatherView {
void updateWeather(WeatherBean weatherBean);
void updateError();
}
創建一個Presenter,作爲IWeatherView 和WeatherModelImpl之間通信的橋樑
public class WeatherPresenter {
private IWeatherModel weatherModel;
private IWeatherView weatherView;
public WeatherPresenter(IWeatherView weatherView) {
this.weatherView = weatherView;
weatherModel = new WeatherModelImpl(new OnWeatherListener() {
@Override
public void onSuccess(WeatherBean weather) {
WeatherPresenter.this.weatherView.updateWeather(weather);
}
@Override
public void onError() {
WeatherPresenter.this.weatherView.updateError();
}
});
}
public void getWeather() {
weatherModel.getWeather();
}
public void detachedView() {
weatherView = null;
weatherModel.onCancel();
}
}
從這裏就可以看出來,當外部調用weatherPresenter.getWeather()時,WeatherPresenter就會去找weatherModel,就是調用它的getWeather()方法,當weatherModel獲取數據之後,通知weatherPresenter,由weatherPresenter去通知weatherView進行UI更新,就是調用weatherView.updateWeather(weather)
在這裏增加了detachView(),防止Activity被回收的時候,Presenter還持有它的引用,防止內存泄露。
View層
public class MvpActivity extends BaseActivity implements IWeatherView {
@BindView(R.id.tv_weather_info)
TextView mTvWeatherInfo;
private WeatherPresenter weatherPresenter;
@Override
protected void initActivity() {
weatherPresenter = new WeatherPresenter(this);
}
@OnClick(R.id.bt_query)
public void onQuery() {
weatherPresenter.getWeather();
}
@Override
public void updateWeather(WeatherBean weatherBean) {
mTvWeatherInfo.setText(weatherBean.toString());
}
@Override
public void updateError() {
Toast.makeText(this, "獲取天氣信息失敗", Toast.LENGTH_SHORT).show();
}
@Override
protected int bindActivityLayout() {
return R.layout.activity_mvp;
}
@Override
protected void onDestroy() {
weatherPresenter.detachedView();
super.onDestroy();
}
上面的Activity實現了IWeatherView接口,Presenter就可以通過調用IWeatherView接口更新UI,並且持有這個View的引用。
View本身只用於數據顯示,並不處理任何數據業務。在這裏View不再依賴於Model,View不參與決策,View與Model不能直接交互,Presenter是整個MVP體系的控制中心。View通過Presenter與Model打交道。Presenter接受View的UI請求,完成簡單的UI處理邏輯,並調用Model進行業務處理,並調用View將相應的結果反映出來,這就是MVP架構思想。
MVVM
其實MVVM也是一種思想,是MVP的升級版,其中VM指的是ViewModel。實現MVVM也有很多種方法,下面我用的是比較普遍的DataBinding方式。DataBinding可以實現雙向的交互,這就使得視圖和控制層之間的耦合程度進一步降低,關注點分離更爲徹底,同時減輕了Activity的壓力。
官方是支持MVVM模式框架的,可以在xml中直接綁定數據,無需再用ButterKnife或者findViewById手動設置數據,實現UI與功能的解耦。
用MVVM實現上面的例子
首先在app/build.gradle配置
android {
...
dataBinding {
enabled = true
}
}
在原來的layout文件上面添加layout標籤,把原來的佈局放到裏面。注意是小寫的layout哦!不然使用data標籤會報錯的。
<?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">
<data>
<import type="android.view.View"/>
<variable
name = "weatherModel" type = "com.act.androidarchtest.mvvm.model.WeatherModelImpl"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_weather_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{weatherModel.querySuccess ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textSize="28sp"
android:text="@{weatherModel.weatherInfo}"
/>
<Button
android:id="@+id/bt_query"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/tv_weather_info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/bt_query_text"
android:onClick="@{() -> weatherModel.getWeather()}"
/>
</android.support.constraint.ConstraintLayout>
</layout>
修改WeatherModelImpl類,取消原來的接口回調,使用觀察者模式。
public class WeatherModelImpl implements IWeatherModel
{
public final ObservableBoolean querySuccess = new ObservableBoolean(false);
public final ObservableField<String> weatherInfo = new ObservableField<>();
private Call mCall;
public WeatherModelImpl() { }
@Override
public void getWeather() {
String url = "http://www.weather.com.cn/data/sk/101280101.html";
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.build();
mCall = okHttpClient.newCall(request);
mCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
querySuccess.set(false);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = response.body().string();
Gson gson = new Gson();
WeatherBean weather = gson.fromJson(body, WeatherBean.class);
querySuccess.set(true);
weatherInfo.set(weather.toString());
}
});
}
@Override
public void onCancel() {
if (null != mCall) {
mCall.cancel();
}
}
}
由於之前的layout文件已經直接綁定了weatherModel.weatherInfo數據,所以一旦weatherInfo數據發生變化,綁定數據的View視圖則會自動刷新UI。
View層代碼
public class MvvmActivity extends AppCompatActivity {
private WeatherModelImpl weatherModel;
private ActivityMvvmBinding mMvvmBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMvvmBinding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
weatherModel = new WeatherModelImpl();
mMvvmBinding.setWeatherModel(weatherModel);
}
@Override
protected void onDestroy() {
super.onDestroy();
weatherModel.onCancel();
}
}
通過上面代碼可以看出,用了MVVM之後,Activity整體的代碼量少了很多,不用處理View刷新問題。
上面例子的全部代碼 GitHub傳送門