原创

MVP模式研究与实践

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://gaolei.blog.csdn.net/article/details/85610156

虽然有那么多资料介绍MVP了,但是还是想把自己的实践经验分享一下。

MVP简介

相信大家对MVC都是比较熟悉了,:M-Model-模型、V-View-视图、C-Controller-控制器,MVP作为MVC的演化版本,那么类似的MVP所对应的意义:M-Model-模型、V-View-视图、P-Presenter-表示器。 从MVC和MVP两者结合来看,Controlller/Presenter在MVC/MVP中都起着逻辑控制处理的角色,起着控制各业务流程的作用。而 MVP与MVC最不同的一点是M与V是不直接关联的也是就Model与View不存在直接关系,这两者之间间隔着的是Presenter层,其负责调控 View与Model之间的间接交互,MVP的结构图如下所示,对于这个图理解即可而不必限于其中的条条框框,毕竟在不同的场景下多少会有些出入的。在 Android中很重要的一点就是对UI的操作基本上需要异步进行也就是在MainThread中才能操作UI,所以对View与Model的切断分离是 合理的。此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合也可以通过接口更加方便地进行单元测试。

MVP结构图

关于android的MVP模式其实一直没有一个统一的实现方式,不同的人由于个人理解的不同,进而产生了很多不同的实现方式,其实很难去说哪一种更好,哪一种不好,针对不同的场合,不同的实现方式都有各自的优缺点。

而我采用的MVP是Google提出的一种MVP实现方式,个人认为这种方式实现简单,更加适合android项目。传统的MVP中Model起着处理具体逻辑的功能,Presenter起着隔离和解耦的作用,而在Google的MVP中弱化了Model,Model的逻辑由Presenter来实现,spManager、dbManager可以看做是Model,由Fragment实现View实现视图的变化,Activity作为一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。官方Demo地址(不仅包含mvp架构,还包含其它一些架构,可以切换不同分支查看):https://github.com/googlesamples/android-architecture/tree/todo-mvp-clean/

下图是Google官方Demo:todo-mvp模式的架构图

从结构方面观察一下MVP模式

 

Google官方Demo:todo-mvp是操作本地数据,而我们实际项目中大多数是获取网络数据。我下面展示的Demo使用todomvp+retrofit+rxjava2+multimodule架构

实现步骤:

1、创建类BasePresenter,封装一些公用操作

public abstract class BasePresenter<V> {

    public V mView;
    public ApiService mRestService = RetrofitProvider.getInstance().builder().getApiService();
    CompositeDisposable mCompositeDisposable;

    /**
     * 绑定View
     *
     * @param view
     */
    public void attach(V view) {
        this.mView = view;
        if (mCompositeDisposable == null) {
            mCompositeDisposable = new CompositeDisposable();
        }
    }

    /**
     * 释放View
     */
    public void dettach() {
        this.mView = null;
        //避免内存泄漏,view销毁的时候解除,取消所有observer
        if (mCompositeDisposable != null) {
            mCompositeDisposable.clear();
        }
    }

    public void addSubscribe(Observable observable, BaseObserver observer) {
        //把所有observer都放到一个集合中
        mCompositeDisposable.add(observer);
        observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }
}

2、创建类Contract,封装获取数据和显示数据接口,相当于签订的契约,根据自己需求设计


public class HomeContract {

    public interface Presenter {

        
        void getFeedArticleList(int num);

        void getBannerInfo();


        void onLoadMore();
    }

    public interface View {

        void showArticleList(ArticleListData itemBeans);

        void showBannerList(BannerListData itemBeans);
    }
}

3、MVP中的M,实现一个Model类,存储网络数据

public class ArticleListData {
    public ListData data;

    public class ListData {
        private int curPage;
        public List<FeedArticleData> datas;
        private int offset;
        private boolean over;
        private int pageCount;
        private int size;
        private int total;

        public int getCurPage() {
            return curPage;
        }

        public void setCurPage(int curPage) {
            this.curPage = curPage;
        }


        public int getOffset() {
            return offset;
        }

        public void setOffset(int offset) {
            this.offset = offset;
        }

        public boolean isOver() {
            return over;
        }

        public void setOver(boolean over) {
            this.over = over;
        }

        public int getPageCount() {
            return pageCount;
        }

        public void setPageCount(int pageCount) {
            this.pageCount = pageCount;
        }

        public int getSize() {
            return size;
        }

        public void setSize(int size) {
            this.size = size;
        }

        public int getTotal() {
            return total;
        }

        public void setTotal(int total) {
            this.total = total;
        }

        public List<FeedArticleData> getDatas() {
            return datas;
        }

        public void setDatas(List<FeedArticleData> datas) {
            this.datas = datas;
        }

    }
        public class FeedArticleData implements Serializable {

            private String apkLink;
            private String author;
            private int chapterId;
            private String chapterName;
            private boolean collect;
            private int courseId;
            private String desc;
            private String envelopePic;
            private int id;
            private String link;
            private String niceDate;
            private String origin;
            private String projectLink;
            private int superChapterId;
            private String superChapterName;
            private long publishTime;
            private String title;
            private int visible;
            private int zan;

            public String getApkLink() {
                return apkLink;
            }

            public void setApkLink(String apkLink) {
                this.apkLink = apkLink;
            }

            public String getAuthor() {
                return author;
            }

            public void setAuthor(String author) {
                this.author = author;
            }

            public int getChapterId() {
                return chapterId;
            }

            public void setChapterId(int chapterId) {
                this.chapterId = chapterId;
            }

            public String getChapterName() {
                return chapterName;
            }

            public void setChapterName(String chapterName) {
                this.chapterName = chapterName;
            }

            public boolean isCollect() {
                return collect;
            }

            public void setCollect(boolean collect) {
                this.collect = collect;
            }

            public int getCourseId() {
                return courseId;
            }

            public void setCourseId(int courseId) {
                this.courseId = courseId;
            }

            public String getDesc() {
                return desc;
            }

            public void setDesc(String desc) {
                this.desc = desc;
            }

            public String getEnvelopePic() {
                return envelopePic;
            }

            public void setEnvelopePic(String envelopePic) {
                this.envelopePic = envelopePic;
            }

            public int getId() {
                return id;
            }

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

            public String getLink() {
                return link;
            }

            public void setLink(String link) {
                this.link = link;
            }

            public String getNiceDate() {
                return niceDate;
            }

            public void setNiceDate(String niceDate) {
                this.niceDate = niceDate;
            }

            public String getOrigin() {
                return origin;
            }

            public void setOrigin(String origin) {
                this.origin = origin;
            }

            public String getProjectLink() {
                return projectLink;
            }

            public void setProjectLink(String projectLink) {
                this.projectLink = projectLink;
            }

            public int getSuperChapterId() {
                return superChapterId;
            }

            public void setSuperChapterId(int superChapterId) {
                this.superChapterId = superChapterId;
            }

            public String getSuperChapterName() {
                return superChapterName;
            }

            public void setSuperChapterName(String superChapterName) {
                this.superChapterName = superChapterName;
            }

            public long getPublishTime() {
                return publishTime;
            }

            public void setPublishTime(long publishTime) {
                this.publishTime = publishTime;
            }

            public String getTitle() {
                return title;
            }

            public void setTitle(String title) {
                this.title = title;
            }

            public int getVisible() {
                return visible;
            }

            public void setVisible(int visible) {
                this.visible = visible;
            }

            public int getZan() {
                return zan;
            }

            public void setZan(int zan) {
                this.zan = zan;
            }
        }

}

4、MVP中的V,和Presenter进行通信并且显示网络数据

public class HomeFragment extends BaseMvpFragment<HomePresenter> implements HomeContract.View {

    @BindView(R.id.project_recyclerview)
    RecyclerView project_recyclerview;
    @BindView(R.id.banner)
    Banner banner;
    @BindView(R.id.smartRefreshLayout_home)
    SmartRefreshLayout smartRefreshLayout;
    private List<FeedArticleData> articleDataList;
    private ArticleListAdapter feedArticleAdapter;

    @Override
    public void initData(Bundle bundle) {

    }

    @Override
    public void initView() {
        initSmartRefreshLayout();
        initRecyclerView();
    }

    @Override
    public int setContentLayout() {
        return R.layout.fragment_home;
    }

    @Override
    public void reload() {
        if (mPresenter == null) return;
        mPresenter.getFeedArticleList(0);
        mPresenter.getBannerInfo();
    }

    @Override
    public HomePresenter initPresenter() {
        //创建Presenter对象
        return new HomePresenter();
    }

    @Override
    protected void loadData() {
        //Fragement懒加载,在这个地方和Presenter交互,请求网络数据
        CustomProgressDialog.show(getActivity());
        mPresenter.getFeedArticleList(0);
        mPresenter.getBannerInfo();
    }

    //显示Article数据
    @Override
    public void showArticleList(ArticleListData listData) {
        final List<FeedArticleData> newDataList = listData.data.getDatas();

        articleDataList.addAll(newDataList);
        feedArticleAdapter.notifyItemRangeInserted(articleDataList.size() - newDataList.size(), newDataList.size());
        feedArticleAdapter.notifyDataSetChanged();
        smartRefreshLayout.finishLoadMore();
    }

    //显示Banner数据
    @Override
    public void showBannerList(BannerListData itemBeans) {

        final List<String> linkList = new ArrayList<String>();
        List imageList = new ArrayList();
        List titleList = new ArrayList();
        int size = itemBeans.data.size();

        for (int i = 0; i < size; i++) {
            imageList.add(itemBeans.data.get(i).getImagePath());
            titleList.add(itemBeans.data.get(i).getTitle());
            linkList.add(itemBeans.data.get(i).getUrl());
        }
        banner.setImageLoader(new com.youth.banner.loader.ImageLoader() {
            @Override
            public void displayImage(Context context, Object path, ImageView imageView) {
                Glide.with(getActivity()).load(path).into(imageView);
            }
        });

        banner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);//设置圆形指示器与标题
        banner.setBannerAnimation(Transformer.FlipHorizontal);
        banner.setIndicatorGravity(BannerConfig.CENTER);//设置指示器位置
        banner.setDelayTime(3000);//设置轮播时间
        banner.setImages(imageList);//设置图片源
        banner.setBannerTitles(titleList);//设置标题源
        banner.start();
        banner.setOnBannerListener(new OnBannerListener() {
            @Override
            public void OnBannerClick(int position) {
                Intent intent = new Intent(getActivity(), ArticleDetailActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("url", linkList.get(position));
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }

    private void initRecyclerView() {
        articleDataList = new ArrayList<>();
        feedArticleAdapter = new ArticleListAdapter(getActivity(), articleDataList);
        project_recyclerview.addItemDecoration(new DividerItemDecoration(getActivity(),
                DividerItemDecoration.VERTICAL_LIST));
        project_recyclerview.setLayoutManager(new LinearLayoutManager(getActivity()));
        project_recyclerview.setAdapter(feedArticleAdapter);
        feedArticleAdapter.setOnItemClickListener(new ArticleListAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View v, int position) {
                Intent intent = new Intent(getActivity(), ArticleDetailActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("url", articleDataList.get(position).getLink());
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }

    //初始化下拉刷新控件
    private void initSmartRefreshLayout() {
        smartRefreshLayout.setEnableRefresh(false);
        smartRefreshLayout.setEnableLoadMore(true);
        smartRefreshLayout.setEnableScrollContentWhenLoaded(true);//是否在加载完成时滚动列表显示新的内容
        smartRefreshLayout.setEnableFooterFollowWhenLoadFinished(true);
        smartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshLayout) {
                mPresenter.onLoadMore();
            }

        });
    }

    public void scrollToTop() {
        project_recyclerview.scrollToPosition(0);
    }

    public void onResume() {
        super.onResume();

    }

    public void onDestroy() {
        super.onDestroy();
    }
}

5、MVP中的P,具体实现BasePresenter,请求网络数据

public class HomePresenter extends BasePresenter<HomeContract.View> implements HomeContract.Presenter {
    private int mCurrentPage = 0;

    //加载更多
    @Override
    public void onLoadMore() {
        ++mCurrentPage;
        Observable observable = mRestService.getFeedArticleList(mCurrentPage);
        addSubscribe(observable, new BaseObserver<ArticleListData>(false) {
            @Override
            public void onNext(ArticleListData feedArticleListData) {
                mView.showArticleList(feedArticleListData);
            }
        });
    }

    //获取文章列表数据
    @Override
    public void getFeedArticleList(int num) {
        Observable observable = mRestService.getFeedArticleList(num);
        addSubscribe(observable, new BaseObserver<ArticleListData>(true) {
            @Override
            public void onNext(ArticleListData feedArticleListData) {
                mView.showArticleList(feedArticleListData);
            }
        });
    }

    //获取banner数据
    @Override
    public void getBannerInfo() {
        Observable observable = mRestService.getBannerListData();
        addSubscribe(observable, new BaseObserver<BannerListData>(true) {

            @Override
            public void onNext(BannerListData bannerListData) {
                mView.showBannerList(bannerListData);
            }

        });


    }

}

6、实现Retrofit的封装类 RetrofitProvider

public final class RetrofitProvider {

    private Retrofit mRetrofit;
    private OkHttpClient mOkHttpClient;
    private static volatile RetrofitProvider sInstance;
    private ApiService restService;
    public static String netCachePath;
    public final String BASE_URL = "http://www.wanandroid.com/";
    private RetrofitProvider() {
    }


    public RetrofitProvider builder() {
        netCachePath= CustomApplication.context.getExternalFilesDir("net_cache").getAbsolutePath();
        if (mOkHttpClient == null) {
            mOkHttpClient = new OkHttpClient.Builder()
                    .addNetworkInterceptor(new HttpLoggingInterceptor())
                    .addNetworkInterceptor(new OnlineCacheInterceptor())//有网缓存拦截器
                    .addInterceptor(new OfflineCacheInterceptor())//无网缓存拦截器
                    .cache(new Cache(new File(netCachePath), 50 * 10240 * 1024))//缓存路径和空间设置
                    .addInterceptor(new RetryIntercepter(4))//重试
                    .addInterceptor(new GzipRequestInterceptor())//开启Gzip压缩

//                    .addInterceptor(new DefaultHeaderInterceptor())//请求连接中添加头信息
//                    .addInterceptor(new ProgressInterceptor())//请求url的进度
//                    .addInterceptor(new TokenInterceptor())//token过期,自动刷新Token
//                    .addInterceptor(new SignInterceptor())//所有的接口,默认需要带上sign,timestamp2个参数
//                    .addNetworkInterceptor(new ParamsEncryptInterceptor())//参数加密,一般针对表单中的字段和值进行加密,防止中途第三方进行窥探和篡改
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(CustomApplication.context)))
                    .connectTimeout(5, TimeUnit.SECONDS)
                    .readTimeout(5, TimeUnit.SECONDS)
                    .writeTimeout(5, TimeUnit.SECONDS)
                    .build();

        }
        if (mRetrofit == null) {
            mRetrofit = new Retrofit.Builder()
                    .client(mOkHttpClient)
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
        }
        return sInstance;
    }

    public static RetrofitProvider getInstance() {
        if (sInstance == null) {
            synchronized (RetrofitProvider.class) {
                if (sInstance == null) {
                    sInstance = new RetrofitProvider();
                }
            }
        }
        return sInstance;
    }

    public ApiService getApiService() {
        if (restService == null)
            restService = mRetrofit.create(ApiService.class);
        return restService;
    }
}

7、以上是主要代码,完整代码可以访问地址:https://github.com/gaoleicoding/MVPModel/tree/todomvp+retrofit+rxjava+multimodule

为什么使用MVP?

MVP的优点:

1.解耦

毋庸置疑这是最大的好处,通过上述例子可以看到一个页面被分成了两个部分,一个是视图的逻辑,另一个是业务逻辑。两者持有的引用都是对方的接口,因此可以随意地替换实现(页面大修改),并且单独修改view或者presenter的逻辑并不会影响另一方(小修改)。

2.代码清晰

以前的MVC模式,当一个页面的逻辑足够复杂,就会出现一个activity上千行的情况,在这样的一个类中想定位一个bug是十分困难的,有时候自己的敲的代码看着都像别人的代码,得重头捋一捋才能定位,相当耗时。而用MVP模式,一个页面的逻辑都可以从Contract类中直观的看到有哪些,找到对应逻辑的方法再进入实现类中找到问题所在,效率上不可同日而语。

3.灵活

好多MVP的实现都是用Activity来实现View,这里使用Fragment便是出于灵活的考虑,Fragment可以复用,可以替换。面对多变的需求可以更从容的应对,例如:一个页面原本是一个列表,后来要求这个页面显示2个Tab,原来的列表变为其中一个Tab。类似的情况可能很常见,如果是用Activity实现的,可能要修改activity类中的很多代码,而原本就使用Fragment和MVP的话,仅需添加一个Fragment,修改Activity假如切换Tab的逻辑,而不用修改第一个Fragment的任何逻辑,这更符合OOP的编程思想。

4.简化

开头说过,传统MVP中Model起着处理具体逻辑的功能,Presenter起着隔离和解耦的作用。这种方式实现的MVP中Presenter看上去更像一个代理类,仅仅是不让View直接访问Model。虽然这样做解耦的程度更高,但实际项目中,一个页面逻辑的修改是非常少的,仅仅在产品修改需求是才会发生,大部分情况下仅仅是修复bug之类的小修改,并需要这样彻底的解耦。从另一个方面来说,一个页面的视图逻辑和业务逻辑本就是一体的。因此,Google的MVP弱化的Model的存在,让Presenter代替传统意义上的Model,减少了因使用MVP而剧增的类。这种方式相比不适用MVP仅从1个activity,增加到了1个activity,1个fragment(view),1个presenter,1个contract(如果有传统意义上的Model,还会有1个Model的接口和1个实现类)。

5.符合功能单一原则

6.MVP模式有那么多优点,那有没有缺点呢?那也是有的,毕竟凡事都有利有弊。缺点:造成接口和类数激增,代码复杂度和学习成本高。但肯定是利大于弊所有才会备受推崇

相关优秀文章推荐

  1. MVP架构封装演变全过程:https://blog.csdn.net/yulong0809/article/details/78622428

文章最后发布于: 2019-01-02 14:16:59
展开阅读全文
0 个人打赏
私信求帮助

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览