基于Retrofit2+OkHttp3+RxJava实现的MVP框架(1)

时间:2021-03-07 21:12:47

基于项目需要,特别设计了一套简单的MVP开发框架,用以描述基于REST接口协议的业务数据流向-获取到展示的过程。

  • V模块-视图模块,是由Activity、Fragment等窗口组件来实现。
  • P模块-展现模块,是由自定义Presenter类及子类实现。
  • M模块-业务数据模块,则是基于Retrofit2+OkHttp3+RxJava实现。Retrofit2+OkHttp3主要是为了方便快速的业务接口的实现,RxJava是为了解决跨线程的数据交互。

基于以上描述,我们先来介绍一下View和Presenter的定义:

1. View模块

    View的相关类设计包括一个接口IView,一个基类BaseView。

IView

public interface IView {
    public void onActionStart();
    public void onActionSuccess();
    public void onActionFailure();
}

IView定义了一些抽象方法,用来描述View类型应该具备的基本行为(方法),作为与Presenter进行交互的渠道。这里定义的三个方法描述了开始、成功、失败共三种行为,则具体的展示方式则有其实现类完成。

BaseView

public abstract class BaseView extends Activity implements IView {
@Override
    public void onActionStart() {
        Log.d(Constants.TAG_DEMONCAT, getClass().getSimpleName() + " -> " + "onActionStart");
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "Action starts...", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onActionSuccess() {
        Log.d(Constants.TAG_DEMONCAT, getClass().getSimpleName() + " -> " + "onActionSuccess");
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "Action success!", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onActionFailure() {
        Log.d(Constants.TAG_DEMONCAT, getClass().getSimpleName() + " -> " + "onActionFailure");
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "Action failure!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

BaseView实现了IView的三个通用行为方法,以Toast展示成功失败,以ProgressDialog展示等待状态。更多的样式及行为完全自定义。考虑到APP交互设计过程中,特别的网络请求过程,大致行为相同,所以可以抽象一部分在基类中实现,业务模块子类则不用单独实现。

2. Presenter

    Presenter部分也包含一个IPresenter接口,一个BasePresenter基类。

IPresenter

public interface IPresenter {
    void destroy();
}

IPresenter接口,定义了通用行为-destroy。

BasePresenter

public abstract class BasePresenter implements IPresenter {    
    public void destroy() {
        // TODO destroy
    }
}

BasePresenter实现了IPresenter的destroy方法,同时可以增加自己的通用行为。

我们都知道,View和Presenter的交互是双向的,View通过用户动作向Presenter发起指令,Presenter执行完之后,将结果告知View做进一步的展示。那么View和Presenter是如何完成交互,它们的关系是如何建立的呢?

首先,我们定义一个原则:

  • 一个Presenter只能对应一个View,反之亦然

这样定义的考虑是,更加明确清晰具体的业务功能。比如,登录功能,则对应一个LoginView和LoginPresenter。基于这个原则,我们可以通过如下方式,将Presenter与View关联起来,

public abstract class BasePresenter<V extends IView> implements IPresenter {
    protected V mView;

    public BasePresenter(V view) {
        mView = view;
    }

    public void destroy() {
        mView = null;
    }
}

我们对BasePresenter增加范型限制,并且该类型必须是IView的实现类,这样就可以将具体业务的Presenter和View给关联起来。同时,Presenter中包含有一个IView实现类的对象mView,在创建Presenter时传入作为属性,那么在需要告知View该展示何种状态时,则可以通过该View属性,完成消息传递-方法调用。还是以登录作为例子,

public class LoginPresenter extends BasePresenter<LoginPresenter.LoginView> {
    public void login(String username, String password) {
        // TODO
    }
    public interface LoginView extends IView {
        public void onLoginSuccess(String token);
        public void onLogining();
        public void onLoginFailure();
    }
}

此时,LoginPresenter声明了范型LoginView,而LoginView是作为内部接口来定义的,并且继承了IView。这么做就是考虑到,具体的展示策略是由Presenter来规范的,那么将具体业务的View定义在该业务的Presenter中,是很合理的。想要具备此业务功能,则实现该View接口。LoginPresenter对外提供了登录功能的对应方法login(), 入餐为用户名和密码;在完成登录后,LoginPresenter则可以通过LoginView对象,来完成消息传递,告知成功、失败。

上面讲到的是从Presenter到View的关联,那么接下来我们来看View到Presenter的关联。

public abstract class BaseView extends Activity implements IView {
    private IPresenter[] mAllPresenters = null;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // init all presenter will be used
        mAllPresenters = getPresenter();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // destroy all presenters when destroy view
        if (mAllPresenters != null && mAllPresenters.length > 0) {
            for (IPresenter presenter : mAllPresenters) {
                presenter.destroy();
            }
        }
    }
}

这里看到,BaseView中定义了Presenter类型的数组对象,该对象保存着当前所需业务对应的所有Presenter对象。并且在onDestroy方法中全部调用destory。可能有人会疑问,上面讲到了一个View-Presenter的一对一关联原则,这里为何是一对多?

其实刚才讲的一对一,是在具体的单个业务功能的角度来看。View的具体业务实现类定义由Presenter的对应业务实现类来完成定义,这样都对应一个具体功能,站在具体功能业务角度,就是一对一的关系。而这里的代码看起来是一对多的关系,是因为用到了数组,这里是站在代码实现的角度。假设这个BaseView的具体实现类需要仅完成一个功能,那么Presenter数组个数为1,那么就是一对一关系;如果要完成多个功能,那么数组个数就为N,这里它已经集成了多个功能业务,那么这个View既是功能A的View,也是功能B的V,这里已经不是站在一个业务功能上来看待这个View了。BaseView是Activity的子类,但不能够完全对等于MVP模式中的View。

public class LoginActivity extends BaseView implements LoginPresenter.LoginView{
    private LoginPresenter mPresenter = new LoginPresenter(this);
@Override
    protected BasePresenter[] getPresenter() {
        return new BasePresenter[]{mPresenter};
    }
    /*-----------Implements of LoginView-----------*/
    @Override
    public void onLoginSuccess(String token) {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
        Toast.makeText(
                getApplicationContext(), "登录成功 token:" + token, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLogining() {
        mProgressDialog =
                ProgressDialog.show(this, null, "登录中...");
    }

    @Override
    public void onLoginFailure() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
        Toast.makeText(
                getApplicationContext(), "登录失败!", Toast.LENGTH_SHORT).show();
    }
}

上面这段代码,则是以登录的Activity为例,按照上述思想,很容易理解。所以,在实现View和Presenter的时候,分别继承BaseView和BasePresenter来完成具体业务功能开发即可。

至此,V和P的关联关系的设计思路已经说明完毕,下一篇将介绍核心-业务数据层M的设计思路及实现。


相关文章