在项目还没开始之前,准备使用MVP和MVVM之间犹豫不决时,决定写个demo来比较两者之间的异同点和一点自己的感想,同时会对比之间的优劣势,在之前先谢谢陪你唠嗑的博主,看了他的demo才决定来做的。
下面直接开搞,首先接口类,最基本的,我定义的是APIservice,代码如下:
public interface ApiService { @GET(UrlConstant.URL_PATH) ObservablegetNewsData(); @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 ObservablegetNewsData( int page) { return getService().getNewsData(page); } public static Observable getNewsData() { return getService().getNewsData(); } }
上述是网络层,下面给出返回的实例对象信息类
public class NewsBean { private int limit; private List> subcribed; private Listothers; 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,BaseListenerlistener);//获取新闻数据 } 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可以看到结合了Rxjava2和retrofit2来虎丘数据,这里提下,首先rxjava的生命周期控制没有控制,这个需要封装控制下,其次这个线程转换也没有封装,需要去封装,不然实际项目中这种重复代码太多了。在这里出现了一个监听器,BaseLitener,这个事干什么的呢?我们看到在OnNext方法中,我们执行了listener.onsuccess()方法,看看这个方法,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() { } }); } }
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 ListothersBeans; 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, IBaseLoadListenerloadListener); }
public class NewsModelImp implements INewsModel{ private static final String TAG = "NewsModelImpl"; ListsimpleNewsBeanList = 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"?>MVVM的核心就是databinding,先不说这个,下次有机会,会撸一篇关于databinding的源码,接下来看看activity的逻辑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 }" />
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,实际的操作还是一样的。
最后送给大家一点建议,任何新的技术需要自己不断的去实践,去犯错了才明白自己到底哪里错了,在改错的过程中熟悉其逻辑过程这样才可能熟练,不要抗拒新技术,不要怕犯错。