一个登录Demo初识MVP

从我们的项目到JD,处处可见Android MVP 模式的身影。

项目越来越庞大,开发越来越多,MVP的优势越来越明显。

今天我们通过一个简单的登录Demo,初步学会MVP的使用。


首先,什么是MVP?

MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。

在MVC模式中,Activity应该是属于View这一层。而实质上,它既承担了View,同时也包含一些Controller的东西。因为耦合度高,因此不利于我们的开发与维护。

所以我们把Activity的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。

MVP模式的核心思想:

MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model不变

这样,Activity的工作简单了,只用来响应生命周期,其他工作都由Presenter去完成。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。


MVP的优势:

  • 分离了视图逻辑和业务逻辑,降低了耦合

  • Activity只处理生命周期的任务,代码变得更加简洁

  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性

  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试

  • 把业务逻辑抽到Presenter中去,避免后台线程引用Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM

使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。

一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest吧。

Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))。Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。

说了那么多,开始实战,先看战略地图(UML图,PPT纯手绘):

一个登录Demo初识MVP_第1张图片

上面一张简单的MVP模式的UML图,从图中可以看出,使用MVP,至少 需要经历以下步骤:

  1. 创建IPresenter接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterImpl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)

  2. 创建IView接口,把所有视图逻辑的接口都放在这里,其实现类是当前的Activity/Fragment

  3. 由UML图可以看出,Activity里包含了一个IPresenter,而PresenterImpl里又包含了一个IView并且依赖了Model。Activity里只保留对IPresenter的调用,其它工作全部留到PresenterImpl中实现

  4. Model并不是必须有的,但是一定会有View和Presenter

通过上面的介绍,MVP的主要特点就是把Activity里的许多逻辑都抽离到View和Presenter接口中去,并由具体的实现类来完成。这种写法多了许多IView和IPresenter的接口,在某种程度上加大了开发的工作量,刚开始使用MVP的小伙伴可能会觉得这种写法比较别扭,而且难以记住。其实一开始想太多也没有什么卵用,只要在具体项目中多写几次,就能熟悉MVP模式的写法,理解TA的意图,以及享受其带来的好处。

PS:

注意一下UML图中的关系,相互依赖(虚线箭头),相互关联实线箭头

依赖表现为函数中的参数(use a),是类与类之间的连接,表示一个类依赖于另一个类的定义,其中一个类的变化将影响另外一个类。例如如果A依赖于B,则B体现为局部变量,方法的参数、或静态方法的调用。如电视(TV)依赖于频道(channel)常见的依赖关系如下:
(1)类B以参数的形式传入类A的方法。
(2)类B以局部变量的形式存在于类A的方法中。
(3)类A调用类B的静态方法。

关联关系包括单项关联,双向关联,组合和聚合

分析依赖和关联的关系

1.依赖是一种弱关联

2.依赖关系表现在局部变量,方法的参数,以及对静态方法的调用

3.关联关系中,体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。

4.依赖关系中,可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。


开始Coding,一个登录小Demo。

先看一下项目结构:

一个登录Demo初识MVP_第2张图片

从V开始,先看接口ILoginView,定义实现类需要实现的方法,清除文本和登录结果展示。

package com.example.quan.quanstudy.MVP_login.view;

/**
 * Created by xingquan.he on 2017/3/7.
 */

public interface ILoginView {
    public void onClearText();
    public void onLoginResult(Boolean result, int code);
}

然后就是实现类LoginActivity,在这里操作UI,关联P(成员变量),实现接口方法,然后就是初始化View和Listener,业务逻辑都通过ILoginPresenter的具体实现类完成,代码很清爽有木有~

package com.example.quan.quanstudy.MVP_login.view;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.example.quan.quanstudy.MVP_login.presenter.ILoginPresenter;
import com.example.quan.quanstudy.MVP_login.presenter.LoginPresenterImpl;
import com.example.quan.quanstudy.R;

/**
 * Created by xingquan.he on 2017/3/7.
 */

public class LoginActivity extends Activity implements ILoginView, View.OnClickListener {

    private EditText mEditUser;
    private EditText mEditPass;
    private Button mBtnLogin;
    private Button mBtnClear;
    private ProgressBar mProgressBarLogin;

    private ILoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp_login);

        initView();
        initOnClickListener();

        mLoginPresenter = new LoginPresenterImpl(this);
        mProgressBarLogin.setVisibility(View.INVISIBLE);
    }

    private void initView() {
        mEditUser = (EditText) this.findViewById(R.id.et_login_username);
        mEditPass = (EditText) this.findViewById(R.id.et_login_password);
        mBtnLogin = (Button) this.findViewById(R.id.btn_login_login);
        mBtnClear = (Button) this.findViewById(R.id.btn_login_clear);
        mProgressBarLogin = (ProgressBar) this.findViewById(R.id.progressbar_login);
    }

    private void initOnClickListener() {
        mBtnLogin.setOnClickListener(this);
        mBtnClear.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_login_clear:
                mLoginPresenter.clear();
                break;
            case R.id.btn_login_login:
                mProgressBarLogin.setVisibility(View.VISIBLE);
                mBtnLogin.setEnabled(false);
                mBtnClear.setEnabled(false);
                mLoginPresenter.doLogin(mEditUser.getText().toString(), mEditPass.getText().toString());
                break;
        }
    }

    @Override
    public void onClearText() {
        mEditUser.setText("");
        mEditPass.setText("");
    }

    @Override
    public void onLoginResult(Boolean result, int code) {
        mProgressBarLogin.setVisibility(View.INVISIBLE);
        mBtnLogin.setEnabled(true);
        mBtnClear.setEnabled(true);
        if (result) {
            Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
        }
        else {
            Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

接下来我们看P,先看接口,定义清除和登录方法:

package com.example.quan.quanstudy.MVP_login.presenter;

/**
 * Created by xingquan.he on 2017/3/7.
 */

public interface ILoginPresenter {
    void clear();
    void doLogin(String name, String passwd);
}

具体的P实现,V(在P而不是Activity里操作UI,简化逻辑,如果在别的 Activity 里也需要用到相同的业务逻辑,就可以直接复用 P(一个V可以包含一个以上的 P,总之,需要什么业务就 new 什么样的 Presenter,灵活~)和M的引用作为成员变量,关联V依赖M,构造方法中初始化赋值:

    private ILoginView mLoginView;
    private User mUser;
    private Handler mHandler;//模拟登录耗时操作

    public LoginPresenterImpl(ILoginView iLoginView) {
        this.mLoginView = iLoginView;
        mUser = new User("xq.he","mvp");
        mHandler = new Handler(Looper.getMainLooper());
    }

复写接口定义的方法:

    @Override
    public void clear() {
        mLoginView.onClearText();
    }

    @Override
    public void doLogin(String name, String password) {
        Boolean isLoginSuccess = true;
        final int code = mUser.checkUserValidity(name,password);
        if (code != 0) {
            isLoginSuccess = false;
        }
        final Boolean result = isLoginSuccess;
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mLoginView.onLoginResult(result, code);
            }
        }, 4000);
    }

这样,我们就通过 IView 和 IPresenter,把 MVC中Activity的V和C分离开来,M不变~

package com.example.quan.quanstudy.MVP_login.model;

/**
 * Created by xingquan.he on 2017/3/7.
 */

public class User {

    private String mName;
    private String mPassword;

    public User(String name, String password) {
        this.mName = name;
        this.mPassword = password;
    }

    public String getName() {
        return mName;
    }

    public String getPassword() {
        return mPassword;
    }

    public int checkUserValidity(String name, String password) {
        if ( name == null || password == null || !name.equals(getName()) || !password.equals(getPassword()) ){
            return -1;
        }
        return 0;
    }
}


完成后界面如图,十分简单的一个登录Demo,关键是领会MVP。

一个登录Demo初识MVP_第3张图片


原创不易,转载请注明出处哈。

权兴权意

代码可以更优雅~

http://blog.csdn.net/hxqneuq2012/article/details/60870945


项目源代码,欢迎提建议(star)。

https://github.com/HXQWill/QuanStudy/tree/master/app/src/main/java/com/example/quan/quanstudy/MVP_login


参考:

MVP 模式简单易懂的介绍方式(大赞~,本文在此基础上修改了P和V的部分逻辑和代码规范)

http://kaedea.com/2015/10/11/android-mvp-pattern/

UML类图实例(重要的基础课)

http://blog.csdn.net/xhf55555/article/details/6896316/

你可能感兴趣的:(Android开发,Java学习笔记,算法与数据结构设计,求职问题,开源项目,趣味编程,设计模式)