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

Android MVP模式实战解析

程序员文章站 2024-03-15 19:24:36
...

前言

大家都知道Android原生开发是以MVC(Model-View-Controller)模式,实际上开发中,我们会发现视图层-逻辑层-数据层并没有清晰的分离,例如Activity和Fragment中可能混杂着对界面、逻辑、数据的编写代码,尤其后期的修改与扩展真的让我们开发人员头疼!
我们想寻求一个更好的开发模式,能做到散耦合、易扩展、易维护的特点,那么我们来介绍一下MVP(Model-View-Presenter)模式。MVP设计概念就是分离各层,各持其职,你会发现Activity和Fragment只负责视图编码,把业务逻辑抽离出来,交给Persenter层来负责,整体结构更加清晰。

MVP

网络上对MVP实现方式有多种,MVP模式是概念,公司或个人都没有确切的标准,可以按自己理解总结一套标准,下面介绍一下我们上个项目的设计MVP方案:
Android MVP模式实战解析

  • View :负责绘制UI元素、与用户进行交互。
  • View interface :View实现的接口,通过接口回调,Persenter层对View层交互。
  • Presenter :View层与Model层交互的中间纽带,处理业务逻辑。
  • Model :负责存储、检索、操纵数据。

在MVP模式中,Persenter层是中间纽带,他负责V与M之间的协调,图中可以看到View与Model不会有任何联系,都是通过Persenter调用的。
View调用Persenter的方法,Persenter通过IView接口回调View。

Android MVP模式实战解析

View与Persenter是一对一的关系,Persenter与Model是一对多的关系。
data包括:数据库,网络数据,传感器,第三方。

View层:

/**
 * Created by Administrator on 2018/3/12.
 * 登录
 */
public class LoginActivity extends BaseActivity implements LoginImpl {

    LoginPersenter loginPersenter;

    @BindView(R.id.login_edit_account)
    EditText loginEditAccount;
    @BindView(R.id.login_edit_password)
    EditText loginEditPassword;
    @BindView(R.id.login_text_prompt)
    TextView loginTextPrompt;
    @BindView(R.id.login_text_forget)
    TextView loginTextForget;
    @BindView(R.id.login_text_login)
    TextView loginTextLogin;
    @BindView(R.id.login_text_sms)
    TextView loginTextSms;
    @BindView(R.id.login_text_register)
    TextView loginTextRegister;
    @BindView(R.id.login_img_passwordshow)
    ImageView loginImgPasswordshow;
    @BindView(R.id.login_layout_passwordshow)
    RelativeLayout loginLayoutPasswordshow;
    private boolean isPasswordShow = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);
        init();
    }

    private void init() {
        loginPersenter = new LoginPersenter(this);
        loginPersenter.requestPermissions();
    }

    @OnClick({R.id.login_text_forget, R.id.login_text_login, R.id.login_text_sms, R.id.login_text_register,R.id.login_layout_passwordshow})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.login_text_forget:
                startActivity(ForgotPasswordActivity.class);
                break;
            case R.id.login_text_login:
                String username = loginEditAccount.getText().toString();
                String password = loginEditPassword.getText().toString();
                loginPersenter.Login(username, password);
                break;
            case R.id.login_text_sms:
                startActivity(SmsLoginActivity.class);
                finish();
                break;
            case R.id.login_text_register:
                startActivity(RegisterActivity.class);
                break;
            case R.id.login_layout_passwordshow:
                if (!isPasswordShow) {
                    loginImgPasswordshow.setImageResource(R.mipmap.ic_login_password_open);
                    isPasswordShow = true;
                    loginEditPassword.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
                    loginEditPassword.setSelection(loginEditPassword.length());//将光标移至文字末尾
                } else {
                    loginImgPasswordshow.setImageResource(R.mipmap.ic_login_password_close);
                    isPasswordShow = false;
                    loginEditPassword.setInputType(
                            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                    loginEditPassword.setTypeface(Typeface.DEFAULT);
                    loginEditPassword.setSelection(loginEditPassword.length());//将光标移至文字末尾
                }
                break;
        }
    }


    @Override
    public void loginInfoShow(String str) {
        loginTextPrompt.setText(str);
    }

    @Override
    public void loginSuccess() {
        startActivity(MainActivity.class);
        finish();
    }

}

可以看到Activity中全是UI绘制,并没有业务逻辑与数据。

View interface接口:

/**
 * Created by Administrator on 2018/3/19.
 */

public interface LoginImpl extends BaseImpl{

    /**
     * 信息提示
     * @param str
     */
    void loginInfoShow(String str);

    /**
     * 登录成功
     */
    void loginSuccess();
}

定义了两个接口

Persenter层:

**
 * Created by Administrator on 2018/3/19.
 * 登录逻辑
 */

public class LoginPersenter extends BasePersenter {

    NetRequest netRequest;

    MApplication app = MApplication.getApp();

    LoginImpl loginImpl;

    public LoginPersenter(LoginImpl loginImpl) {
        this.loginImpl = loginImpl;
        netRequest = new NetRequest();
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     */
    public void Login(String username, String password) {
        if (username.trim().equals("") || password.trim().equals("")) {
            loginImpl.loginInfoShow("手机号和密码不能为空");
            return;
        }
        netRequest.Login(username, password).subscribe(new Observer<LoginBean>() {
            @Override
            public void onSubscribe(Disposable d) {
                loginImpl.showDialog();
            }

            @Override
            public void onNext(LoginBean value) {
                if (value.getCode() == 0) {
                    Log.i(TAG, "登录成功");
                    Log.i(TAG, value.getResult().getToken());
                    Hawk.put(SharedKey.Token, value.getResult().getToken());
                    loginImpl.loginSuccess();
                } else {
                    loginImpl.loginInfoShow(value.getMsg());
                    Log.i(TAG, "登录失败" + value.getMsg());
                }
            }

            @Override
            public void onError(Throwable e) {
                Log.i(TAG, e.getMessage());
                loginImpl.dismissDialog();
                ToastUtils.showLong(app.getmContext(), "访问失败");
            }

            @Override
            public void onComplete() {
                loginImpl.dismissDialog();
            }

        });
    }

}

Persenter中Login()实现了访问网络数据,访问成功与失败然后回调View接口,给用户提示,完成整个交互。
UI只负责界面部分编码,提供对外使用接口就行了,而业务逻辑也无需了解界面怎么实现,二者可以做到真正的分离。

一个View对应一个Persenter,在很多环境下我们需要多个View对应一个Persenter,我们用Java的多态来写Persenter层,看代码:

**
 * Created by Administrator on 2018/3/19.
 * 登录逻辑
 */

public class LoginPersenter extends BasePersenter {

    NetRequest netRequest;

    MApplication app = MApplication.getApp();

    LoginImpl loginImpl;

    TelLoginImpl telLoginImpl;

    /**
     * 用户登录
     *
     */
    public LoginPersenter(LoginImpl loginImpl) {
        this.loginImpl = loginImpl;
        netRequest = new NetRequest();
    }

    /**
     * 手机登录
     *
     */
    public LoginPersenter(TelLoginImpl telLoginImpl) {
        this.telLoginImpl = telLoginImpl;
        netRequest = new NetRequest();
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     */
    public void login(String username, String password) {
        if (username.trim().equals("") || password.trim().equals("")) {
            loginImpl.loginInfoShow("手机号和密码不能为空");
            return;
        }
        netRequest.Login(username, password).subscribe(new Observer<LoginBean>() {
            @Override
            public void onSubscribe(Disposable d) {
                loginImpl.showDialog();
            }

            @Override
            public void onNext(LoginBean value) {
                if (value.getCode() == 0) {
                    Log.i(TAG, "登录成功");
                    Log.i(TAG, value.getResult().getToken());
                    Hawk.put(SharedKey.Token, value.getResult().getToken());
                    loginImpl.loginSuccess();
                } else {
                    loginImpl.loginInfoShow(value.getMsg());
                    Log.i(TAG, "登录失败" + value.getMsg());
                }
            }

            @Override
            public void onError(Throwable e) {
                Log.i(TAG, e.getMessage());
                loginImpl.dismissDialog();
                ToastUtils.showLong(app.getmContext(), "访问失败");
            }

            @Override
            public void onComplete() {
                loginImpl.dismissDialog();
            }

        });
    }

    /**
     * 手机登录
     *
     * @param tel
     * @param code 
     */
    public void telLogin(String tel,String code) {

        ...
    }

    /**
     * 获取验证码
     *
     * @param tel
     */
    public void getVerificationCode(String tel){

        ...
    }

}

我们在Persenter添加了一个构造方法,这样就可以调用不同View的接口。
意见没有特殊情况,尽量做到一个View对应一个Persenter,这样减少P层逻辑,增加可维护性。