欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅谈Android MVP模式

程序员文章站 2022-05-12 16:33:14
...

随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的展示以及与用户的交互,同时让Model只关心数据的处理,基于MVC概念的MVP(Model-View-Presenter)模式应运而生。

MVC/MVP区别

MVC模式

MVC模式的结构分为三部分,实体层的Model,视图层的View,以及控制层的Controller。
浅谈Android MVP模式

  • View:对应于布局文件
  • Model:业务逻辑和实体模型
  • Controllor:对应于Activity

看起来的确像那么回事,但是细细的想想这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller,这样一来V和C就耦合在一起了。

MVP模式

浅谈Android MVP模式
当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:

  • View 对应于Activity,负责View的绘制以及与用户交互
  • Model 依然是业务逻辑和实体模型
  • Presenter 负责完成View与Model间的交互

这就是MVP模式,现在这样的话,Activity的工作的简单了,只用来响应生命周期,复杂的逻辑代码都丢到Presenter中去完成,耦合度更低。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大不同之处。还有一点就是Presenter与View之间的交互是通过接口的(代码中会体现)

转变图

浅谈Android MVP模式

变化成:

浅谈Android MVP模式

MVP优点

  • 分离了视图逻辑和业务逻辑,降低了耦合
  • Activity只处理生命周期的任务,代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
    使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。)
  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
    MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。)
  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
    采用MVP模式,只要在当前的Activity的onDestroy里,分离Presenter对Activity的引用,就能避免 Activity Leak。)

MVP模式的应用

(1)view层描述和具体代码

负责显示数据、提供友好界面跟用户交互就行。MVP下Activity和Fragment以及View的子类体现在了这一 层,Activity一般也就做加载UI视图、设置监听再交由Presenter处理的一些工作,所以也就需要持有相应Presenter的引用。本层所需要做的操作就是在每一次有相应交互的时候,调用presenter的相关方法就行。(比如说,button点击)

public interface LoginView {

    void showProgress();

    void hideProgress();

    void setUsernameError();

    void setPasswordError();

    void navigateToHome();
}
public class LoginActivity extends Activity implements LoginView, View.OnClickListener {

    private ProgressBar progressBar;
    private EditText username;
    private EditText password;
    private LoginPresenter presenter;

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

        progressBar = (ProgressBar) findViewById(R.id.progress);
        username = (EditText) findViewById(R.id.username);
        password = (EditText) findViewById(R.id.password);
        findViewById(R.id.button).setOnClickListener(this);

        presenter = new LoginPresenterImpl(this); //获取presenter对象
    }

    @Override
    protected void onDestroy() {
        presenter.onDestroy(); //离Presenter对Activity的引用,避免OOM
        super.onDestroy();
    }

    //实现LoginView接口
    @Override
    public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideProgress() {
        progressBar.setVisibility(View.GONE);
    }

    @Override
    public void setUsernameError() {
        username.setError(getString(R.string.username_error));
    }

    @Override
    public void setPasswordError() {
        password.setError(getString(R.string.password_error));
    }

    @Override
    public void navigateToHome() {
        startActivity(new Intent(this, MainActivity.class));       
        finish();
    }

    //登录业务逻辑交由presenter处理
    @Override
    public void onClick(View v) {
        presenter.validateCredentials(username.getText().toString(), password.getText().toString());
    }

}

(2)presenter层描述和具体代码

Presenter扮演着view和model的中间层的角色。获取model层的数据之后构建view层;也可以收到view层UI上的反馈命令后分发处理逻辑,交给model层做业务操作。它还可以决定View层的各种操作。

public interface LoginPresenter {

    void validateCredentials(String username, String password);

    void onDestroy();
}
/**
 * Class Note:
 * 1 完成presenter的实现。这里面主要是Model层和View层的交互和操作。
 * 2  presenter里面还有个OnLoginFinishedListener,
 * 其在Presenter层实现,给Model层回调,更改View层的状态,
 * 确保 Model层不直接操作View层。
 */
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {

    private LoginView loginView;
    private LoginModel loginModel;

    public LoginPresenterImpl(LoginView loginView) {
        this.loginView = loginView; //获取了View的引用
        this.loginModel = new LoginModelImpl(); //构建Model对象
    }

    //实现LoginPresenter接口
    @Override
    public void validateCredentials(String username, String password) {
        if (loginView != null) {
            loginView.showProgress();
        }

        loginModel.login(username, password, this); //具体登录逻辑业务交给Model层实现,将this传给Model,用来做回调
    }

    @Override
    public void onDestroy() {
        loginView = null; //切断View层的引用,防止OOM
    }

    //实现OnLoginFinishedListener接口,给Model层回调
    @Override
    public void onUsernameError() {
        if (loginView != null) {
            loginView.setUsernameError();
            loginView.hideProgress();
        }
    }

    @Override
    public void onPasswordError() {
        if (loginView != null) {
            loginView.setPasswordError();
            loginView.hideProgress();
        }
    }

    @Override
    public void onSuccess() {
        if (loginView != null) {
            loginView.navigateToHome();
        }
    }
}

(3)model层描述和具体代码

model层提供我们想要实现的业务逻辑,这里是展示在view层的数据和具体登陆业务逻辑处理。

public interface LoginModel {
    void login(String username, String password, OnLoginFinishedListener listener);
}
/**
 * Class Note:延时模拟登陆(2s),如果名字或者密码为空则登陆失败,否则登陆成功
 */
public class LoginModelImpl implements LoginModel {

    @Override
    public void login(final String username, final String password, final OnLoginFinishedListener listener) {

        new Handler().postDelayed(new Runnable() {
            @Override public void run() {
                boolean error = false;
                if (TextUtils.isEmpty(username)){
                    listener.onUsernameError(); //model层里面回调listener函数
                    error = true;
                }
                if (TextUtils.isEmpty(password)){
                    listener.onPasswordError();
                    error = true;
                }
                if (!error){
                    listener.onSuccess();
                }
            }
        }, 2000);
    }
}

最后看看登陆的回调接口

public interface OnLoginFinishedListener {

    void onUsernameError();

    void onPasswordError();

    void onSuccess();
}

demo的代码流程:(请参考下面的类图)

  • Activity做了一些UI初始化的东西并需要实例化对应LoginPresenter的引用和实现 LoginView的接口,监听界面动作
  • 登陆按钮按下后即接收到登陆的事件,在onClick里接收到即通过LoginPresenter的引用把它交给LoginPresenter处理。LoginPresenter接收到了登陆的逻辑就知道要登陆了
  • 然后LoginPresenter显示进度条并且把逻辑交给我们的Model去处理,也就是这里面的LoginModel,(LoginModel的实现类LoginModelImpl),同时会把OnLoginFinishedListener也就是LoginPresenter自身传递给我们的Model(LoginModel)。
  • LoginModel处理完逻辑之后,结果通过OnLoginFinishedListener回调通知LoginPresenter
  • LoginPresenter再把结果返回给view层的Activity,最后activity显示结果

请参考这张类图:
浅谈Android MVP模式

注意:presenter里面还有个OnLoginFinishedListener,其在Presenter层实现,给Model层回调,更改View层的状态,确保 Model层不直接操作View层。

相关标签: mvp