Android MVP模式简单介绍:以一个登陆流程为例

时间:2024-01-21 09:32:14

老的项目用的MVC的模式,最近完成了全部重构成MVP模式的工作,虽然比较麻烦,好处是代码逻辑更加清楚、简洁,流程更加清晰,对于后续版本迭代维护都挺方便。
对于一些想要学习MVP模式的同学来讲,百度搜出来的好多都没法直接转化为项目里可以直接用的东西,所以这里正好拿出自己项目里已经用了的,你们可以直接用到自己的项目里。当然,不可能把所有项目代码在这里放出来,所以就拿登陆的流程出来,这个比较合适也比较常用。

1、先看下包结构:

model:放一些bean类,以及网络处理类RetrofitManager,ServiceHelper(封装的网络请求类)等

view:放UI层需要实现的逻辑

presenter:放一些业务逻辑相关的接口及实现类

 2、进入正题

首先,以登陆流程为例,简单画下流程图:

然后开始划分对应三个层的逻辑:

presenter:作为登录页面,涉及的业务逻辑有:记住密码,登录,保存登录之后获取的Token

public interface ILoginPresenter {
   void rememberPassword(String account,String pwd);
   void login(String phoneNum,String pwd);
   void saveToken(String token);
}

LoginPresenterImpl:负责具体登陆逻辑及view层业务的调用,持有view层对象引用:iLoginView

public class LoginPresenterImpl implements ILoginPresenter {
    private static final String TAG = "LoginPresenterImpl";

    private String mMd5Pwd;
    ILoginView mILoginView;

    private Context mContext;


    public LoginPresenterImpl(ILoginView iLoginView, Context context) {
        this.mILoginView = iLoginView;
        this.mContext = context;
    }

    @Override
    public void rememberPassword(String account, String pwd) {
        SPUtils.put(mContext, "remember_password", true);
        SPUtils.put(mContext, "phoneNum", account);
        SPUtils.put(mContext, "password", pwd);
    }

    @Override
    public void login(String phoneNum, String pwd) {
        if (TextUtils.isEmpty(phoneNum)) {
            mILoginView.loginResult(false, Constant.PHONENUM_NULL);return;
        }

        if (!Utils.isMobileNO(phoneNum)) {
            mILoginView.loginResult(false, Constant.PHONENUM_FALSE);return;
        }

        if (TextUtils.isEmpty(pwd)) {
            mILoginView.loginResult(false, Constant.PWD_NULL);return;
        }

        mMd5Pwd = Utils.encrypt(pwd);
        LogUtils.d(TAG, "pwd:" + pwd + "------------ mMd5Pwd:" + mMd5Pwd);

        //判断网络是否可用
        if (!Utils.isNetAvail()) {
            mILoginView.loginResult(false, Constant.INTERNET_FAILED);
            LogUtils.d(TAG, "网络不可用");
            return;
        }

        //发起网络请求,查看手机号和密码是否正确
        ServiceHelper.callEntity(RetrofitManager.getInstance().createReq(Login.class).getLoginData(phoneNum, mMd5Pwd), LoginBean.class, new OnResponseLisner<LoginBean>() {
            @Override
            public void onSuccess(LoginBean info) {
                int mUid = info.getData().getUID();      
                String token = info.getData().getToken();
                saveToken(token);

                mILoginView.loginResult(true, String.valueOf(mUid));
            }

            @Override
            public void onError(String errorMsg) {
                mILoginView.loginResult(false, errorMsg);
            }
        });
    }

    @Override
    public void saveToken(String token) {
        if (!TextUtils.isEmpty(token)) {
            //存储String值
            SPUtils.put(mContext, "Token", token);
        }
    }

 

view:登陆结果的处理展示(由具体实现类MainActivity实现对应的方法)

public interface ILoginView {
    void loginResult(Boolean result, String msg);
}

model:服务器返回的数据bean类

public class LoginBean {
    public boolean Success;
    public int Code;
    public String ErrorMsg_zh;
    public String ErrorMsg_en;
    public DataBean Data;
    public int ServerTime;
    public String LogId;

    public static class DataBean {
        public int UID;
        public String Name;
        public String Phone;
        public String Email;
        public String FacePic;
        public String Token;

}

 

最后,看下完整的登陆页面MainActivity(ILoginView实现类)的代码:

public class LoginActivity extends BaseActivity implements ILoginView {

    @BindView(R.id.et_phoneNum)
    EditText mEtPhoneNum;

    @BindView(R.id.et_pwd)
    EditText mEtPwd;

    @BindView(R.id.iv_phoneNumClear)
    ImageView mPhoneNumClear;

    @BindView(R.id.iv_pwdClear)
    ImageView mPwdClear;

    @BindView(R.id.cb_checkbox)
    CheckBox mCheckBox;

    @BindView(R.id.btn_login)
    Button mBtnLogin;

    @BindView(R.id.avi_loading)
    AVLoadingIndicatorView mAviLoading;


    private String TAG = "LoginActivity";
    private String mPhoneNum;
    private String mPwd;
    private ILoginPresenter mILoginPresenter;


    @Override
    public int getLayoutResId() {
        return R.layout.activity_login;
    }

    @Override
    protected void init() {
        super.init();

        mILoginPresenter = new LoginPresenterImpl(this, LoginActivity.this);

        boolean isRemenber = (boolean) SPUtils.get(this, "remember_password", false);
        LogUtils.d(TAG, "isRemenber:" + isRemenber);

        if (isRemenber) {
            //将账号和密码都设置到文本中
            String account = (String) SPUtils.get(this, "phoneNum", "");
            String password = (String) SPUtils.get(this, "password", "");

            mEtPhoneNum.setText(account);
            mEtPwd.setText(password);
            mCheckBox.setChecked(true);

        }

        mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                mAviLoading.setVisibility(View.VISIBLE);

                mPhoneNum = mEtPhoneNum.getText().toString().trim();
                mPwd = mEtPwd.getText().toString().trim();

                mILoginPresenter.login(mPhoneNum, mPwd);
                if (mCheckBox.isChecked()) {
                    mILoginPresenter.rememberPassword(mPhoneNum, mPwd);
                } else {
                    SPUtils.remove(LoginActivity.this, "remember_password");
                    SPUtils.remove(LoginActivity.this, "phoneNum");
                    SPUtils.remove(LoginActivity.this, "password");
                }
            }
        });
    }

    @Override
    public void loginResult(Boolean result, String msg) {
        if (result) {
            LogUtils.d(TAG, "uid:" + msg);
            Intent intent = new Intent(this, MainActivity.class);
            intent.putExtra("uid", msg);
            startActivity(intent);
        } else {
            CustomToast.show(this, msg + " 请稍后再试!");
        }
        mAviLoading.setVisibility(View.INVISIBLE);
    }

3、总结,MVP结构图:

view层和Presenter层互相持有对方的引用,model只会被presenter层使用。

PS:觉得看了还是不太明白或是好像明白的同学可以自己亲自动手写一写,应该写完就完全可以明白了。