早期的Android应用开发中,Activity/Fragment承担了过多的职责,它们不仅负责了应用界面的显示,而且负责了业务逻辑的处理。这样一来,Activity/Fragment很容易就变得臃肿、复杂,造成应用难以测试、维护和扩展。随着Android应用开发技术的不断发展和成熟,Android应用架构的设计得到了越来越多开发人员的关注和重视。目前,Android的应用架构主要有MVC、MVP和MVVM模式,本文将介绍一下MVP模式。
MVP模式是从经典的MVC模式演变而来的,所以先来了解一下MVC模式。
MVC(Model-View-Controller)模式的结构如下图所示:
MVC模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,Controller层主要负责业务逻辑的处理。在MVC模式中,Model层和View层可以直接通信。
MVP(Model-View-Presenter)模式的结构如下图所示:
MVP模式与MVC模式一样,也将应用分为三层,并且各个对应的层的职责相似:
MVP模式与MVC模式最大的区别在于:Model层和View层不能直接通信。这样降低了耦合,任何一方代码的改变不会影响到另外一方。
MVP模式主要有如下优点:
MVP模式主要有如下缺点:
下面举一个简单的例子来实践MVP模式。完整的项目代码可以去GitHub上查看:
https://github.com/chongyucaiyan/MVPDemo
例子实现的主要功能是:点击按钮网络查询天气,查询成功后在界面上显示天气信息。主界面如下图所示:
MVP模式的代码组织结构建议按照 业务功能 进行划分,具体操作是:每个业务功能独立一个包存放;每个业务功能包下面再按Model、View、Presenter分包存放,所有的Model存放在model包下面,所有的Activity和Fragment存放在activity包下面,所有的Presenter存放在presenter包下面。该例子比较简单,只有一个weather业务功能模块,最终的代码组织结构如下图所示:
查询杭州天气的URL为:
http://www.weather.com.cn/data/cityinfo/101210101.html
访问该URL将返回一串JSON字符串,如下所示:
{"weatherinfo":{"city":"杭州","cityid":"101210101","temp1":"5℃","temp2":"20℃","weather":"晴转多云","img1":"n0.gif","img2":"d1.gif","ptime":"18:00"}}
按照此JSON字符串,可以编写相应的实体类。WeatherData类的代码如下所示:
public class WeatherData {
private WeatherInfo weatherinfo;
public WeatherInfo getWeatherinfo() {
return weatherinfo;
}
public void setWeatherinfo(WeatherInfo weatherinfo) {
this.weatherinfo = weatherinfo;
}
}
WeatherInfo类的代码如下所示:
public class WeatherInfo {
private String city;
private String cityid;
private String temp1;
private String temp2;
private String weather;
private String img1;
private String img2;
private String ptime;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCityid() {
return cityid;
}
public void setCityid(String cityid) {
this.cityid = cityid;
}
public String getTemp1() {
return temp1;
}
public void setTemp1(String temp1) {
this.temp1 = temp1;
}
public String getTemp2() {
return temp2;
}
public void setTemp2(String temp2) {
this.temp2 = temp2;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public String getImg1() {
return img1;
}
public void setImg1(String img1) {
this.img1 = img1;
}
public String getImg2() {
return img2;
}
public void setImg2(String img2) {
this.img2 = img2;
}
public String getPtime() {
return ptime;
}
public void setPtime(String ptime) {
this.ptime = ptime;
}
}
View和Presenter都是通过接口进行访问的。我们定义一个QueryWeatherContract契约接口,在该接口中统一放置View和Presenter接口,这样便于统一管理和查找。View和Presenter接口的代码如下所示:
public interface QueryWeatherContract {
interface View extends BaseView {
void showLoading(boolean show);
void showLoadingFailure(boolean show);
void showWeatherInfo(boolean show);
void setCity(String city);
void setCityId(String cityId);
void setTemp1(String temp1);
void setTemp2(String temp2);
void setWeather(String weather);
void setReleaseTime(String time);
void setQueryWeatherBtnEnabled(boolean enabled);
}
interface Presenter extends BasePresenter {
void queryWeather();
void cancelRequest();
}
}
View接口只进行界面更新操作,而Presenter接口只进行业务逻辑操作。
接下来编写QueryWeatherActivity,它实现了View接口。具体的代码如下所示:
public class QueryWeatherActivity extends AppCompatActivity implements QueryWeatherContract.View {
// Presenter
private QueryWeatherContract.Presenter mPresenter;
private ViewGroup mVgWeatherInfo;
private TextView mTvCity;
private TextView mTvCityId;
private TextView mTvTemp1;
private TextView mTvTemp2;
private TextView mTvWeather;
private TextView mTvReleaseTime;
private TextView mTvQueryFailure;
private Button mBtnQueryWeather;
private ProgressBar mPbProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_query_weather);
initContentView();
// 创建Presenter
mPresenter = new QueryWeatherPresenter(this);
}
private void initContentView() {
mVgWeatherInfo = findViewById(R.id.vg_weather_info);
mTvCity = findViewById(R.id.tv_city_value);
mTvCityId = findViewById(R.id.tv_city_id_value);
mTvTemp1 = findViewById(R.id.tv_temp1_value);
mTvTemp2 = findViewById(R.id.tv_temp2_value);
mTvWeather = findViewById(R.id.tv_weather_value);
mTvReleaseTime = findViewById(R.id.tv_time_value);
mTvQueryFailure = findViewById(R.id.tv_query_failure);
mBtnQueryWeather = findViewById(R.id.btn_query_weather);
mPbProgress = findViewById(R.id.pb_progress);
mBtnQueryWeather.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.queryWeather();
}
});
}
@Override
public void showLoading(boolean show) {
mPbProgress.setVisibility(show ? View.VISIBLE : View.GONE);
}
@Override
public void showLoadingFailure(boolean show) {
mTvQueryFailure.setVisibility(show ? View.VISIBLE : View.GONE);
}
@Override
public void showWeatherInfo(boolean show) {
mVgWeatherInfo.setVisibility(show ? View.VISIBLE : View.GONE);
}
@Override
public void setCity(String city) {
mTvCity.setText(city);
}
@Override
public void setCityId(String cityId) {
mTvCityId.setText(cityId);
}
@Override
public void setTemp1(String temp1) {
mTvTemp1.setText(temp1);
}
@Override
public void setTemp2(String temp2) {
mTvTemp2.setText(temp2);
}
@Override
public void setWeather(String weather) {
mTvWeather.setText(weather);
}
@Override
public void setReleaseTime(String time) {
mTvReleaseTime.setText(time);
}
@Override
public void setQueryWeatherBtnEnabled(boolean enabled) {
mBtnQueryWeather.setEnabled(enabled);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 取消请求
mPresenter.cancelRequest();
}
}
QueryWeatherActivity以接口的方式引用Presenter,在它的onCreate()方法中创建了Presenter。当点击查询天气按钮时,QueryWeatherActivity将调用Presenter的方法进行网络查询天气。
接下来编写QueryWeatherPresenter,它实现了Presenter接口。具体的代码如下所示:
public class QueryWeatherPresenter implements QueryWeatherContract.Presenter {
private static final String TAG = "QueryWeatherPresenter";
// View
private QueryWeatherContract.View mView;
private Call<WeatherData> mCall;
public QueryWeatherPresenter(QueryWeatherContract.View view) {
mView = view;
}
@Override
public void queryWeather() {
mView.showLoading(true);
mView.showWeatherInfo(false);
mView.showLoadingFailure(false);
mView.setQueryWeatherBtnEnabled(false);
mCall = RetrofitManager.get()
.create(QueryWeatherRequest.class)
.queryWeather();
mCall.enqueue(new Callback<WeatherData>() {
@Override
public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
WeatherInfo weatherInfo = response.body().getWeatherinfo();
mView.setCity(weatherInfo.getCity());
mView.setCityId(weatherInfo.getCityid());
mView.setTemp1(weatherInfo.getTemp1());
mView.setTemp2(weatherInfo.getTemp2());
mView.setWeather(weatherInfo.getWeather());
mView.setReleaseTime(weatherInfo.getPtime());
mView.showWeatherInfo(true);
mView.showLoading(false);
mView.setQueryWeatherBtnEnabled(true);
}
@Override
public void onFailure(Call<WeatherData> call, Throwable t) {
if (call.isCanceled()) {
Log.i(TAG, "call is canceled.");
} else {
mView.showLoadingFailure(true);
mView.showLoading(false);
mView.setQueryWeatherBtnEnabled(true);
}
}
});
}
@Override
public void cancelRequest() {
if (mCall != null) {
mCall.cancel();
}
}
}
QueryWeatherPresenter以接口的方式引用View,在它的构造方法中注入了View的实例。当网络查询天气成功后,QueryWeatherPresenter将调用View的方法来更新界面。
MVP架构模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,Presenter层主要负责业务逻辑的处理。各个层职责单一,结构清晰,应用可以很方便地进行测试、维护和扩展。