理解使用MVP架构

目录

技术答疑,成长进阶,可以加入我的知识星球:音视频领域专业问答的小圈子

在安卓开发中使用 MVP 模式已经非常普遍了,网上关于 MVP 的讲解也相当多了,不过看得再多还是自己写一遍比较熟练。

MVP 分别代表了:M(Model)、V(View)、P(Presenter)的缩写,代表了三个不同的模块。

  • Model:负责处理数据的加载或者存储,比如从网络中或者本地数据库中获取数据。
  • View:负责界面数据的展示,以及与用户行为的交互。
  • Presenter:是模型和视图的桥梁,将模型和视图分离开来,主要负责处理事务逻辑。

如果所示,MVP 与 MVC 的不同之处在于,MVP 中的 View 层和 Model 层之间不进行交互,是通过中间的 Presenter 中来进行交互的。这样的好处就是能够解耦,降低 View 层和 Model 层之间的耦合度。

谷歌官方也给出了一个 MVP 模式的示例:https://github.com/googlesamples/android-architecture

MVP 模式实现登陆功能

用 MVP 模式简单实现了一个登陆功能,项目的包结构如下,根据模块层次来分层的。

接口定义

在 LoginContract 接口中定义了 M 层、V 层、P 层的接口,以及 Model 层数据传递到 Presenter 时的回调接口,将所有的接口放到一个总的接口中,这样不会那么的散乱,便于管理。

而且, M 层、V 层、P 层的实现类都将实现这些接口,这样的好处就是便于替换。

因为 Java 中的向上转型,当我们如下声明一个类时,LoginPresenter 类就向上转型成了 LoginContract.Presenter 类,这时,LoginPresenter 只能调用向上转型的类,也就是接口类中的定义的方法,不能再调用自己实现的其他方法,这样一样,就方便我们在需要的时候对 M 层、V 层、P 层 的类进行替换,可以替换内部的实现方法,而调用的方法名不变。

1private LoginContract.Presenter mPresenter = new LoginPresenter()
JAVA
 1public interface LoginContract {
 2
 3    /**
 4     * View 层的接口
 5     */
 6    interface View extends BaseView<Presenter> {
 7        void showLoading();
 8
 9        void hideLoading();
10
11        void setEmail(String email);
12
13        void setPassword(String password);
14
15        void toActivity();
16
17        boolean isActive();
18    }
19
20    /**
21     * Presenter 层的接口
22     */
23    interface Presenter extends BasePresenter {
24        void showLoadingDialog();
25
26        void cancelLoadingDialog();
27
28        void checkUserInfo(String email,String  password);
29    }
30
31    /**
32     * Model 层的接口
33     */
34    interface Model {
35
36        List<String> LoaderInitData();
37
38        void checkUserInfo(String email,String password,LoginContract.CallBack callBack);
39
40    }
41
42    /**
43     * Model 层传递数据到 Presenter 层时的回调接口
44     */
45    interface CallBack {
46        void LoginSuccess();
47        void LoginFailed();
48    }
49}
JAVA

V 层实现

将 Fragment 作为 MVP 中的 V 层,而 Activity 则是作为一个总的控制类,并没有做具体的业务逻辑操作,这里是仿照着谷歌官方的例子来编写的,当然也可以将 Activity 作为 V 层来实现。

在 V 层中需要绑定 P 层的 Presenter ,而 V 层则处理数据的展示和与用户的 UI 交互。

而 P 层则是负责处理具体的逻辑。

所以,V 层是将用户的事件都交给了 P 层去处理,而 P 层通过调用 M 层处理的数据,再将它展示给 V 层去。

 1/**
 2 * 给 View 来传入 Presenter 来绑定 presenter
 3 *
 4 * @param presenter
 5 */
 6
 7@Override
 8public void setPresenter(LoginContract.Presenter presenter) {
 9    this.mPresenter = presenter;
10}
JAVA

P 层实现

P 层是 MVP 中最重要的负责具体逻辑处理的一层,它持有 V 层和 M 层的引用,毕竟它需要调用 V 层和 M 层的方法。

由于 P 层持有了 V 层的引用,当 presenter 有后台异步的耗时操作时,如果这时退出了 V 层,而后台异步线程的操作并不会立即停止,这时候就会引发内存泄漏了。

 1/**
 2 * 构造方法中通过 View 的 setPresenter 让 View 获得了 Presenter 的实例
 3 * 这样,在 View 中就可以对 Presenter 中的方法进行操作了
 4 *
 5 * @param view
 6 */
 7public LoginPresenter(LoginContract.View view) {
 8    mView = view;
 9    /**
10     *  Presenter 绑定 View
11     */
12    mView.setPresenter(this);
13
14    /**
15     * Presenter 引用数据层 Model
16     */
17
18    mModel = new LoginModel();
19
20    /**
21     * 得到主线程,也就是 UI 线程的消息循环 Looper
22     */
23    UiHandler = new Handler(Looper.getMainLooper());
24}
JAVA

M 层实现

M 层也实现了 LoginContract 中的 Model 接口,M 层主要负责从网络或者本地数据库中加载数据,因为安卓的主线程并不是线程安全的,而且主线程中不允许进行网络和耗时操作,所以 M 层中的操作都是在异步的子线程中进行的,这样就需要向主线程的消息循环中发送消息,获得主线程中的消息循环(Looper.getMainLooper()),从而更新主线程的 UI 。

1public class LoginModel implements LoginContract.Model {
2
3    public LoginModel() {
4    }
5}
JAVA

具体的代码实现都可以在 Github 上找到,项目地址是:https://github.com/glumes/MVPLogin

注意事项

防止内存泄漏

在这里,我用的是 Fragment 作为 V 层,为了防止在 P 层中的异步耗时操作执行的时候,V 层已经退出了,所以在仿照谷歌官方的例子,在 V 层接口中定义了 isActive() 方法:

1/**
2 * 防止 P 层中持有 V 层导致内存泄漏
3 * 通过 Fragment 中的 isAdded() 方法判断当前 Fragment 是否已经添加到 Activity 当中去
4 * @return
5 */
6@Override
7public boolean isActive() {
8    return isAdded();
9}
JAVA

如果是采用 Activity 作为 V 层,则可以在 P 层添加一个销毁 V 层的方法,P 层增加了类似的生命周期的方法,用来在退出 Activity 的时候取消持有的 V 层引用。

1//presenter中添加mvpView 置为null的方法public void onDestroy(){    
2    mvpView = null;
3}
4
5//退出时销毁持有Activity@Overrideprotected void onDestroy() {    
6    mvpPresenter.onDestroy();    
7    super.onDestroy();
8}
JAVA

声明 Presenter 并绑定 V 层

在 Activity 中有这样一段代码,通过 new 创建了一个 Presenter,但却没有赋值给任何对象。

在谷歌的官方例子中也是这样创建的,这是因为将 Fragment 作为 V 层,并不需要在 Activity 中处理什么逻辑操作。

 1private LoginFragment mFragment;
 2@Override
 3protected void onCreate(Bundle savedInstanceState) {
 4    super.onCreate(savedInstanceState);
 5    setContentView(R.layout.activity_main);
 6    mFragment = LoginFragment.newInstance();
 7
 8    FragmentTransaction transaction = getFragmentManager().beginTransaction();
 9    transaction.replace(R.id.content,mFragment);
10    transaction.commit();
11
12    /**
13     * 声明 Presenter 的同时,将 View 和 Presenter 绑定起来
14     * 因为 Activity 是一个总控制类,所以 New 方法创建了一个 Presenter 却不需要赋值给 Activity 中的变量
15     */
16    new LoginPresenter(mFragment);
17}
JAVA

通过查看代码可以得知,在 Presenter 的构造函数中,通过 mView.setPresenter(this); 将 Presenter 自身又传入了 V 层中,这样 V 层通过 setPresenter() 方法持有了 P 层的引用,这样一来,P 层和 V 层就互相持有了各自的引用 。

1public LoginPresenter(LoginContract.View view) {
2    mView = view;
3    /**
4     *  Presenter 绑定 View
5     */
6    mView.setPresenter(this);
JAVA

参考

  1. http://www.cnblogs.com/liuling/archive/2015/12/23/mvp-pattern-android.html
  2. http://blog.csdn.net/dantestones/article/details/51445208
  3. http://blog.csdn.net/lmj623565791/article/details/46596109

欢迎关注微信公众号:音视频开发进阶

粤ICP备20067247号
使用 Hugo 构建    主题 StackedJimmy 设计,Jacob 修改