Android:MVP模式

简介

关于MVP的介绍网上有很多,这里不再累述,虽然大家的实现方式也许各不相同,但是有一些基本的共识:

  • Model:数据模型;
  • View:用户界面,一般对应于Activity、Fragment;
  • Presenter:作为View和Model的桥梁,实现两者解耦。

MVP的核心是:将View中与UI无关的部分逻辑转移到Presenter中。

其依赖关系如下:
View与Presenter相互依赖,Presenter依赖于Model,View和Model解耦。
这里写图片描述


实例

经典的登陆例子
这里写图片描述

Model

数据来源,一般是从网络或者数据库异步加载通过接口回调。我们从内存模拟,然后通过handler#postDelayed来模拟加载过程。

public class LoginModel {

    public static Map<String, String> maps = new HashMap<>();
    private Handler mHandler = new Handler();

    static {
        maps.put("key1", "password1");
        maps.put("key2", "password2");
        maps.put("key3", "password3");
    }

    public void getPassword(final String username, final OnGetPasswordListener listener) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                String password = maps.get(username);
                if (password == null) {
                    listener.onGetError();
                } else {
                    listener.onGet(password);
                }
            }
        }, new Random().nextInt(2000));
    }

    public void addUser(final String username, final String password, final OnRegisterListener registerListener) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    if (LoginModel.maps.containsKey(username)) {
                        registerListener.onUserExist();
                    } else {
                        LoginModel.maps.put(username, password);
                        registerListener.onSuccessful();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    registerListener.onNetError();
                }
            }
        }, new Random().nextInt(2000));
    }

    public interface OnGetPasswordListener {
        void onGet(String password);

        void onGetError();
    }

    public interface OnRegisterListener {
        void onSuccessful();

        void onUserExist();

        void onNetError();
    }
}

View

首先明确一个View的职责,以LoginActivity为例,他要做的是:

  • 用户点击登陆:

    1. 检查账号密码合法性;
    2. 登陆过程给用户提示;
    3. 如果登陆成功,用户提示消失,执行其他操作;
    4. 如果登陆失败,用户提示消失,执行其他操作;
  • 用户点击清除:清除已经输入的内容

  • 用户点击注册:跳转到注册界面

我们将其抽象为一个接口:

public interface ILoginView {

    void usernameIllegal();

    void passwordIllegal();

    void showProgress();

    void dismissProgress();

    void loginSuccessfully();

    void loginUnsuccessfully();

    void clearState();

}

然后View去实现上诉接口:

public class LoginActivity extends AppCompatActivity implements ILoginView {

    @InjectView(R.id.et_username)
    EditText mEtUsername;
    @InjectView(R.id.et_password)
    EditText mEtPassword;
    @InjectView(R.id.btn_login)
    Button mBtnLogin;
    @InjectView(R.id.btn_clear)
    Button mBtnClear;
    private ProgressDialog mProgressDialog;

    private LoginPresenter mLoginPresenter = new LoginPresenter(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        mProgressDialog = new ProgressDialog(this);
        mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLoginPresenter.doLogin(mEtUsername.getText().toString(), mEtPassword.getText().toString());
            }
        });

        mBtnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLoginPresenter.doClear();
            }
        });
    }


    @Override
    public void showProgress() {
        mProgressDialog.show();
    }

    @Override
    public void dismissProgress() {
        mProgressDialog.dismiss();
    }

    @Override
    public void loginSuccessfully() {
        Toast.makeText(LoginActivity.this, "login successfully", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginUnsuccessfully() {
        Toast.makeText(LoginActivity.this, "login unsuccessfully, please check", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void clearState() {
        mEtUsername.getText().clear();
        mEtPassword.getText().clear();
    }

    @Override
    public void usernameIllegal() {
        mEtUsername.setError("should not be empty");
    }

    @Override
    public void passwordIllegal() {
        mEtPassword.setError("should not be empty");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        if (id == R.id.action_settings) {
            startActivity(new Intent(this, RegisterActivity.class));
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

可以看到上诉的View只执行UI相关的操作,没有任何逻辑,逻辑都转移到了Prensenter。有一些比较简单的逻辑可以不必转移到Presenter Layer,比如clearState,这一逻辑完全可以直接在View层调用而不经过Presenter回调。还有输入合法性判断,只判断其是否为空也是很简单的逻辑,同样可以在View层实现。总而言之,简单的逻辑可以在View中处理。


Presenter

最后也是最关键的,在Presenter中连接View与Model,承担View的大部分逻辑。

public class LoginPresenter {

    private ILoginView mLoginView;

    private LoginModel mModel = new LoginModel();

    public LoginPresenter(ILoginView loginView) {
        mLoginView = loginView;
    }


    public void doLogin(String inputUsername, final String inputPassword) {
        if (inputUsername.equals("")) {
            mLoginView.usernameIllegal();
            return;
        }
        if (inputPassword.equals("")) {
            mLoginView.passwordIllegal();
            return;
        }
        mLoginView.showProgress();
        mModel.getPassword(inputUsername, new LoginModel.OnGetPasswordListener() {
            public void onGet(String password) {
                if (inputPassword.equals(password)) {
                    mLoginView.loginSuccessfully();
                } else {
                    mLoginView.loginUnsuccessfully();
                }
                mLoginView.dismissProgress();
            }

            public void onGetError() {
                mLoginView.loginUnsuccessfully();
                mLoginView.dismissProgress();
            }
        });
    }

    public void doClear() {
        mLoginView.clearState();
    }
}

比较

假设没有使用MVP架构,常见的做法会是:

mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
    if (mEtUsername.getText().toString().equals("")) {
        usernameIllegal();
        return;
    }
    if (mEtPassword.getText().toString().equals("")) {
        passwordIllegal();
        return;
    }
    LoginModel loginModel = new LoginModel();
    showProgress();
    loginModel.getPassword(mEtUsername.getText().toString(), new LoginModel.OnGetPasswordListener() {
        public void onGet(String password) {
            if (mEtPassword.getText().toString().equals(password)) {
                loginSuccessfully();
            } else {
                loginUnsuccessfully();
            }
            dismissProgress();
        }

        public void onGetError() {
            loginUnsuccessfully();
            dismissProgress();
        }
    });
}
});

这种写法,虽然一开始写起来会非常方便,但是越到后面越臃肿而难以维护。所以使用MVP架构会使得层次清晰,View与Model解耦,代码也较为整洁。
假设现在我要测试一下逻辑是否有误,如果是上诉写法,除了run一下工程然后手动点击测试之外,仿佛无从下手。使用了MVP架构,将View职责抽象成接口后,我们写起单元测试会比较方便。

最后,很多文章会把Model和Prensenter也抽象出一个接口,目前我不知道这样做有什么好处,所以我倾向于Model和Presenter先不需要抽象接口,需要时再进行重构。

项目地址:
https://github.com/leelit/android-mvp-pattern

参考:
http://blog.csdn.net/vector_yi/article/details/24719873
https://segmentfault.com/a/1190000003927200
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0227/2503.html
http://blog.csdn.net/lmj623565791/article/details/46596109
https://github.com/antoniolg/androidmvp


更新

使用这套MVP组合开发了一段时间,有几点新的认识,为了不和上面的弄混乱,新开一个标题。

IPresenter接口
public interface IPresenter {
    void doDestroy();
}

每个Presenter都应该实现这个接口,并将View依赖清除。这样做是为了防止内存泄露,如果你的Model使用RxJava,一般最好调用unsubscribe()以取消网络请求。

@Override
public void doDestroy() {
    mLoginView = null;
    // if you do with RxJava, you should call unsubscribe() here
}

最后当View回调onDestroy()时调用Presenter的doDestroy();

@Override
protected void onDestroy() {
    super.onDestroy();
    mLoginPresenter.doDestroy();
}
AS开发步骤

AS环境下使用以下步骤进行MVP开发会非常便捷

1、创建一个空的接口,IView;
2、View(Fragment、Activity)实现IView;

public class MyActivity extends Activity implements IView

3、在Presenter里面聚合IView;

public class MyPresenter {
    private IView mView;

    public MyPresenter(IView view) {
        mView = view;
    }
}

4、在View里面组合Presenter;

public class MyActivity extends Activity implements IView {
    private MyPresenter mPresenter = new MyPresenter(this);
}

5、当View需要Model的逻辑时,用mPresenter来调用一个尚未实现的方法;

if(needModel) {
    mPresenter.doSomeThing();
}

6、按alt + enter,MyPresenter里面会自动添加这个方法,然后进行业务处理;

7、当业务需要回调View时,用mView来调用一个尚未实现的方法;

if(needView) {
    mView.showSomeThing();
}

8、按alt + enter,IView接口将自动添加这个方法,并且View也会实现这个方法。

项目地址:
https://github.com/leelit/android-mvp-pattern

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值