关于MVP和MVVM实战对比的一点感想

    在项目还没开始之前,准备使用MVP和MVVM之间犹豫不决时,决定写个demo来比较两者之间的异同点和一点自己的感想,同时会对比之间的优劣势,在之前先谢谢陪你唠嗑的博主,看了他的demo才决定来做的。

    下面直接开搞,首先接口类,最基本的,我定义的是APIservice,代码如下:

public interface ApiService {
    @GET(UrlConstant.URL_PATH)
    Observable getNewsData();
    @GET(UrlConstant.URL_PATH)
    Observable getNewsData( @Query("page")int page);
}
public static final String URL_NEWS = "https://news-at.zhihu.com/api/4/themes";
public static final String URL_BASE = "https://news-at.zhihu.com/";
public static final String URL_PATH = "api/4/themes";

    为什么有两个一样的方法,因为我想到做分页需要传入一个分页参数,所以直接分开写,下面是网络封装部分,这部分大家封装根据自己需要,我因为是demo所以大概就封装了一下。

public class HttpUtils { //网络工具类
    public static  final  int DEFAULT_TIME = 8;

    public static OkHttpClient client = new OkHttpClient().newBuilder()
            .connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS)
            .readTimeout(DEFAULT_TIME,TimeUnit.SECONDS)
            .writeTimeout(DEFAULT_TIME,TimeUnit.SECONDS)
            .build();

    public static HttpUtils httpUtils;
    public static Retrofit retrofit;
    public static ApiService service;


    public   synchronized static ApiService getService(){
        if (retrofit == null){
            retrofit = new Retrofit.Builder()
                    .baseUrl(UrlConstant.URL_BASE)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
            service = retrofit.create(ApiService.class);
        }
        return service;
    }
    public static Observable getNewsData( int page) {
        return getService().getNewsData(page);
    }
    public static Observable getNewsData() {
        return getService().getNewsData();
    }
}

上述是网络层,下面给出返回的实例对象信息类

public class NewsBean {

    private int limit;
    private List subcribed;
    private List others;

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public List getSubcribed() {
        return subcribed;
    }

    public void setSubcribed(List subcribed) {
        this.subcribed = subcribed;
    }

    public List getOthers() {
        return others;
    }

    public void setOthers(List others) {
        this.others = others;
    }

    public static class OthersBean{
        private int color;
        private String thumbnail;
        private String description;
        private int id;
        private String name;

        public int getColor() {
            return color;
        }

        public void setColor(int color) {
            this.color = color;
        }

        public String getThumbnail() {
            return thumbnail;
        }

        public void setThumbnail(String thumbnail) {
            this.thumbnail = thumbnail;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

准备工作完成,下面着重来说说MVP结构,目前的需求是在列表上展示信息,刷新加载更多功能,这是一个很普遍的需求。

首先  M层,MVP中M层的任务就是获取网络数据,交给P层,P层将数据交给V层去显示,同时V层交互告知P层,需要什么数据,P层就会通知M层去获取。举个很简单的例子,我们区参观吃饭,坐下点菜,服务员会把菜单给我们让我们点菜,点好之后,服务员会告知后厨,需要做什么菜,后厨把菜做好之后,服务员会把菜端给我们,这一次完成了  V--->P---->m,m--->p----v的整个过程,这就是三层之间的关系。

     好,接下来我们首先定义V层,第一步,我们定义一个接口BaseView,

public interface BaseView {
   

    
}

这个接口是干嘛的呢,当我们需要请求数据时有个进度条,或者当请求出错我们需要展示一个错误页面,或者提示用户,我们可以在这里面全局控制。

        在看看具体的V层,

public interface Contract {

        interface NewsModel extends BaseModel{
            void getNewsData(int page,BaseListener listener);//获取新闻数据
        }
        interface NewsView extends BaseView{//展示数据
            void showNewsData(boolean isRefresh, List list);
            void showErrorData(boolean isRefresh);
        }
        abstract class NewsPresenter extends BasePresenter,NewsModel>{//处理数据
            public abstract void loadData(int page,boolean isRefresh);
        }

}

这个事协议类,我们看到,NewsView是继承了BaseView,我们自己定义了两个方法,显示数据和显示错误的方法,

在看看 m层,

public interface BaseModel {
}

同理M层中我们需要自己去实现全局请求的逻辑,在结合协议类中可以看到NewsModel继承了BaseModel,定义了获取data的方法,我们在看看具体获取data的方法里面的逻辑,

**
 * 作者 ;Created by ${wh} on 2018/4/26.
 */

public class ModelImp implements Contract.NewsModel{
    //获取网络数据
    @Override
    public void getNewsData(int page,final BaseListener listener) {
        HttpUtils.getNewsData(page)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver() {
                    @Override
                    public void onNext(NewsBean newsBean) {
                        listener.onSuccess(newsBean.getOthers());//通过litener接口将数据传递给V
                    }

                    @Override
                    public void onError(Throwable e) {
                        listener.onError(e.getMessage());
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}
可以看到结合了Rxjava2和retrofit2来虎丘数据,这里提下,首先rxjava的生命周期控制没有控制,这个需要封装控制下,其次这个线程转换也没有封装,需要去封装,不然实际项目中这种重复代码太多了。在这里出现了一个监听器,BaseLitener,这个事干什么的呢?我们看到在OnNext方法中,我们执行了listener.onsuccess()方法,看看这个方法,

public interface BaseListener <T>{//数据从M传到V
    void onSuccess(List<T> result);//成功

    void onError(String msg);//失败
}

这个接口定义了两个方法,那么还没说明白这个事干什么用的,不急,我们回到上面举的例子,后厨把菜做好了,肯定要告知服务员,那怎么告知呢,没错,传话器,这个listener就类似这个作用。好,数据获取到了,怎么交给P层呢,有了这个“传话器”

还怕通知不到P?

            看看P层是怎么去做的,还是同上,我们定义一个BasePresenter抽象类,

public class BasePresenter <V extends BaseView,M extends BaseModel>{
    protected WeakReference<V> mView;//软引用
    protected M mModel;

    protected void attachView(V view,M model){
        mModel = model;
        mView = new WeakReference<V>(view);
    }

    protected V getView() {//获取v
        return isViewAttached() ? mView.get() : null;
    }

    protected boolean isViewAttached() {//是否绑定
        return null != mView && null != mView.get();
    }

    protected void detachView() {//取消绑定
        if (null != mView) {
            mView.clear();
            mView = null;
        }
    }
}

    可以看到,我们在这里面分别用到了泛型来让P层持有V和M层的引用,这里为了避免内存泄露,我用了弱引用,

在看看协议类中也是继承了这个,接下来看看具体的P层做了什么操作,

/**
 * 作者 ;Created by ${wh} on 2018/4/25.
 */

public class PresenterImp extends Contract.NewsPresenter {
    private Contract.NewsView newsView;
    public PresenterImp(Contract.NewsView view) {
        newsView = view;
        mModel = new ModelImp();
    }
    @Override
    public void loadData(int page,final boolean isRefresh) {//M V之间数据通过P来绑定
        mModel.getNewsData(page,new BaseListener() {
            @Override
            public void onSuccess(List result) {
                newsView.showNewsData(isRefresh,result);
                Log.d("xx","size=="+result.size());
            }
            @Override
            public void onError(String msg) {
                newsView.showErrorData(isRefresh);
            }
        });
    }

}

定义了一个方法,获取数据,但是,这个数据是听过调M层中的方法来实现的,这里需要提两点,首先在这里,我们持有了M和V层的引用,需要在需要的时候,释放引用的持有,避免造成内存泄露------->在界面不存在的时候,如果不释放,那么还持有引用,而这个事强引用,GC是回收不了的,这样的话,会导致堆中的无用引用越来越多,占据堆中内存,造成内存泄露。第二点,我们看到了一个熟悉的东西BaseListener,这不是我们前面所说的“传话器”吗?这就是我所说的M怎么去通知P层的,这个监听器就是将M和P层之间的“数据交互”连接起来。

        接下来重点来,activity中的骚操作要来啦!!!!

public class MVPActivity extends AppCompatActivity implements Contract.NewsView,XRecyclerView.LoadingListener {

    private XRecyclerView xrv;
    private MvpAdapter adapter;
    private List othersBeans;
    private PresenterImp presenterImp;
    private int currentPage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        xrv = (XRecyclerView) findViewById(R.id.news_rv1);
        othersBeans  = new ArrayList<>();
        adapter = new MvpAdapter(othersBeans,this);
        xrv.setLayoutManager(new LinearLayoutManager(this));
        xrv.setLoadingListener(this);
        xrv.setAdapter(adapter);
        presenterImp = new PresenterImp(this);
        presenterImp.loadData(0,false);
    }


    @Override
    public void showError(Throwable throwable) {

    }

    @Override
    public void showNewsData(boolean isRefresh, List list) {
       if (isRefresh){
           othersBeans.clear();
           othersBeans.addAll(list);
           adapter.notifyDataSetChanged();
           Log.d("xx","size1=="+list.size());
       }else {
           if (list != null && list.size() > 0) {
               othersBeans.addAll(list);
               Log.d("xx","size2=="+list.size());
               adapter.notifyDataSetChanged();
           }
       }
    xrv.refreshComplete();
    xrv.loadMoreComplete();
 }

    @Override
    public void showErrorData(boolean isRefresh) {
        if (isRefresh){
        }else {
            currentPage--;
        }
    }

    @Override
    public void onRefresh() {
        presenterImp.loadData(0,true);
    }
//    private void loadMore(boolean isRefresh) {
//        if (!isRefresh && othersBeans != null && othersBeans.size() > 0) {
//
//        }
//    }
    @Override
    public void onLoadMore() {
        currentPage++;
        presenterImp.loadData(currentPage,false);;
    }

这里需要重点说下刷新和加载的处理,我们通过currentpage来控制的,之前我们在接口中传入了一个分页参数,这个时候,当刷新的时候,设置currentpage为0,并且设置isFresh为false,加载则让其currentpage++,记住在showError中当加载失败时,记得currentpage--。

    好了MVP结构简单的完了,那么接下来我们需要来看看MVVM模式下的操作了,M --v - - vm;

首先看看M层,获取数据的方法和MVP中类似。

public interface INewsModel {

    //获取新闻数据

    void loadData(int page, IBaseLoadListener loadListener);
}
 
  
public class NewsModelImp implements INewsModel{
    private static final String TAG = "NewsModelImpl";
    List simpleNewsBeanList = new ArrayList();
    @Override
    public void loadData(final int page, final IBaseLoadListener loadListener) {
        HttpUtils.getNewsData()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver() {
                    @Override
                    public void onNext(NewsBean newsBean) {
                        List list = newsBean.getOthers();
                        simpleNewsBeanList.clear();
                        if (list != null && list.size() > 0) {
                            for (NewsBean.OthersBean othersBean : list){
                                String thumbnail = othersBean.getThumbnail();
                                String name = othersBean.getName();
                                String description = othersBean.getDescription();
                                SimpleNewsBean simpleNewsBean = new SimpleNewsBean();
                                simpleNewsBean.thumbnail.set(thumbnail);
                                simpleNewsBean.name.set(name);
                                simpleNewsBean.description.set(description);
                                simpleNewsBeanList.add(simpleNewsBean);
                                if (page>1){

                                }
                            }
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        loadListener.loadFailure(e.getMessage());
                    }

                    @Override
                    protected void onStart() {
                        loadListener.loadStart();
                    }

                    @Override
                    public void onComplete() {
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                loadListener.loadSucess(simpleNewsBeanList);
                                loadListener.loadComlete();
                            }
                        }, 2000);
                    }
                });
    }
}

接下来看看VM中的逻辑

public class NewsVM implements IBaseLoadListener {
    private static final String TAG = "NewsVM";
    private INewsModel mNewsModel;
    private INewsView mNewsView;
    private NewsAdapter mAdapter;
    private int currPage = 1; //当前页数
    private int loadType; //加载数据的类型

    public NewsVM(INewsView mNewsView, NewsAdapter mAdapter) {
        this.mNewsView = mNewsView;
        this.mAdapter = mAdapter;
        mNewsModel = new NewsModelImp();
        getNewsData();
    }

        private void getNewsData() {
            loadType = MainConstant.LoadData.FIRST_LOAD;
            mNewsModel.loadData(currPage, this);
        }
    public void loadRefreshData() {
        loadType = MainConstant.LoadData.REFRESH;
        currPage = 1;
        mNewsModel.loadData(currPage, this);
    }
    public void loadMoreData() {
        loadType = MainConstant.LoadData.LOAD_MORE;
        currPage++;
        mNewsModel.loadData(currPage, this);
    }


    @Override
    public void loadSucess(List list) {
        if (currPage > 1) {
            //上拉加载的数据
            mAdapter.loadMore(list);
        } else {
            //第一次加载或者下拉刷新的数据
            mAdapter.refreshData(list);
        }
    }

    @Override
    public void loadFailure(String message) {
        if (currPage > 1) {
            //加载失败需要回到加载之前的页数
            currPage--;
        }
        mNewsView.loadFailure(message);
    }

    @Override
    public void loadStart() {
        mNewsView.loadStart(loadType);
    }

    @Override
    public void loadComlete() {
        mNewsView.loadComplete();
    }
}

无非是对获取到M层中数据进行处理,处理什么呢,我们可以看到,刷新和加载。

我们先看看xml文件,

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"
        >
    android:id="@+id/news_rv"
    android:layout_width="match_parent"
    android:layout_height="match_parent">




    
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">


    type="com.example.wh.mvvmdemo.R"/>
            name="simpleNewsBean"
        type="bean.SimpleNewsBean"
        />
            name="adapter"
        type="adapter.NewsAdapter" />
            name="position"
        type="int" />

    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >

            android:id="@+id/header_iv"
        android:layout_width="120dp"
        android:layout_height="60dp"
        app:imageUrl="@{simpleNewsBean.thumbnail}"
        />
            android:id="@+id/title_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:layout_toEndOf="@id/header_iv"
        android:textColor="#000"
        android:textSize="16sp"
        android:text="@{simpleNewsBean.name}"
        />
            android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignStart="@id/title_tv"
        android:layout_below="@id/title_tv"
        android:layout_marginTop="8dp"
        android:textSize="14sp"
        android:text="@{simpleNewsBean.description}"
        />
            android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_below="@id/header_iv"
        android:layout_marginEnd="15dp"
        android:layout_marginTop="8dp"
        android:onClick="@{()->adapter.clickDianZan(simpleNewsBean,position)}"
        app:resId="@{simpleNewsBean.isGood? R.mipmap.dianzan_press : R.mipmap.dianzan_normal }"
        />

MVVM的核心就是databinding,先不说这个,下次有机会,会撸一篇关于databinding的源码,接下来看看activity的逻辑

public class MainActivity extends AppCompatActivity implements INewsView,XRecyclerView.LoadingListener{
    private Context context;
    private ActivityMainBinding binding;
    private NewsAdapter adapter;
    private NewsVM vm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        context = this;
        initRecylerView();
        vm = new NewsVM(this,adapter);
    }

    private void initRecylerView() {
        binding.newsRv.setRefreshProgressStyle(ProgressStyle.BallClipRotate); //设置下拉刷新的样式
        binding.newsRv.setLoadingMoreProgressStyle(ProgressStyle.BallClipRotate); //设置上拉加载更多的样式
        binding.newsRv.setArrowImageView(R.mipmap.pull_down_arrow);
        binding.newsRv.setLoadingListener(this);

        LinearLayoutManager manager =new LinearLayoutManager(this);
        binding.newsRv.setLayoutManager(manager);
        adapter = new NewsAdapter(this);
        binding.newsRv.setAdapter(adapter);

    }

    @Override
    public void loadStart(int type) {
        if (type == FIRST_LOAD) {
            DailogHelper.getInstance().show(context, "加载中...");
        }
    }

    @Override
    public void loadComplete() {
        DailogHelper.getInstance().close();
        binding.newsRv.loadMoreComplete(); //结束加载
        binding.newsRv.refreshComplete(); //结束刷新
    }

    @Override
    public void loadFailure(String message) {
        DailogHelper.getInstance().close();
        binding.newsRv.loadMoreComplete(); //结束加载
        binding.newsRv.refreshComplete(); //结束刷新
        ToastUtils.show(context, message);
    }

    @Override
    public void onRefresh() {
       vm.loadRefreshData();
    }

    @Override
    public void onLoadMore() {
        vm.loadMoreData();
    }
}

我们可以看到在activity中获取VM的实例,通过VM 来操作相关的逻辑,

综上,我们可以看到,MVP中是通过M获取到数据,然后交个P,在交个P中我们是通过一个接口去实现的,然后P持有M和V的引用,那么在V中可以同过P从M得到的数据来展示。而MVVM实际上就是MVP的加强版,我们可以看到抛开XML不说,实际上就是把MVP中的P换成了VM,实际的操作还是一样的。

        最后送给大家一点建议,任何新的技术需要自己不断的去实践,去犯错了才明白自己到底哪里错了,在改错的过程中熟悉其逻辑过程这样才可能熟练,不要抗拒新技术,不要怕犯错。



你可能感兴趣的:(新技术)