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

Android框架之MVP模式(以登录为例)

程序员文章站 2024-01-09 23:47:34
...

    Android框架现在常见的有MVC模式、MVP模式、MVVM模式。我们首先先明确一个概念:模式是指组织代码的结构方式,模式并不能提高代码的执行效率。模式是为了后续功能的扩展方便和代码的结构清晰而使用的。

    在上一篇文章中,由刚开始的把代码都写在Activity里演化出Android的MVC模式,但是在最后,我们可能还是感觉Activity中的代码有些繁杂,这是因为V层和C层的角色都由Activity来承担,要想更进一步的话,就需要我们再进一步了解MVP模式。 

MVP模式即将软件分为三个基本部分:模型(Model)、视图(View)和主持人(Presenter)(我暂且翻译成主持人)。

    模型(Model)- 用来产生数据(访问网络、数据库等)

    视图(View)- 视图层相关(界面布局控件等相关的代码:Activity、Fragment)

    主持人(Presenter)- 连接M和V的(业务逻辑相关的代码)

Android框架之MVP模式(以登录为例)

在MVC模式中,Activity同时承担着V层和C层的角色,即Activity中除了有布局控件等相关的代码还有业务逻辑相关的代码,此时我们把Activity中的业务逻辑相关的代码全部抽离出去就变成了MVP模式。在MVP模式中,Activity中只包含界面布局控件等相关的代码,不再包含业务逻辑相关的代码,Activity只承担View层的角色。并且Model层和View层不直接交互,而是由Presenter作为中间人,Presenter会持有 Model 和 View 的对象。

一、抽离业务逻辑相关代码,实现MVP模式

首先在项目中创建一个presenter包,然后创建LoginPresenter.java类,抽取Activity中的业务逻辑相关的代码就形成了登录的Presenter层:

//登录模块的业务逻辑
public class LoginPresenter {
    private static final String TAG = "LoginPresenter";

    private LoginActivity loginActivity;

    public LoginPresenter(LoginActivity loginActivity) {
        this.loginActivity = loginActivity;
    }

    public void login(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            loginActivity.showProgressBar();
            String md5Password = MD5Utils.getMd5(password);

            LoginModel loginModel = new LoginModel();
            loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
                @Override
                public void onNetResposeError(String msg) {
                    loginActivity.hideProgressBar();
                    loginActivity.showToast(msg);
                }

                @Override
                public void onNetReponseSuccess(LoginData loginData) {
                    loginActivity.hideProgressBar();
                    switch (loginData.status) {
                        case 200: //用户名未注册
                        case 201: //密码有误
                        case 203: //登录失败
                            loginActivity.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);
                            break;
                        case 202:   //登录成功
                            loginActivity.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);

                            //本地保存必要的用户信息
                            //......
                            loginActivity.jumpSuccessActivity(loginData);
                            break;
                        default:
                            loginActivity.showToast("登录出现未知异常");
                            break;
                    }
                }
            });
        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            loginActivity.showToast("手机号不能为空");
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            loginActivity.showToast("密码不能为空");
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            loginActivity.showToast("请输入正确的手机号");
            return false;
        }
        return true;
    }
}

此时,Activity就只有界面布局控件等相关的代码,即View层变成:

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "LoginActivity";

    private EditText    etPhoneNumber;
    private EditText    etPassword;
    private Button      btnLogin;
    private ProgressBar progressBar;

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

        etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
        etPassword = (EditText) findViewById(R.id.et_password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        btnLogin.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:    //登录
                String phoneNumber = etPhoneNumber.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                //login(phoneNumber, password);
                LoginPresenter loginPresenter = new LoginPresenter(this);
                loginPresenter.login(phoneNumber, password);
                break;
            default:
                break;
        }
    }

    public void showProgressBar(){
        progressBar.setVisibility(View.VISIBLE);
    }

    public void hideProgressBar(){
        progressBar.setVisibility(View.GONE);
    }
    
    public void showToast(String toast){
        Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
    }

    public void jumpSuccessActivity(LoginData loginData){
        Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
        startActivity(intent);
        //登录页面直接消失
        finish();
    }
}

Model层还和原来的一样,不变:

//Model层只用来产生数据的
public class LoginModel {
    private static final String TAG = "LoginModel";

    public void gotoLogin(String phoneNumber, String md5Password, final OnNetResponseListener listener) {
        OkHttpUtils
                .post()
                .url(Constants.URL_LOGIN)
                .addParams("phoneNumber", phoneNumber)
                .addParams("password", md5Password)
                .build()
                .execute(new StringCallback() {
                    @Override
                    public void onError(okhttp3.Call call, Exception e, int id) {
                        Log.i(TAG, "onError: ---网络访问出现异常---" + e.getMessage());
                        e.printStackTrace();
                        listener.onNetResposeError("网络访问出现异常");
                    }

                    @Override
                    public void onResponse(String response, int id) {
                        Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
                        Gson gson = new Gson();
                        LoginData loginData = gson.fromJson(response, LoginData.class);
                        listener.onNetReponseSuccess(loginData);
                    }
                });
    }

    public interface OnNetResponseListener {

        void onNetResposeError(String msg);

        void onNetReponseSuccess(LoginData loginData);
    }
}

此时LoginActivity已经分离成独立的M、V、P三层:Model层只用来产生数据,View层即Activity只包含界面布局控件等相关的代码、Presenter层负责相关业务逻辑的处理。并且Model层和View层不直接交互。

二、Presenter层适用性的扩展

    上面的例子我们使用LoginActivity作为View层,如果登录页面变成Fragment比如LoginFragment,那么不仅View层的代码需要更改,Presenter层的代码也需要修改,因为上面的LoginPresenter持的View层对象是LoginActivity,这样显然是不合适的,因为Presenter层负责业务逻辑的处理,并不用关心View层到底是Activity还是Fragment,View层的变化也不应该影响Presenter层的代码,Presenter层的代码应该是相对独立的。

此时就需要我们为View层定义一种 规则(规范)即接口,Presenter层持的View层对象是一个接口,而Presenter层并不用关注这个接口的具体实现是谁。我们新建一个view包,并创建一个ILoginView接口,用来规范登录页面相关的UI操作:

public interface ILoginView {

    public void showProgressBar();

    public void hideProgressBar();

    public void showToast(String toast);

    public void jumpSuccessActivity(LoginData loginData);
}

然后将上面LoginPresenter持的View层引用改为ILoginView接口:

//登录模块的业务逻辑
public class LoginPresenter {
    private static final String TAG = "LoginPresenter";

    private ILoginView loginView;

    public LoginPresenter(ILoginView loginView) {
        this.loginView = loginView;
    }

    public void login(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            loginView.showProgressBar();
            String md5Password = MD5Utils.getMd5(password);

            LoginModel loginModel = new LoginModel();
            loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
                @Override
                public void onNetResposeError(String msg) {
                    loginView.hideProgressBar();
                    loginView.showToast(msg);
                }

                @Override
                public void onNetReponseSuccess(LoginData loginData) {
                    loginView.hideProgressBar();
                    switch (loginData.status) {
                        case 200: //用户名未注册
                        case 201: //密码有误
                        case 203: //登录失败
                            loginView.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);
                            break;
                        case 202:   //登录成功
                            loginView.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);

                            //本地保存必要的用户信息
                            //......
                            loginView.jumpSuccessActivity(loginData);
                            break;
                        default:
                            loginView.showToast("登录出现未知异常");
                            break;
                    }
                }
            });
        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            loginView.showToast("手机号不能为空");
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            loginView.showToast("密码不能为空");
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            loginView.showToast("请输入正确的手机号");
            return false;
        }
        return true;
    }
}

此时LoginPresenter就不用关心View层的具体实现是Activity还是Fragment,只需要它们实现ILoginView这个接口并重写抽象方法即可。这样presenter层的适用性就大大增加了。甚至依据这种思路,我们也可以为Presenter创建一个接口,让LoginPresenter实现这个接口。

最终呈现的包结构应该是这样的:

Android框架之MVP模式(以登录为例)

以上就是以登录为例实现的MVP模式。

相关标签: MVP Android Login