> For the complete documentation index, see [llms.txt](https://ryan-8.gitbook.io/android-architecture-journey/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryan-8.gitbook.io/android-architecture-journey/corlibsdesign_md/mvp.md).

# MVP

![MVP结构图](http://rocko-blog.qiniudn.com/Android%E4%B8%AD%E7%9A%84MVP_1.png)

## View

### 界面

由于MVP被动视图的特性, Presenter会引用View的抽象以及Model的抽象, 同时View会引用Presenter, 那么此时几乎每个界面都会多出很多声明, 初始化的重复语句. 要消除这些重复可以考虑使用依赖注入的框架, 如Dagger, 但是考虑到Dagger本身的复杂性, 以及项目复杂度等原因, CoreLibs采用泛型及模板方法来消除重复.

基本上Android中是由Activity和Fragment来承载视图, 也有可能是View/ViewGroup. 因此在CoreLibs中提供了BaseActivity以及BaseFragment两个模板类, 来处理一些公共的视图逻辑. 至于View/ViewGroup的模板类会在以后加入. 模板类, 不仅可以简化代码, 同时也能起到一定的约束作用, 比如将onCreate方法拆分成多个符合单一职责原则的小方法, 从而避免onCreate过于臃肿.

### 抽象接口

被动视图中的View是将界面行为抽象出来的接口, 以供Presenter调用. 同Base模板类一样, View也可以抽象出几乎所有界面所共有的行为 -- BaseView. 比如只要有网络请求的界面(项目中几乎都是), 都会有加载框来提示用户当前正在加载网络数据, 加载完成后需要隐藏加载框, 并在适当时候提示用户一些错误消息. 一旦抽象出来BaseView, 就可以在Presenter等类里自动的调用这些公共的行为, 而无需再重复手动写.

## Presenter

Presenter的共有逻辑一般就是与界面的绑定, 解绑等.

## Model

Model这一层比较复杂, 随着项目的复杂度, Model可能会继续划分层级, 因此暂时没有抽出共有的逻辑.

## View与Presenter的结合

View与Presenter之间是通过泛型来约束类型, 这里View以BaseActivity为例, BaseFragment与其类似.

**BaseActivity声明**

`public abstract class BaseActivity<V extends BaseView, T extends BasePresenter<V>>`

**BasePresenter声明**

`public abstract class BasePresenter<T extends BaseView>`

**BaseView声明**

`public interface BaseView`

可以看到, BaseActivity需要两个泛型, 第一个是继承自BaseView的接口, 第二个是继承自BasePresenter的Presenter, 同时Presenter里的泛型必须是V类型, 也就是第一个泛型类型.

BasePresenter则只需要一个继承自BaseView的接口.

这样做是为了约束View与Presenter的类型, 可以强制开发使用MVP模式, 同时可以将一些通用逻辑被抽象到BaseActivity与BasePresenter中:

**BaseActivity部分通用逻辑**

```
    protected T presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());

        presenter = createPresenter();
        if (presenter != null) presenter.attachView((V) this);

        init(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (presenter != null) presenter.detachView();
        presenter = null;
    }

    /**
     * 指定Activity需加载的布局ID, {@link BaseActivity}BaseActivity
     * 会通过{@link #setContentView}方法来加载布局
     *
     * @return 需加载的布局ID
     */
    protected abstract int getLayoutId();

    /**
     * 初始化方法, 类似OnCreate, 仅在此方法中做初始化操作, findView与事件绑定请使用ButterKnife
     */
    protected abstract void init(Bundle savedInstanceState);

    /**
     * 创建Presenter, 然后通过调用{@link #getPresenter()}来使用生成的Presenter
     * @return Presenter
     */
    protected abstract T createPresenter();
```

上述代码声明了T类型的Presenter, 根据BaseActivity的声明可以得知T类型就是我们需要的Presenter的实际类型.

在onCreate方法中, 首先通过getLayoutId()抽象方法来获取Activity的布局id, 然后通过setContentView()设置到content中. 这样我们在BaseActivity的子类中就无需再复写onCreate方法. 然后通过createPresenter()抽象方法来实例化声明的presenter. 由于presenter是protected, 因此子类可以直接使用presenter成员变量. 接着调用presenter的attachView()方法将View与Presenter绑定起来, attachView()的实现会在下面贴出来. 最后调用了抽象方法init(Bundle savedInstanceState), 来替代onCreate方法.

在onDestroy中, 首先通过presenter的detachView()将View与Presenter解绑, 同时将presenter置空, 很大程度上避免了内存泄漏的可能.

可以看到, BaseActivity为子类做了如下逻辑, 子类无需再做:

* 设置布局视图
* 声明以及初始化Presenter
* 将Presenter与View绑定
* 初始化Activity
* 将Presenter与View解除绑定

子类需要实现三个抽象方法, 具体含义请参考注释:

* int getLayoutId()
* T createPresenter()
* void init(Bundle savedInstanceState)

**BasePresenter部分通用逻辑**

```
    protected T view;

    protected void onViewAttached() {}

    /**
     * 将Presenter与MVPView绑定起来.
     * @param view MVPView
     */
    public void attachView(T view) {
        this.view = view;
        onViewAttached();
    }

    /**
     * 将Presenter与MVPView解除.
     */
    public void detachView() {
        view = null;
    }
```

BasePresenter的逻辑比较简单, 就不做过多解释了, 同样的BasePresenter的子类可以直接使用view变量, 当然这里的view只是一个接口, 具体还需要BaseActivity子类去实现. 如果不去实现则会报类型转换错误. onViewAttached()方法通过名字也可以知道作用, 一些需要延迟初始化的变量或代码可以在覆写的onViewAttached()方法里写.

**BaseView部分代码**

```
    /**
     * 加载时显示加载框
     */
    void showLoading();

    /**
     * 加载完成时隐藏加载框
     */
    void hideLoading();

    /**
     * 显示提示消息
     */
    void showToastMessage(String message);

    /**
     * 获取Context对象
     */
    Context getViewContext();
```

BaseView接口中抽象了几乎所有页面都会使用的方法, 有了BaseView, 我们就可以利用BaseView为开发者做一些通用的逻辑, 比如自动隐藏加载框, 自动提示服务器返回的错误消息或者是网络错误消息等. 但是这段代码不在Presenter或View中, 以后会提及.

BaseActivty默认实现了BaseView, 因此BaseActivty的子类去实现BaseView的子类时, 只需要实现BaseView中没有的方法即可. 也可以通过覆写已实现的方法来达到细化控制的目的.

**BaseActivity的完整声明**

`public abstract class BaseActivity<V extends BaseView, T extends BasePresenter<V>>`

`extends FragmentActivity implements BaseView`

以上就是通过泛型来抽象通用逻辑的过程, 当然这三个Base类中还可以加入更多的逻辑, 这里只是其中一部分, 比如BaseActivity中还加入了ButterKnife的bind()方法, 以及加载框等.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ryan-8.gitbook.io/android-architecture-journey/corlibsdesign_md/mvp.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
