Android MVP模式实战解析
前言
大家都知道Android原生开发是以MVC(Model-View-Controller)模式,实际上开发中,我们会发现视图层-逻辑层-数据层并没有清晰的分离,例如Activity和Fragment中可能混杂着对界面、逻辑、数据的编写代码,尤其后期的修改与扩展真的让我们开发人员头疼!
我们想寻求一个更好的开发模式,能做到散耦合、易扩展、易维护的特点,那么我们来介绍一下MVP(Model-View-Presenter)模式。MVP设计概念就是分离各层,各持其职,你会发现Activity和Fragment只负责视图编码,把业务逻辑抽离出来,交给Persenter层来负责,整体结构更加清晰。
MVP
网络上对MVP实现方式有多种,MVP模式是概念,公司或个人都没有确切的标准,可以按自己理解总结一套标准,下面介绍一下我们上个项目的设计MVP方案:
- View :负责绘制UI元素、与用户进行交互。
- View interface :View实现的接口,通过接口回调,Persenter层对View层交互。
- Presenter :View层与Model层交互的中间纽带,处理业务逻辑。
- Model :负责存储、检索、操纵数据。
在MVP模式中,Persenter层是中间纽带,他负责V与M之间的协调,图中可以看到View与Model不会有任何联系,都是通过Persenter调用的。
View调用Persenter的方法,Persenter通过IView接口回调View。
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层逻辑,增加可维护性。
上一篇: MVP模式的认识
下一篇: junit的简单使用