MVP+Retrofit+RxJava2 封装

使用架构的目的是为使程序模块化,做到模块内的高聚合和模块间的低耦合,架构和模式并不是让你的代码减少,可能往往会增大,但是它帮你在逻辑上更简洁了,很好的定义了单一性原则,提供了更好的扩展性,方便定位问题以及后续开发中需求变跟时不至于满篇的去改一大堆东西 。(一下图片均来自网络,侵删)

MVC简述

首先熟悉下在Android开发中,传统的MVC模式(view、model、controller)。


mvc.jpg

Activity、Fragment、布局xml就相当于View层。业务模型,建立的数据结构,以及网络请求、数据库处理、I/O操作相当于model层。至于controller层就有点模糊了,我们大家都将Activity、Fragment视作controller层,也就是把view和controller都揉和在Activity和Fragment中,视图显示和控制逻辑错综复杂

这样经过几次需求变跟、代码迭代后一个Activity动辄上千行代码,最主要的修改某个页面时控制逻辑也得跟着变动,当然了可能会出现的bug以及后续开发人员阅读代码的难度也就增加了很多,这就违背了架构的低耦合。

MVP简述

这样更优的架构模式MVP也就得到更多开发者的青睐。

MVP中每个单词具体代表的含义是什么?

M层 和MVC中的model一样,都是模型层,负责处理存储、检索、操作数据,包括网络请求、数据库处理以及IO操作。

V层 和MVC的view一样,都是视图层,负责绘制UI元素以及与用户的交互,对应的是activity、Fragment、adapter、xml

P层 (Presenter)是真个MVP的控制中心,作为View和Model的中间枢纽,处理View和Model间的交互和业务逻辑

这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离 ,视图层专注View的展示和用户的交互。

MVP.jpg

这样做的优势也就是显而易见了

  • Model层与View层完全分离,修改view层不会影响Model层,降低了耦合

  • 复杂的逻辑处理放在presenter进行处理,减少了Activity的臃肿

  • 可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑, 提高了代码复用 ,代码灵活性

  • 模块职责划分明显 , 具有良好的可扩展性

  • Presenter层与View层的交互是通过接口来进行的,便于单元测试。

当然MVP也是有缺点的。

视图和Presenter的交互会过于频繁,使得他们的联系过于紧密。也就是说,一旦视图变更了,presenter也要变更。

MVP 存在的问题

1、接口过多问题

使用MVP模式去构建项目,会造成类文件和接口文件的过多,进而增大包的体积。

解决方式:写一个Contract接口,然后把与MVP相关接口全部列入到里面去。

2、内存泄漏

当用户关闭了View层,但这时Model层如果仍然在进行耗时操作,因为Presenter层也持有View层的引用,所以造成垃圾回收器无法对View层进行回收,这样一来,就造成了内存泄漏。

解决方式:可以重写onDestroy()方法,在View销毁时强制回收掉Presenter;或是采用弱引用的方式

MVP例子

基类

从上面说明中,我们知道MVP有三次View层、Presenter层、Model层构成,在构建MVP架构的时候需考虑到代码的复用性已经以后的可扩展性,这样我们先设计基类。

基类BaseActivity、基类BaseFragment,所有activity和fragment的基类,

基类BasePresenter,所有Presenter的基类

接口IBaseView, 说明了每一个View基本需要的一些操作

这样主要的一些基类就设计好了

mvp_base.png
BaseView

定义每一个View基本需要的一些操作

public interface IBaseView {

    void showLoading();

    void hideLoading();

    void showError(String msg);
}
BasePresenter

因为Presenter将View与Model进行隔离,也将业务逻辑从View中抽离 。

1、Presenter处理的数据需要回显到View上时,就需要持有View的引用,也就是Presenter需要 绑定 View 和解绑 View (onAttachView(V view) onDetachView())。所有的View都是IBaseView的子类, 所以必须要传入一个泛型的 View 层接口 。

2、View展现的数据需要从Model层获取,而Presenter是他们的桥梁,所有Presenter也需要持有Model层引用。

public class BasePresenter<V extends IBaseView> {
    //用于及时取消订阅,以防止内存泄漏
    private CompositeDisposable mDisposable;

    protected ApiServer mApiServer = ApiServer.getInstance();

    private SoftReference <V> mReferenceView;

    /**
     * activity 创建的时关联presenter
     * @param view
     */
    public void onAttachView(V view) {
        this.mReferenceView = new SoftReference<>(view);
    }

    /**
     * activity 关闭时解除绑定,防止内存泄漏
     */
    public void onDetachView() {
        this.mReferenceView.clear();
        this.mReferenceView = null;
        removeDisposable();
    }

    public V getMvpView() {
        return this.mReferenceView.get();
    }

    protected void addDisposable(Disposable disposable) {
        if (mDisposable == null) {
            mDisposable = new CompositeDisposable();
        }
        if (!mDisposable.isDisposed()) {
            mDisposable.add(disposable);
        }
    }

    protected void removeDisposable() {
        if (mDisposable != null) {
            mDisposable.dispose();
            mDisposable = null;
        }
    }
}

在BasePresenter中用到了SoftReference,这样就更加严谨,防止内存泄漏。

BaseActivity
BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutID());
        initViews();
        initViewListener();
    }

    /**
     * 初始化布局文件
     * @return  返回布局文件的xml
     */
    protected abstract int initLayoutID();

    /**
     * 初始化View控件
     */
    protected abstract void initViews();

    /**
     * 初始化View的事件
     */
    protected abstract void initViewListener();

上面的BaseActivity就是我们在MVC里面用到的基类,但在MVP架构中,需要实现IBaseView,同时要初始化Presenter以及绑定Presenter。

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements IBaseView {
    private MaterialDialog mDialog;
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutID());
        initPresenter();
        initViews();
        initViewListener();
    }

    /**
     * 初始化Presenter
     */
    protected void initPresenter() {
        mPresenter = createrPresenter();
        if (mPresenter != null) {
            mPresenter.onAttachView(this);
        }
    }

    protected abstract P createrPresenter();

    /**
     * 初始化布局文件
     *
     * @return 返回布局文件的xml
     */
    protected abstract int initLayoutID();

    /**
     * 初始化View控件
     */
    protected abstract void initViews();

    /**
     * 初始化View的事件
     */
    protected abstract void initViewListener();

    @Override
    public void showLoading(String msg) {
        if (mDialog != null) {
            mDialog = mDialog.getBuilder().title(msg).build();
            mDialog.show();
        } else {
            MaterialDialog.Builder builder = MaterialDialogUtils.showIndeterminateProgressDialog(this, msg, true);
            mDialog = builder.show();
        }
    }

    @Override
    public void hideLoading() {
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
    }

    @Override
    public void showError(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.onDetachView();
        }
    }

这样简单的基类代码就完成了。

具体的用法

那到底具体怎么用呢?定义一个简单的按钮,然后点击请求网络数据,然后显示。

定义一个Activity 继承BaseActivity,实现IMainView接口,对应的MainPresenter里面处理请求网络,结果回显给页面。

MainActivity:

public class MainActivity extends BaseActivity<MainPresenter> implements IMainView {

    TextView textView;
    Button btnSuccess;

    @Override
    protected MainPresenter createrPresenter() {
        return new MainPresenter();
    }

    @Override
    protected int initLayoutID() {
        return R.layout.activity_main;
    }

    @Override
    protected void initViews() {
        textView = findViewById(R.id.textView);
        btnSuccess = findViewById(R.id.btn_success);
    }

    @Override
    protected void initViewListener() {
        btnSuccess.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.requestData();
            }
        });
    }

    @Override
    public void showData(List<Banner> banners) {
        textView.setText(new Gson().toJson(banners));
    }
}

IMainView:

public interface IMainView extends IBaseView {
    void showData(List<Banner> banners);
}

MainPresenter:

public class MainPresenter extends BasePresenter<IMainView> {

    void requestData() {
        if (getView() == null) {
            return;
        }
        addDisposable(mApiServer.toSubscribe(mApiServer.getApi().getBanner(),
                new BaseObserver<List<Banner>>(getView(), "加载中...") {
                    @Override
                    protected void onSuccess(List<Banner> banners) {
                        if (getView() != null) {
                            getView().showData(banners);
                        }
                    }

                    @Override
                    protected void onError(ApiException ex) {
                        if (getView() != null) {
                            getView().showError(ex.getMessage());
                        }
                    }

                }));
    }
}

这样显示整个例子就完成了,效果如下:


Screenshot_20200304_143119_com.wenbing.mvpdemo.jpg

项目地址

欢迎转载,但是请注明出处

示例源码 个人博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容