是时候换一种架构了

目录

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开源项目推荐

vip视频
MVP + RxJava + Retrofit示例
MVP + Dagger2 + Retrofit + Rxjava示例

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

推荐阅读更多精彩内容