MVP在Android中的使用

概述

关于Android MVP设计模式已经出来很久了,在项目中也普遍使用这个设计模式,但是MVP对于初学者来说的话还是有点难以捉摸的!每次看上去都感觉这样写很不错,很好理解,就是增加了很多类,看完之后自己去写又很难写出来。再讲MVP设计模式之前,我们先来讲一下MVC设计模式。

MVC模式

MVC(模型-视图-控制器)用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互时,无须重新编写业务逻辑,MVC模式结构分为三个部分:

  • Model:实体模型,我们针对业务模型,建立的数据结构和相关类,对应着Bean实体类。Model与View无关,但是与业务有关。
  • View:一般采用XML文件或者Java代码进行界面的描述,也可以使用JS+HTML的方式作为View。
  • Controller:Android的控制器通常在Activity、Fragment或者由它们控制的其他业务类中。

MVC用一句话来概括就是通过Controller来操作Model层的数据,并且返回给View层来展示。如图所示:
这里写图片描述

看起来像是那么回事,但是MVC也有它的缺点,不然就没我们MVP模式什么事了。

  • Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用布局和初始化界面,接受并出来来自用户的请求,并作出相应,随着界面及其逻辑复杂度的不断提升,Activity类的职责不断增加,导致其变得庞大而臃肿。
  • Model层和View层相互耦合,不易于开发和维护。

MVP模式

MVP模式是MVC模式的一个演化版本,它的结构同样分为三个部分:

  • Mode:实体模型,同样对应着Bean实体类主要提供数据的存取的功能,Present需要通过Model层来存储和或者获取数据

    从网络,数据库,文件,传感器,第三方等数据源读写数据。
    对外部的数据类型进行解析转换为APP内部数据交由上层处理。
    对数据的临时存储,管理,协调上层数据请求。

  • View:负责处理用户事件交互和视图部分的展示,在Android中它可以使Activity和Fragment或者是某个View控件

    提供UI交互
    在presenter的控制下修改UI。
    将业务事件交由presenter处理。
    注意: View层不存储数据,不与Model层交互。View和Presenter通过接口进行交互 面向接口编程

  • Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。它还从Model中去获取数据然后返回给View,使得Model和View没有耦合。
    这里写图片描述

MVP的核心思想

MVP 把 Activity 中的 UI 逻辑抽象成 View 接口,把业务逻辑抽象成 Presenter 接口,Model 类还是原来的 Model。这就是 MVP 模式,现在这样的话,Activity 的工作的简单了,只用来响应生命周期,其他工作都丢到 Presenter 中去完成。从上图可以看出,Presenter 是 Model 和 View 之间的桥梁,为了让结构变得更加简单,View 并不能直接对 Model 进行操作,这也是 MVP 与 MVC 最大的不同之处。

MVP的优点

  • 分离了视图逻辑和业务逻辑,降低了耦合
  • Activity 只处理生命周期的任务,代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了 View 和 Presenter 的接口中去,提高代码的可阅读性
  • Presenter 被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  • 把业务逻辑抽到 Presenter 中去,避免后台线程引用着 Activity 导致 Activity 的资源无法被系统回收从而引起内存泄露和 OOM

使用 MVP,至少需要经历以下步骤:

创建 IPresenter 接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterCompl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)创建 IView 接口,把所有视图逻辑的接口都放在这里,其实现类是当前的 Activity/Fragment。Activity 里包含了一个 IPresenter,而 PresenterCompl 里又包含了一个 IView 并且依赖了 Model。Activity 只保留对 IPresenter 的调用,其它工作全部留到 PresenterCompl 中实现。Model 并不是必须有的,但是一定会有 View 和 Presenter。

MVP简单的登陆实例

首先看看我们的项目的结构:
这里写图片描述

就跟我们上面说的一样将业务逻辑和试图逻辑抽象成接口,然后创建他的实现类,其中IView的实现类是Activity。

(一) Model

首先就和我们上面说的一样,实体类User是要有的。然后至少有一个业务方法Login()用来从网络中读取数据。

public class User {

    private String name;
    private String pwd;

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
public interface IUserBiz {
    void login(String name, String pwd, OnLoginListener onLoginListener);
}
public interface OnLoginListener
{
    void loginSuccess(User user);

    void loginFailed();
}
public class UserBiz implements IUserBiz {
    @Override
    public void login(final String name, final String pwd, final OnLoginListener onLoginListener) {
        //子线程耗时操作
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //登录成功
                if ("admin".equals(name) && "admin".equals(pwd)) {
                    User user = new User(name,pwd);
                    onLoginListener.loginSuccess(user);
                } else {
                    onLoginListener.loginFailed();
                }
            }
        }.start();
    }
}

(二) View

我们这里是模拟登陆,有Login和Clear两个按钮,用来登陆和清除账号密码。所以我们需要清楚账号密码的方法,还有ProgressBar的显示与隐藏和对登陆这个操作的结果做出响应的方法。综上所述接口为:

public interface IUserLoginView {

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void loginSuccess(User user);

    void loginError();
}

实现类代码:

public class LoginOptimized1Activity extends AppCompatActivity implements IUserLoginView, View.OnClickListener {


    private EditText editUser;
    private EditText editPass;
    private Button btnLogin;
    private Button btnClear;
    private ProgressBar progressBar;

    private ILoginPresenter mPresenter;


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

        mPresenter = new LoginPresentCompl(this);
    }

    private void initViews() {
        editUser = (EditText) this.findViewById(R.id.et_login_username);
        editPass = (EditText) this.findViewById(R.id.et_login_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login_login);
        btnClear = (Button) this.findViewById(R.id.btn_login_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);

        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login_clear:
                mPresenter.clear();
                break;
            case R.id.btn_login_login:
                mPresenter.login(editUser.getText().toString(), editPass.getText().toString());
                break;
        }
    }

    @Override
    public void clearUserName() {
        editUser.setText("");
    }

    @Override
    public void clearPassword() {
        editPass.setText("");
    }

    @Override
    public void showLoading() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        progressBar.setVisibility(View.INVISIBLE);
    }

    @Override
    public void loginSuccess(User user) {
        Toast.makeText(this, "Login Success", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginError() {
        Toast.makeText(this, "Login Error", Toast.LENGTH_SHORT).show();
    }

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

}

(三) Presenter

Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。

public interface ILoginPresenter {
    void login(String name, String pwd);
    void clear();
}

实现类:

public class LoginPresentCompl implements ILoginPresenter {

    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    public LoginPresentCompl(IUserLoginView userLoginView) {
        this.userLoginView = userLoginView;
        this.userBiz = new UserBiz();
    }

    @Override
    public void login(String name, String pwd) {
        userLoginView.showLoading();
        userBiz.login(name, pwd, new OnLoginListener() {
            @Override
            public void loginSuccess(final User user) {
                //需要在UI线程执行
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.hideLoading();
                        userLoginView.loginSuccess(user);
                    }
                });
            }

            @Override
            public void loginFailed() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.hideLoading();
                        userLoginView.loginError();
                    }
                });
            }
        });
    }

    @Override
    public void clear() {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }

}

解决OOM和内存泄漏

好了以上基本上就是一个简单的MVP模拟登陆的例子了。但是以上的代码还有一个问题,就是可能会造成内存泄漏,上面我们说MVP的优点的时候也说了。MVP能避免OOM和内存泄漏。接下来我们就利用弱引用结合Activity的生命周期来解决这个问题。

首先我们定义一个Present的基类,让其他presenter继承此类。

public class BasePresenter<V> {
    protected Reference<V> mViewRef;

    public void attachView(V view) {
        mViewRef = new WeakReference<V>(view);
    }

    public V getView() {
        if (mViewRef == null) {
            return null;
        }
        return mViewRef.get();
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

}

这是一个泛型类,参数为V为要引用的View.
attachView()方法的作用是创建View的虚引用,此方法放在View初始化过程中,例如onCreate()方法中
detachView()方法的作用是销毁View的虚引用,此方法放在View销毁化过程中,例如onDestroy()方法中
getView()放用来获取View

然后我们定义一个View的基类,让其他的View继承此类。

public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
    protected T mPresenter;

    @Override
    protected void onCreate(Bundle arg) {
        super.onCreate(arg);
        mPresenter = createPresenter();
        mPresenter.attachView((V) this);
    }

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

    protected abstract T createPresenter();
}

这也是一个泛型基类,第一个泛型参数V:代表当前处理的View,第二个泛型参数T:代表当前使用的Presenter,要求此presenter必须继承至 BasePresenter.要求每个Activity必须继承至此类。在onCreate()和onDestroy()中的处理逻辑已经在讲解 Prestener 时候已经说过。createPresenter() 用来创建presenter实例,便于在View中通过其调用presenter的方法,此方法是abstract的,这就要求MVPBaseActivity子类必须实现此方法。

最后讲到这里基本就结束了。其他的有什么问题的地方可以去查看本文的源码去解决。

github源码地址

写在最后(参考文章)

https://blog.csdn.net/lmj623565791/article/details/46596109
http://kaedea.com/2015/10/11/android-mvp-pattern/
https://blog.csdn.net/ShuSheng0007/article/details/77938378
https://www.jianshu.com/p/9a6845b26856

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值