是时候换一种架构了

目录

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示例

关注微信公众号获取更多相关资源

Android小先生

猜你喜欢

转载自blog.csdn.net/guofudong2/article/details/81287029