基于项目需要,特别设计了一套简单的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的设计思路及实现。