目录
1.MVP简介
2.基类抽取
3.项目运用
4.MVP开源框架
之前做项目一直使用的是MVC架构,后来MVP大热,随之越来越多的项目开始使用这个架构,因为对MVC的使用熟悉,快速开发项目,所以一直还是使用MVC。在闲暇时间也开始接触学习MVP,后来在原有项目的新功能和新项目中开始使用MVP,MVP虽然有一些缺点但确实比MVC好很多。现在各种博客中讲解MVP大都以一个小Demo,比如登入操作来讲解说明它的使用,以至于对于初学者不能快速用于项目中。其实还是推荐通过学习官方给出的Demo,来理解MVP。文本以整体项目来说明理解MVP。
谷歌官方Android框架Demo地址
因为官方的都是英文的,所以这里推荐一篇博客,对官方Demo的理解和说明和详细,便于理解:官方MVP项目学习
MVP简介
MVP是由MVC演化而来的,所以前提是对MVC的理解,这个不在阐述MVC。
MVP的出发点是关注点分离,将视图和业务逻辑解耦。Model-View-Presenter三个部分可以简单理解为:
- Model:获取视图中显示的数据和相应的业务逻辑。
- View:显示数据(model)的界面,同时将用户指令(事件)发送给Presenter来处理。View通常含有Presenter的引用。在Android中Activity,Fragment和ViewGroup都扮演视图的角色。
- Presenter:中间人,同时有两者的引用。请注意单词model非常有误导性。它应该是获取或处理model的业务逻辑。例如:如果你的数据库表中存储着User,而你的视图想显示用户列表,那么Presenter将有一个数据库业务逻辑(例如DAO)类的引用,Presenter通过它来查询用户列表。
简单来说就是:View负责页面展示和传递用户操作,当用户在页面上操作时,比如刷新页面,那么在View中就会调用Presenter的刷新页面方法,但是刷新页面获取数据的操作并不是Presenter完成的,而是在它的刷新方法中调用Model对应的方法来完成的,也就是说真正数据的获取是由Model来获取的,Presenter只是一个中介。
所以,View中持有Presenter的引用,Presenter持有Model的引用。View是怎么调用Presenter的呢?其实还有一个角色,就是View interface,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试。
项目运用
分包
关于项目的分包,没有绝对的好坏,看自己更习惯与哪一种,我喜欢根据功能模块分,也就是将实现某一功能的相关类放在同一包下。
每一个界面都有对应的Model,Presenter,还有一个Contract,它是协议的意思,规定Model,View ,Presenter之前的关系。很方便通过它查看它们之前的联系以及要实现的功能。
在官方的Demo中没有把Model放入其中,我觉得把Model也放进去更加便于查看和理解它们之间的联系。Contract本身是一个接口,只是规定协议,所以对应的Model,Presenter,View都要实现对应的接口。
public class ArticleDetailActivity extends BaseFrameActivity<DetailPresenter, DetailModel> implements DetailContract.View
public class DetailModel implements DetailContract.Model
public class DetailPresenter extends DetailContract.Presenter
在Activity需要显示数据的时候是通过Presenter来实现的
Presenter中并没有真是实现,而是调用Model对应的方法。
Model真正的去处理业务逻辑获取数据
抽取基类
每一个功能中都会有Model,Presenter,View Interface,Activity和Fragment中也会有相同的方法,所以抽取基类那是必然的。具体该怎么抽取呢?
在项目中并不是所有的功能都会用到MVP的模式,别入闪屏页展示一个固定的图片。所以考虑这种情况不能直接抽取一个MVP的基类,我们可以跟MVC中那样抽取一个基类,然后让MVP基类再继承该基类就可以。下面看具体的代码实现:
//Activity和Fragment公共方法,抽取成了一个接口,各自实现即可。
public interface BaseFuncIml {
/* 初始化数据方法 */
void initData();
/* 初始化UI控件方法 */
void initView();
/* 初始化事件监听方法 */
void initListener();
/* 初始化界面加载方法 */
void initLoad();
}
因为Activity和Fragment有一些公共方法,而它们没有共同的父类,所以这里定义一个接口,在接口中定义公共方法,各自实现即可。
//BaseFragment
public class BaseFragment extends Fragment implements BaseFuncIml {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
initData();
initView();
initListener();
initLoad();
}
//BaseActivity
public class BaseActivity extends AppCompatActivity implements BaseFuncIml, View.OnClickListener{
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
initData();
initView();
initListener();
initLoad();
}
这里只是贴出关键的代码,并不是全部的。需要什么方法自己添加即可。
这只是普通的基类,下面看一下MVP中的基类:
public abstract class BaseFrameActivity<P extends BasePresenter, M extends BaseModel> extends BaseActivity implements BaseView{
public P mPresenter;
public M mModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = TUtil.getT(this, 0);
mModel = TUtil.getT(this, 1);
if (this instanceof BaseView) {
mPresenter.attachVM(this, mModel);
}
}
@Override
protected void onDestroy() {
if (mPresenter != null) mPresenter.detachVM();
super.onDestroy();
}
}
MVP中基类跟普通基类的区别就是:它要实现对应的View Interface,并且持有Presenter的引用,不同的功能对应View Interface,Presenter和Model都是不一样的,所以它们各自也得抽取基类。因为Presenter持有Model的引用,而Presenter是在View中初始化的,所以这里也把Model传递进来了。从代码中看出在Activity创建的时候分别创建了Presenter和Model的实例,然后将Model,View绑定到Presenter中。在销毁的时候解绑。
mPresenter = TUtil.getT(this, 0);
是怎么初始化的呢?因为使用了泛型,所以做初始化的时候要根据具体使用的类型来做初始化,那么就要获取具体传入泛型的类型。具体获取类型代码如下:
public class TUtil {
public static <T> T getT(Object o, int i) {
try {
/**
* getGenericSuperclass() : 获得带有泛型的父类
* ParameterizedType : 参数化类型,即泛型
* getActualTypeArguments()[] : 获取参数化类型的数组,泛型可能有多个
*/
return ((Class<T>) ((ParameterizedType) (o.getClass()
.getGenericSuperclass())).getActualTypeArguments()[i])
.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassCastException e) {
e.printStackTrace();
}
return null;
}
}
Java给我们提供了相应的Api来获取具体泛型的类型。
下面就是Model,Presenter,View Interface 的基类实现:
public abstract class BasePresenter<M, V> {
public M mModel;
public V mView;
public void attachVM(V v, M m) {
this.mModel = m;
this.mView = v;
}
public void detachVM() {
mRxManager.clear();
mView = null;
mModel = null;
}
}
public interface BaseModel {
}
public interface BaseView {
void onRequestStart();
void onRequestError(String msg);
void onRequestEnd();
}
Model不同的功能,方法不同,所以这里是一个空接口;View Interface:可以把所有界面的一些公共操作抽取出来,要根据具体的业务逻辑抽取。
以上基类抽取完毕,以一个文章详情看一下具体的使用:
主要公共获取文章的详细内容展示:定义协议DetailContract:
public interface DetailContract {
interface Model extends BaseModel {
Observable<StoryContentEntity> getStoryContent(int id);
Observable<StoryExtraEntity> getStoryExtras(int id);
}
interface View extends BaseView {
void showContent(StoryContentEntity storyContentEntity);
void showStoryExtras(StoryExtraEntity storyExtraEntity);
}
abstract class Presenter extends BasePresenter<Model, View> {
abstract void getStoryContent(int id);
abstract void getStoryExtras(int id);
}
}
通过该类我们就可以看出要实现的功能和下一步要实现的东西。所以要根据业务逻辑定义出协议类,然后再根据协议类逐步去实现。
public class ArticleDetailActivity extends BaseFrameActivity<DetailPresenter, DetailModel> implements DetailContract.View {
private static final String TAG = "ArticleDetailActivity";
ActionProviderView commentProvider;
ActionProviderView praiseProvider;
@BindView(R.id.toolBar)
Toolbar mToolbar;
@BindView(R.id.detail_bar_image)
ImageView detailBarImg;
@BindView(R.id.detail_bar_title)
TextView detailBarTitle;
@BindView(R.id.detail_bar_copyright)
TextView detailBarCopyright;
@BindView(R.id.wv_detail_content)
WebView detailContentWV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article_detail);
ButterKnife.bind(this);
}
@Override
public void initData() {
Intent intent = getIntent();
int articleId = intent.getIntExtra("articleId", 0);
if (articleId != 0) {
mPresenter.getStoryContent(articleId);
mPresenter.getStoryExtras(articleId);
} else {
ToastUtils.showToast(this, TAG + "数据加载出错");
}
}
@Override
public void initView() {
initToolbar();
initWebViewClient();
}
}
因为我们把基类定义好了,在具体的实现中只需要继承基类,然后传入具体的类型即可,因为在基类总自动完成了Presenter和Model的绑定,另外业务逻辑已经在Model中实现好了,只需要在View中方法中通过Presenter调用即可。这样能大大简化Activity的代码,不再像MVC中那样,如果业务逻辑比较多,Activity的代码非常繁多。
总结
使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。
MVP开源框架
Mosby
Nucleus
Beam
TheMVP
MVPro
MVP开源项目推荐
MVP + RxJava + Retrofit示例
MVP + Dagger2 + Retrofit + Rxjava示例