一、MVC
MVP是由MVC演变过来的,所以在学习MVP之前我们先看看MVC。
MVC介绍部分转载自天码营-《Android App的设计架构:MVC,MVP,MVVM与架构经验谈》。地址为https://www.tianmaying.com/tutorial/AndroidMVC
1、MVC简介
MVC全名是Model View Controller,如图,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。
2、Android中的MVC
Android中界面部分也采用了当前比较流行的MVC框架,在Android中:
1)、视图层(View)
一般采用XML文件进行界面的描述,这些XML可以理解为AndroidApp的View。使用的时候可以非常方便的引入。同时便于后期界面的修改。逻辑中与界面对应的id不变化则代码不用修改,大大增强了代码的可维护性。
2)、控制层(Controller)
Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
3)、模型层(Model)
我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。就是应用程序中二进制的数据。
3、MVC例子
1)、Controller控制器&View
代码如下:
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
private WeatherModel weatherModel;
private EditText cityNOInput;
private TextView city;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
weatherModel = new WeatherModelImpl();
initView();
}
//初始化View
private void initView() {
cityNOInput = findView(R.id.et_city_no);
city = findView(R.id.tv_city);
...
findView(R.id.btn_go).setOnClickListener(this);
}
//显示结果
public void displayResult(Weather weather) {
WeatherInfo weatherInfo = weather.getWeatherinfo();
city.setText(weatherInfo.getCity());
...
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
break;
}
}
// 传给Model的OnWeatherListener中的方法
@Override
public void onSuccess(Weather weather) {
displayResult(weather);
}
// 传给Model的OnWeatherListener中的方法
@Override
public void onError() {
Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
}
private T findView(int id) {
return (T) findViewById(id);
}
}
从上面代码可以看到,Activity持有了WeatherModel模型的对象,当用户有点击Button交互的时候,Activity作为Controller控制层读取View视图层EditTextView的数据,然后向Model模型发起数据请求,也就是调用WeatherModel对象的方法 getWeather()方法。当Model模型处理数据结束后,通过接口OnWeatherListener通知View视图层数据处理完毕,View视图层该更新界面UI了。然后View视图层调用displayResult()方法更新UI,这时Activity又充当View层。至此,整个MVC框架流程就在Activity中体现出来了。上面流程中Activity充当了Controller和View,这两层没有实现解耦。
2)、Model
代码如下:
public interface WeatherModel {
void getWeather(String cityNumber, OnWeatherListener listener);
}
................
public class WeatherModelImpl implements WeatherModel {
/*这部分代码范例有问题,网络访问不应该在Model中,应该把网络访问换成从数据库读取*/
@Override
public void getWeather(String cityNumber, final OnWeatherListener listener) {
/*数据层操作*/
VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
Weather.class, new Response.Listener<weather>() {
@Override
public void onResponse(Weather weather) {
if (weather != null) {
listener.onSuccess(weather);
} else {
listener.onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onError();
}
});
}
}
以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类。controller控制器activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI 。至此,Activity就将View视图显示和Model模型数据处理隔离开了。activity担当contronller完成了model和view之间的协调作用。
至于这里为什么要定义接口WeatherModel?你考虑下这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,你怎么解决问题?难道是修改 getWeather()方法的实现? 这样修改不仅破坏了以前的代码,而且还不利于维护, 考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决着一个问题,我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel,重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式。Activity调用代码无需要任何修改。
二、MVC的缺点
controller和view(除开xml中定义的view)都在Activity中实现,controller和view在android中无法做到彻底分离。View 和 Controller 层的耦合,导致 Activity 或者 Fragment 很臃肿,代码量很大。
三、MVP
大家都在Activity里面处理逻辑因为太方便了。 但是这样之后,我们Activity的职责太多了,耦合也严重,所以我们就想着怎么能给Activity减负,同时把耦合也降下来,所以就想找个哥们来替Activity分担的责任,大家最后都只有一个责任,各层关系也相对好理解。
既然Activty这么愿意和View搞到一起,那么就让他们俩在一起,共同负责View,我们在招聘一个职业经理人(Presenter)来处理事务,有啥事都找Presenter,你看着多方便啊,MVP是一个真正意义上的隔离View的细节和复杂性的模式。
1、简介
MVP 全称:Model-View-Presenter ;MVP 是从经典的模式 MVC 演变而来,它们的基本思想有相通的地方:Controller/Presenter 负责逻辑的处理,Model 提供数据,View 负责显示。
当 View 需要更新数据时,首先去找 Presenter,然后 Presenter 去找 Model 请求数据,Model 获取到数据之后通知 Presenter,Presenter 再通知 View 更新数据,这样 Model 和 View 就不会直接交互了,所有的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。很显然,Presenter 必须同时持有 View 和 Model 的对象的引用,才能在它们之间进行通信。
2、mvp例子
下面例子使用MVP实现天气查询的功能。效果图如下:
1)、网络相关
网络请求采用Retrofit+RxJava实现,具体实现这里不作演示。我们先定义后台返回数据的实体类,已省略getter、
setter方法。代码如下:
public class WeatherInfo {
private String reason;
private ResultBean result;
private int error_code;
public static class ResultBean {
private String city;
private RealtimeBean realtime;
private List<FutureBean> future;
public static class RealtimeBean {
private String temperature;
private String humidity;
private String info;
private String wid;
private String direct;
private String power;
private String aqi;
}
public static class FutureBean {
private String date;
private String temperature;
private String weather;
private WidBean wid;
private String direct;
public static class WidBean {
private String day;
private String night;
}
}
}
}
2)、定义Model
这里提供一个GetRemoteListener,当网络请求完成时通过GetRemoteListener回调给调用者。
public interface GetRemoteListener<T> {
void onSuccess(T t);
void onError(String msg);
}
public class WeatherModel {
public void getWeatherInfo(String city, final GetRemoteListener<WeatherInfo> listener) {
HttpHelper.getWeatherInfoApi(city).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherInfo>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(WeatherInfo value) {
listener.onSuccess(value);
}
@Override
public void onError(Throwable e) {
listener.onError(e.getMessage());
}
@Override
public void onComplete() {
}
});
}
}
3)、定义接口
View的接口定义一个创建Presenter的方法,因为View中持有Presenter,所以View中都要创建Presenter,代码如下:
public interface BaseView {
void createPresenter();
}
Presenter的接口中定义一个start方法,表示Presenter开始时处理的内容,代码如下:
public interface BasePresenter {
void start();
}
4)、定义contract
每个View和Presenter都有一个接口,定义需要实现的功能。使用contract将View和Presenter的接口定义在一个类中,这样可以减少类和接口的总数。代码如下:
public class WeatherContract {
public interface Presenter extends BasePresenter{
void getWeatherInfo(String city);
}
public interface View extends BaseView{
/**
* 显示正在加载
* @param isShow
*/
void showLoading(boolean isShow);
/**
* 显示查询的天气信息
* @param info
*/
void showWeatherInfo(String info);
/**
* 清除显示的天气信息
*/
void clearWeatherInfo();
/**
* 显示错误提示信息
* @param info
*/
void showErrInfo(String info);
}
}
5)、实现View
View有两部分,一部分是xml中定义的,一部分是activity中定义的。xml代码如下:
<LinearLayout 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"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".view.WeatherActivity">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_city"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="城市"
android:inputType="textPersonName" />
<Button
android:id="@+id/btn_support_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="支持城市查询"/>
</LinearLayout>
<Button
android:id="@+id/btn_request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="获取天气" />
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="@android:color/holo_orange_dark"
android:gravity="center_horizontal"
android:padding="20dp"
android:visibility="visible" />
<LinearLayout
android:id="@+id/ll_err"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="@android:color/darker_gray"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/error" />
<TextView
android:id="@+id/tv_err_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="center_horizontal" />
</LinearLayout>
</LinearLayout>
Activity实现了View接口并且实现点击事件的处理,代码如下:
public class WeatherActivity extends AppCompatActivity implements WeatherContract.View, View.OnClickListener {
private MaterialDialog dialog;
private TextView tvResult;
private LinearLayout llErrInfo;
private TextView tvErrInfo;
private EditText etCity;
private WeatherContract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
initView();
setListener();
createPresenter();
}
private void initView() {
initDialog();
tvResult = findViewById(R.id.tv_result);
llErrInfo = findViewById(R.id.ll_err);
tvErrInfo = findViewById(R.id.tv_err_info);
etCity = findViewById(R.id.et_city);
}
private void setListener() {
findViewById(R.id.btn_request).setOnClickListener(this);
findViewById(R.id.btn_support_city).setOnClickListener(this);
}
private void initDialog() {
MaterialDialog.Builder builder = new MaterialDialog.Builder(this);
dialog = builder.content("拼命加载中...")
.progress(true, 0)
.build();
}
@Override
public void createPresenter() {
presenter = new WeatherPresenter(this);
}
@Override
public void showLoading(boolean isShow) {
if (isShow) {
dialog.show();
} else {
dialog.dismiss();
}
}
@Override
public void showWeatherInfo(String info) {
showWeatherOrErrInfo(true);
tvResult.setText(info);
}
@Override
public void clearWeatherInfo() {
showWeatherOrErrInfo(true);
tvResult.setText("");
}
@Override
public void showErrInfo(String info) {
showWeatherOrErrInfo(false);
tvErrInfo.setText(info);
}
private void showWeatherOrErrInfo(boolean isShowWeather) {
if (isShowWeather) {
tvResult.setVisibility(View.VISIBLE);
llErrInfo.setVisibility(View.GONE);
} else {
tvResult.setVisibility(View.GONE);
llErrInfo.setVisibility(View.VISIBLE);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_request:
presenter.getWeatherInfo(etCity.getText().toString());
break;
case R.id.btn_support_city:
startActivity(new Intent(this, SupportActivity.class));
break;
}
}
}
6)、Presenter
Preaenter处于中间层,所以Presenter应该拥有View和Model。Presenter实现接口中的方法,实现逻辑处理功能,代码如下:
public class WeatherPresenter implements WeatherContract.Presenter {
private WeatherModel model;
private WeatherContract.View view;
public WeatherPresenter(WeatherContract.View view) {
this.view = view;
model = new WeatherModel();
}
@Override
public void getWeatherInfo(String city) {
if(TextUtils.isEmpty(city)){
view.showErrInfo("城市名不能为空");
return;
}
view.showLoading(true);
view.clearWeatherInfo();
model.getWeatherInfo(city, new GetRemoteListener<WeatherInfo>() {
@Override
public void onSuccess(WeatherInfo weatherInfo) {
view.showLoading(false);
if(weatherInfo.getError_code() == 0) {
String format = ((Context) view).getString(R.string.weather_show_format);
WeatherInfo.ResultBean.RealtimeBean realtime = weatherInfo.getResult().getRealtime();
String showReslut = String.format(format, realtime.getTemperature(), realtime.getHumidity(), realtime.getAqi(),
realtime.getDirect(), realtime.getPower());
view.showWeatherInfo(showReslut);
}else{
view.showErrInfo(weatherInfo.getReason());
}
}
@Override
public void onError(String msg) {
view.showLoading(false);
view.showErrInfo(msg);
}
});
}
@Override
public void start() {
}
}