Android中使用MVP架构相关知识梳理
Android中使用MVP架构相关知识梳理
综述
本文为了记录笔者对MVP架构的认知, 因为当前使用的主要是MVVM, 后面需要考虑尝试MVI或者其他架构, 也可能考虑尝试flutter, 所以将承上启下的MVP相关知识点总结梳理出来, 方便记忆以及后期的回顾.
Android中的MVP架构从MVC架构演变而来, 所以要谈论MVP就不得不先说MVC.
MVC是最传统的Android开发架构, 按照字面理解会分为三层
- Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
- View层: 负责展示页面, 一般对应布局文件
- Controller层: 负责接收输入, 调用业务逻辑并更新页面的显示, 一般对应Activity/Fragment
这个三层的划分实际上并不差, 责任清晰, 包括早期的web开发使用的也是MVC架构.
但MVC开始慢慢被新架构替代, 因为Activity/Fragment不是一个完美的Controller, 它还承担了一部分view的职责. 由于Activity/Fragment既承载视图的显示逻辑,又包含业务逻辑. 这就导致Activity/Fragment如果用来承担Controller的职责的时候会特别的重, 很多MVC结构的项目中, 一个Controller一千多行, 甚至两千多行, 代码耦合度高,同时还难以维护和测试.
于是逐步发展出了MVP架构, 架构同样分了三层
- Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
- View层: 负责展示页面, 一般对应布局文件, 以及和显示逻辑强关联的Activity/Fragment
- Presenter层: 负责存放页面状态, 根据用户的输入调用业务逻辑和显示逻辑
这样, 原本笨重的Activity/Fragment大半职责被拆分到了Presenter层.
概念
一个典型的MVP架构如下图所示
这里用户与View层交互, View通过与Presenter互相持有调用Presenter中实现的调用业务逻辑. Presenter通过持有Model实例并调用对应的方法来实现业务逻辑. 如果业务逻辑有返回数据, 那么数据将会返回到Presenter, 然后数据通过Presenter返回View, 最后被用户所感知.
当然只有图只是为了方便理解关系. 但关系总归要落于实处. 下面给出一个最简单的MVP架构示例以供参考.
//一个Presenter持有Activity的实例, 并在调用构造函数时获取Model类的实例
public class MainPresenter {
private MainActivity view;
private Model model;
public MainPresenter(MainActivity view) {
this.view = view;
this.model = new Model();
}
//唯一的方法通过mode获取数据
public void getData() {
String data = model.getData();
view.showResult(data);
}
}
//一个Activity作为view层, 持有Presenter的实例
public class MainActivity extends AppCompatActivity {
private MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter(this);
init();
}
private void init() {
Button btnClick = findViewById(R.id.btn_click);
btnClick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.getData();
}
});
}
void showResult(String message) {
TextView tvResult = findViewById(R.id.tv_result);
tvResult.setText(message);
}
}
//一个model提供数据
public class Model {
String getData() {
return "通过Model获取的data";
}
}
//layout略, 只要求一个id为btn_click的button和一个id为tv_result的text.
上面这个例子没有使用接口, 没有抽象类, 没有依赖注入或者Provider工厂, 没有使用回调或者数据总线返回getData的结果, 也没有用线程模拟延时, 各个类之间高度耦合, 而且还有内存泄露的问题.
但MVP和MVC两种架构之间最本质的区别给出来了, 那就是Activity从作为Controller的角色改为View的角色, 只负责接收用户的输入, 并显示结果. 而原本Activity肩负的调用Model并且获取结果后通过页面显示出来的职责被移交给了Presenter.
这里的核心是Activity和Presenter互相持有, 且原本MVC中Controller的职责被一分为二, 负责调度业务逻辑的部分被划分给了Presenter.
从整个操作流程分析, 用户通过View触发OnClickListener()回调, OnClickListener()回调调用被Activity持有的Presenter实例对应的方法, Presenter获取数据后通过Presenter持有的Activity实例调用showResult()方法将获取的数据显示到页面上.
MVP架构的可用实现
前面已经给出了一个mvp架构的最小示例了, 但是一个高度耦合的mvp框架是无法满足实际的使用需求, 所以在真正使用时一般至少需要引入接口, 使得View和Presenter之间互相解耦, 并准备好attachView和detachView两个方法, 方便Presenter和View的生命周期绑定. 这样才算是一个初步可用的mvp架构.
//使用contract即契约类汇总单个页面对应的View和Presenter接口, 方便维护
public interface MainContract {
interface View {
void showResult(String message);
}
interface Presenter {
void attachView(View view);
void detachView();
void getData();
}
}
//修改Presenter, 实现attachView和detachView方法, 避免内存泄露
public class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private Model model;
@Override
public void attachView(MainContract.View view) {
this.view = view;
this.model = new Model();
}
@Override
public void detachView() {
this.view = null;
this.model = null;
}
//唯一的方法通过mode获取数据
public void getData() {
String data = model.getData();
view.showResult(data);
}
}
//修改Activity, 在恰当的生命周期调用Presenter的attachView()和detachView()方法
public class MainActivity extends AppCompatActivity implements MainContract.View {
private MainContract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter();
presenter.attachView(this);
init();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView();
presenter = null; //可以不写
}
private void init() {
Button btnClick = findViewById(R.id.btn_click);
btnClick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.getData();
}
});
}
@Override
public void showResult(String message) {
TextView tvResult = findViewById(R.id.tv_result);
tvResult.setText(message);
}
}
//model部分没有变化
public class Model {
String getData() {
return "通过Model获取的data";
}
}
//同样, layout略
以上示例相较于一开始用于理解的版本初步解决了两个问题, 一个是内存泄露, 一个是Presenter和View的解耦. 属于可用范畴, 当然正常使用仍然应该加上依赖注入或者使用Provider进行手动注入来进一步解耦.
进阶版本的MVP架构
在上述版本中MVP架构仍然存在一些问题, 比如如果有多个页面每个页面都需要调用attachView()和detachView()方法, 这么做很麻烦而且也担心遗漏所导致的内存泄露, 所以如果要进一步拓展, 可以考虑引入基类和WeakReference 并且使用依赖注入框架或手动注入来尽可能解耦合. 参考下面这个拓展写法.
//首先是两个以base命名的基类
public abstract class BaseActivity<V, P extends BasePresenter<V>> extends AppCompatActivity{
protected P presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = getPresenter();
presenter.attachView(getCurrentView());
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView();
}
protected abstract P getPresenter();
protected abstract V getCurrentView();
}
public abstract class BasePresenter<V> {
private Reference<V> mViewRef;
public void attachView(V view) {
mViewRef = new WeakReference<V>(view);
this.onAttach();
}
public void detachView() {
mViewRef.clear();
mViewRef = null;
this.onDetach();
}
protected boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
protected V getView() {
return mViewRef != null ? mViewRef.get() : null;
}
protected abstract void onAttach();
protected abstract void onDetach();
}
//一般来说应该选择一个依赖注入框架比如ButterKnife, Dagger等, 但是那就超出本文想要讨论的范畴了, 所以这里给出手动注入的做法
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
initModuleProvider();
}
void initModuleProvider(){
ModuleProvider.getInstance();
}
}
public class ModuleProvider {
private static volatile ModuleProvider instance;
private static final Object mSync = new Object();
private ModuleProvider(){}
public static ModuleProvider getInstance(){
if (instance == null){
synchronized (mSync){
if (instance == null){
instance = new ModuleProvider();
}
}
}
return instance;
}
private final Model model = new Model();
private Reference<MainPresenter> mainPresenterRef;
public Model getModel(){
return model;
}
public MainPresenter getMainPresenter() {
if (mainPresenterRef == null || mainPresenterRef.get() == null){
mainPresenterRef = new WeakReference<>(new MainPresenter());
}
return mainPresenterRef.get();
}
}
//然后是一直没有改变的model类
public class Model {
public String getData() {
return "通过Model获取的data";
}
}
//最后是正常的Contract, 以及View和Presenter
public interface MainContract {
interface View {
void showResult(String message);
}
interface Presenter{
void getData();
}
}
public class MainActivity extends BaseActivity<MainContract.View, MainPresenter> implements MainContract.View {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected MainPresenter getPresenter() {
return ModuleProvider.getInstance().getMainPresenter();
}
@Override
protected MainContract.View getCurrentView() {
return this;
}
private void init() {
Button btnClick = findViewById(R.id.btn_click);
btnClick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.getData();
}
});
}
@Override
public void showResult(String message) {
TextView tvResult = findViewById(R.id.tv_result);
tvResult.setText(message);
}
}
public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter{
private Model model;
@Override
protected void onAttach() {
model = ModuleProvider.getInstance().getModel();
}
@Override
protected void onDetach() {
model = null;
}
//唯一的方法通过mode获取数据
public void getData() {
if (!isViewAttached()){
return;
}
String data = model.getData();
getView().showResult(data);
}
}
以上就是一个比较完整的MVP架构, View使用Fragment也是类似的做法. 不过为了控制大小尽量避免引入三方依赖, 作为Model层示例的Model类也只是给出了一个样子货.
实际使用时可以拓展为Repository层架构或者直接Clean Architect架构, 两者结合就称得上是一个完整的android项目结构了.