Android应用架构之MVP模式

前言

早期的Android应用开发中,Activity/Fragment承担了过多的职责,它们不仅负责了应用界面的显示,而且负责了业务逻辑的处理。这样一来,Activity/Fragment很容易就变得臃肿、复杂,造成应用难以测试、维护和扩展。随着Android应用开发技术的不断发展和成熟,Android应用架构的设计得到了越来越多开发人员的关注和重视。目前,Android的应用架构主要有MVC、MVP和MVVM模式,本文将介绍一下MVP模式。

相关知识

  • Android应用架构之MVP模式
  • Android应用架构之MVVM模式

学习项目

  • MVVM模式开源学习项目——玩Android客户端

MVC模式

MVP模式是从经典的MVC模式演变而来的,所以先来了解一下MVC模式。

MVC(Model-View-Controller)模式的结构如下图所示:

MVC模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,Controller层主要负责业务逻辑的处理。在MVC模式中,Model层和View层可以直接通信。

MVP模式

MVP(Model-View-Presenter)模式的结构如下图所示:

MVP模式与MVC模式一样,也将应用分为三层,并且各个对应的层的职责相似:

  • Model层,主要负责数据的提供。Model层提供业务逻辑的数据结构(比如,实体类),提供数据的获取(比如,从本地数据库或者远程网络获取数据),提供数据的存储。
  • View层,主要负责界面的显示。View层还负责转发用户的交互事件。View层以接口的方式持有Presenter层的引用,当交互事件发生时,View层通知Presenter层进行相应的业务逻辑处理。
  • Presenter层,主要负责业务逻辑的处理。Presenter层还负责充当中间人,实现Model层和View层之间的间接通信。例如,Presenter层接收View层的加载网络数据请求,将请求转发给Model层处理,同时监听Model层的处理结果,最后将结果反馈给View层,View层实现界面的刷新。

MVP模式与MVC模式最大的区别在于:Model层和View层不能直接通信。这样降低了耦合,任何一方代码的改变不会影响到另外一方。

MVP模式主要有如下优点:

  • 降低了耦合。分离了Model层和View层;分离了视图操作和业务逻辑。
  • 便于维护和扩展。各个层职责单一,结构清晰。
  • 便于测试。操作视图的View层和处理业务逻辑的Presenter层被抽象成接口,方便进行单元测试。

MVP模式主要有如下缺点:

  • View层的接口、Presenter层的接口和Presenter层的实现等增加了应用的代码量。
  • 业务逻辑复杂的情况下,Presenter层会变得非常臃肿,不易维护。

例子

下面举一个简单的例子来实践MVP模式。完整的项目代码可以去GitHub上查看:

https://github.com/chongyucaiyan/MVPDemo

例子实现的主要功能是:点击按钮网络查询天气,查询成功后在界面上显示天气信息。主界面如下图所示:

Android应用架构之MVP模式_第1张图片

MVP模式的代码组织结构建议按照 业务功能 进行划分,具体操作是:每个业务功能独立一个包存放;每个业务功能包下面再按Model、View、Presenter分包存放,所有的Model存放在model包下面,所有的Activity和Fragment存放在activity包下面,所有的Presenter存放在presenter包下面。该例子比较简单,只有一个weather业务功能模块,最终的代码组织结构如下图所示:

Android应用架构之MVP模式_第2张图片

编写Model

查询杭州天气的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

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的方法进行网络查询天气。

编写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层主要负责业务逻辑的处理。各个层职责单一,结构清晰,应用可以很方便地进行测试、维护和扩展。

参考

  • https://github.com/googlesamples/android-architecture
  • https://github.com/googlesamples/android-architecture/tree/todo-mvp/

你可能感兴趣的:(Android架构)