MVP架构的简单使用

版权声明:转载请标明出处 https://blog.csdn.net/panyongjie2577 https://blog.csdn.net/panyongjie2577/article/details/88619303

前言

最近的招聘面试过程中,发现很多工作了很多年的面试者任然在使用MVC架构在开发相对复杂的app应用,即使是使用了MVP架构的人往往也对整体架构的分离不是很清晰,对于架构的优点和可扩展的方向不甚了解。今天就准备一篇博客来简述一下什么是MVP,如何实现一个简单的MVP架构,使用MVP有什么好处。

参考资料

– Android MVP开发模式 google 官方Mvp架构详解
https://blog.csdn.net/jungle_pig/article/details/65626469

– 谷歌官方架构
https://github.com/googlesamples/android-architecture

概述

MVP模式 概念

之前有一个MVC模式: Model-View-Controller.
MVC模式 有两个主要的缺点: 首先, View持有Controller和Model的引用; 第二, 它没有把对UI逻辑的操作限制在单一的类里, 这个职能被Controller和View或者Model共享.
所以后来提出了MVP模式来克服这些缺点.

MVP(Model-View-Presenter)模式:

  • Model: 数据层. 负责与网络层和数据库层的逻辑交互.
  • View: UI层. 显示数据, 并向Presenter报告用户行为.
  • Presenter: 从Model拿数据, 应用到UI层, 管理UI的状态, 决定要显示什么, 响应用户的行为.

MVP模式的最主要优势就是耦合降低, Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity, Fragment等关联, 便于写单元测试.

基本的Model-View-Presenter架构

整体结构分为以下几块:

  • 控制器,通常由Activity来承担
  • 视图,通常使用Fragment来实现
  • Presenter,用于承担Fragment视图的数据操作和复杂的业务流程,并在执行完成后通知View进行视图更新操作。
  • Contract,定义View和Presenter中应该分别实现的接口

每个功能都有:

  • 一个定义View和Presenter接口的Contract接口;
  • 一个Activity用来管理fragment的创建;
  • 一个实现了View接口的Fragment, 并提供presenter的创建;
  • 一个实现了Presenter接口的presenter.

在这里插入图片描述

环境准备

引用的第三方框架

项目中需要使用butterknife来实现对视图的注解绑定,在app工程下的build.gradle文件中的dependencies配置中添加如下配置

implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

在整个工程下的build.gradle中添加

 classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1' 

模型类

public class WeatherBean {
    //最低温度
    private int minTempetarute;

    //最高温度
    private int maxTempetarute;

    //城市名称
    private String cityName;

    public int getMinTempetarute() {
        return minTempetarute;
    }

    public void setMinTempetarute(int minTempetarute) {
        this.minTempetarute = minTempetarute;
    }

    public int getMaxTempetarute() {
        return maxTempetarute;
    }

    public void setMaxTempetarute(int maxTempetarute) {
        this.maxTempetarute = maxTempetarute;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }
}

数据适配器

public class WeatherListAdapter extends BaseAdapter {
    private List<WeatherBean> dataList =
                   new ArrayList<WeatherBean>();
    private Context mContext;

    public WeatherListAdapter(Context context, 
                              List<WeatherBean> list)
    {
        mContext = context;
        if(null != list) {
            dataList.addAll(list);
        }
    }

    public List<WeatherBean> getDataSource()
    {
        return dataList;
    }

    @Override
    public int getCount() {
        return dataList.size();
    }

    @Override
    public Object getItem(int position) {
        return dataList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, 
        View convertView, ViewGroup parent) {
        
        ViewHolder holder;
        if(null == convertView)
        {
            convertView = LayoutInflater.from(mContext).
               inflate(R.layout.weather_list_item, null);
               
            holder = new ViewHolder(convertView);
            
            holder.cityNameTextView = convertView.
               findViewById(R.id.cityNameTextView);
            holder.temperatureRangeTextView = convertView.
               findViewById(R.id.temperatureRangeTextView);

            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) convertView.getTag();
        }

        WeatherBean weatherBean = dataList.get(position);
        
        holder.cityNameTextView
           .setText(weatherBean.getCityName());
        holder.temperatureRangeTextView
           .setText(weatherBean.getMinTempetarute()
           + " ~~ " + weatherBean.getMaxTempetarute() + " ℃");

        return convertView;
    }

    class ViewHolder
    {
        @BindView(R.id.cityNameTextView)
        TextView cityNameTextView;

        @BindView(R.id.temperatureRangeTextView)
        TextView temperatureRangeTextView;

        public ViewHolder(View view)
        {
            ButterKnife.bind(this, view);
        }
    }
}

布局文件

MainActivity的布局文件activity_main.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout   
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:id="@+id/containerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" />

</LinearLayout>

adapter中使用的weather_list_item.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/cityNameTextView"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:textSize="16dp"
        android:layout_margin="20dp"
        android:textColor="#ffffff"/>
    <TextView
        android:id="@+id/temperatureRangeTextView"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:textSize="14dp"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="20dp"
        android:textColor="#ffffff"/>
</LinearLayout>

简单示例搭建

创建基础接口

/**
 * 视图基本操作接口
 *
 * */
public interface IBaseView<T> {
    /**
    * 是否已经添加到父视图中
    * */
    public boolean isAddedToParentView();

    /**
     * 获取当前的上下文
     * */
    public Context getCurrentContext();
}

public interface IBasePresenter {

}

创建Contract接口

Contract接口主要用于分别定义ViewPresenter的行为,然后自定义的Presenter类实现Contract.Presenter接口,Fragment或Activity实现Contract.View接口。

public interface FirstFragmentContract {

    //定义视图操作行为
    interface View extends IBaseView<Presenter>
    {
        public void onFirstLoadDataArrived(List list);
        public void onLoadMoreDataArrived(List list);
        public void onRefreshError();
        public void onLoadMoreError();
    }

    //定义代理人操作行为
    interface Presenter
    {
        public void refershData();
        public void loadMoreData();
    }
}

创建Presenter类

public class FirstPresenter 
                implements FirstFragmentContract.Presenter {
  
    private FirstFragmentContract.View mView;
    private int page = 1;

    public FirstPresenter(FirstFragmentContract.View view)
    {
        mView = view;
    }

    @Override
    public void refershData() {
        if(null == mView){
            Log.i("Debug",  "mView is null");
            mView.onRefreshError();
            return;
        }

        if(page > 1){
            mView.onRefreshError();
            return;
        }

        mView.onFirstLoadDataArrived(getWeatherList(page));
        page++;
    }

    @Override
    public void loadMoreData() {
        if(null == mView){
            Log.i("Debug",  "mView is null");
            return;
        }

        if(page > 3){
            mView.onLoadMoreError();
            return;
        }

        mView.onLoadMoreDataArrived(getWeatherList(page));
        page++;
    }

    private List getWeatherList(int page){
        ArrayList<WeatherBean> dataList = new ArrayList<>();
        for(int i = 0; i < 10; i++)
        {
            WeatherBean weatherBean = new WeatherBean();
            weatherBean.setCityName("南京" + page);

            int minTempetarute = (int)(Math.random() * 30);
            int maxTempetarute = minTempetarute +
		             (int)(Math.random() * 30);
            weatherBean.setMinTempetarute(minTempetarute);
            weatherBean.setMaxTempetarute(maxTempetarute);

            dataList.add(weatherBean);
        }
        return dataList;
    }
}

创建Fragment类

public class FirstFragment extends Fragment 
               implements FirstFragmentContract.View{
               
    FirstPresenter firstPresenter;

    private View view;

    @BindView(R.id.weatherListView)
    ListView weatherListView;

    @BindViews({R.id.refreshDataButton, R.id.loadDataButton})
    List<Button> buttonList;

    private WeatherListAdapter weatherListAdapter;
    Unbinder unbinder;

    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             Bundle savedInstanceState)
    {
        super.onCreateView(inflater, container, 
                           savedInstanceState);

        if(null == view)
        {
            firstPresenter = new FirstPresenter(this);
            view = inflater.inflate(
                           R.layout.first_fragment, null);

            //这里必须进行绑定
            unbinder = ButterKnife.bind(this, view);

            weatherListAdapter = 
                new WeatherListAdapter(getContext(), null);
            weatherListView.setAdapter(weatherListAdapter);

            firstPresenter.refershData();
        }
        return  view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if(null != unbinder){
            unbinder.unbind();
        }
    }

    @Override
    public void onFirstLoadDataArrived(List list) {
        weatherListAdapter.getDataSource().addAll(0, list);
        weatherListAdapter.notifyDataSetChanged();
    }

    @Override
    public void onLoadMoreDataArrived(List list) {
        weatherListAdapter.getDataSource().addAll(list);
        weatherListAdapter.notifyDataSetChanged();
    }

    @Override
    public void onRefreshError() {
        Toast.makeText(getContext(), "未发现新数据" , 
			        Toast.LENGTH_LONG).show();
        buttonList.get(0).setEnabled(false);
    }

    @Override
    public void onLoadMoreError() {
        Toast.makeText(getContext(), "已加载完所有数据" , 
				        Toast.LENGTH_LONG).show();
        buttonList.get(1).setEnabled(false);
    }

    @Override
    public boolean isAddedToParentView() {
        return true;
    }

    @Override
    public Context getCurrentContext() {
        return getContext();
    }

    @OnClick({R.id.refreshDataButton, R.id.loadDataButton})
    public void onViewClicked(View v){
        switch (v.getId())
        {
            case R.id.refreshDataButton:
                firstPresenter.refershData();
                break;
            case R.id.loadDataButton:
                firstPresenter.loadMoreData();
                break;
        }
    }
}

创建Activity

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.containerLayout)
    LinearLayout containerLayout;

    Unbinder unbinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        unbinder = ButterKnife.bind(this);

        FirstFragment firstFragment = new FirstFragment();

        FragmentTransaction fragmentTransaction = 
           getFragmentManager().beginTransaction();
        fragmentTransaction.add(
           R.id.containerLayout, firstFragment);
        fragmentTransaction.commit();
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();

        if(null != unbinder)
        {
            unbinder.unbind();
        }
    }
}

总结

MVP通过将整体结构分为: 1. Model层,用户进行数据封装和操作 2. View层,通常使用Fragment来实现视图的加载与操作,将fragment添加到activity中进行管理。 3. Presenter层,作为视图层和数据业务处理层之间的中间代理人,通常作为业务层来进行代码分离和解耦。4. Contract接口层,预先定义数据和视图接口规范数据层和视图层的操作行为。

每一层都队里完成自己的工作,通过接口进行回调,大大降低了代码的耦合度,代码层级分明加强了程序的可维护性,方便业务拓展。
后期还可以通过加入RxJava、Retrofit、Dagger来进一步降级代码耦合性,增加代码的可维护性。

猜你喜欢

转载自blog.csdn.net/panyongjie2577/article/details/88619303