MVP架构:
- View:对应于Activity,负责处理用户事件和视图部分的展示,它可能是Activity或者Fragment类。
- Model:业务逻辑和实体模型,负责访问数据。数据可以是远端的Server API,本地数据库或者SharedPreference等
- Presenter:负责完成Model和View之间的交互
MVP的优点:
减少了Activity的职责,简化了Activity中的代码,将复杂的逻辑代码提取到了Presenter中进行处理。降低了代码的耦合度,方便测试,使后期维护更容易。
图解:
从MVP图解中,很容易可以看出Model与View之间的交互由Presenter完成,而Presenter与View之间的交互则是通过接口的。Presenter 从View中获取所需要的参数交给Model去执行,执行过程中需要的反馈操作及结果再由View做出对应的显示(在下面demo代码中能体现出来)。Model 中主要是写一些耗时操作和对数据库的处理等操作。
Retrofit 2.0
Retrofit是一个类型安全的网络请求库,它和OKHttp共同出自于square公司。Retrofit是对OKHttp做了一层封装,把网络请求都交给了OKHttp,使我们只需要通过简单的配置就能使用retrofit来进行网络请求了。
Retrofit简化了网络请求流程,它可以通过注解(GET、POST、PUT、DELETE、HEAD)来配置请求参数,通过工厂来生成CallAdapter,Converter,可以使用不同的请求适配器CallAdapter(Rxjava、java等)[这篇文章中的demo用的是RxJava作为请求适配器],也可以使用不同的反序列化工具Converter(json、xml等)。它基于OKHttp做的封装使解耦更加彻底。
RxJava+ Retrofit 不多说,直接在例子中分析和介绍用法。
RxJava + Retrofit + MVP 登录例子demo
效果图如图所示:
首先,看下项目结构:
实现思路:
(一) Model
model中主要是存放数据处理和业务逻辑等,从效果图可以看出至少有一个登录login()方法和一个实体类user,那么就先完成login()方法和User类:
User
类:
public class User extends BaseResponseBean {
/**
"status": 1,
"uid": "241",
"upwd": "123456",
"type": "2",
"token": "e1eeffb878dc660226599be2abcdfb34",
"errcode": "00001",
"errinfo": "登录成功"
*/
public String status;
public String uid;
public String upwd;
public String type;
public String token;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUpwd() {
return upwd;
}
public void setUpwd(String upwd) {
this.upwd= upwd;
}
@Override
public String toString() {
return "User{" +
"status='" + status + '\'' +
", uid='" + uid + '\'' +
",upwd = '" + upwd + '\'' +
", type='" + type + '\'' +
", token='" + token + '\'' +
'}';
}
}
ILoginModel
接口:
public interface ILoginModel {
/**
* 网络请求 --- 登录
*/
void login(String account, String password, String type);
}
ILoginModel 接口实现类 LoginModel
:
public class LoginModel1 implements ILoginModel {
private ResponseOnListener mLoginResponseOnListener;
public LoginModel1(ResponseOnListener mLoginResponseOnListener)
{
this.mLoginResponseOnListener = mLoginResponseOnListener;
}
@Override
public void login(String account, String password,String type) {
RequestUrl service = ServiceGenerator.createService(RequestUrl.class);
Observable<User> stringObservable = service.LoginVery(CodeUtils.encodeToString(account),
CodeUtils.encodeToString(password),
CodeUtils.encodeToString(type));
stringObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<User>() {
@Override
public void call(User s) {
Log.e("loginResponse1", s.toString());
User loginResponse = s;
if (loginResponse.errcode.equals("00001")) {
mLoginResponseOnListener.onSuccess(loginResponse);
} else {
mLoginResponseOnListener.onFailure(loginResponse.errinfo);
}
}
});
}
}
在login方法中我们可以看到,它网络链接是用retrofit+RxJava来实现的。Retrofit本身对RxJava提供了支持。使用方法如下:
首先添加:
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'io.reactivex:rxjava:1.1.5'
然后创建Retrofit,代码如下:
/**
* 封装的Retrofit工具类
*/
public class ServiceGenerator {
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS);
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://zthdwl.com/wap.php/")
.addConverterFactory(FastJsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
Retrofit retrofit = builder.client(httpClient.build()).build();
return retrofit.create(serviceClass);
}
}
定义RequestUrl:
public interface RequestUrl {
@FormUrlEncoded
@POST("Public/loginAction")
Observable<User> LoginVery(@Field("phone") String phone, @Field("password") String password, @Field("type") String type);
}
然后就在login()方法中调用,login()方法中完成了Retrofit和RxJava的结合。
(二) View
View对应于activity,负责view的绘制及与用户的交互,它和presenter之间通过接口之间交互。故这里要定义一个接口类ILoginView
,那么ILoginView
接口中需要定义哪些方法呢?根据效果图,考虑全面的话不外乎有这几种方法:
- 得到账号和密码
getAccount()
和getPassword()
- 登录失败的时候清除密码
clearPwd()
- 判断用户名和密码是否可用
isUserNameVaild(String userName)
和isPasswordVaild(String password)
- 若用户名和密码可用改变登录按钮背景为高亮
changeLoginBtn()
- 登录成功后跳转主界面方法
startHomeActivity()
- 登录进行中时的progressbar的显示
showLoading()
和hideLoading
综上,ILoginView
接口完整代码如下:
public interface ILoginView extends IBaseView {
/**
* 得到账号和密码
*/
String getAccount();
String getPassword();
/**
* 登录失败清除密码
*/
void clearPwd();
/**
* 判断用户名和密码是否可用
*/
Boolean isUserNameVaild(String userName);
Boolean isPasswordVaild(String password);
/**
* 改变按钮状态
*/
void changeLoginBtn();
void resetLoginBtn();
/**
* 登录成功后跳转
*/
void startHomeActivity();
/**
* 找回密码
*/
void findPasswordActivity();
/**
* 立即注册
*/
void startRegistActivity();
}
view对应于activity,那么ILoginView
接口的实现类就是 LoginAct
了,LoginAct
类:
public class LoginAct extends BaseActivity implements ILoginView {
private LoginPresenter mLoginPresenter;
@InjectView(R.id.login_top_bar)
TopBarView loginTopBar;
@InjectView(R.id.login_et_userName)
EditText loginEtUserName;
@InjectView(R.id.login_et_userPwd)
ShowPwdEditText loginEtUserPwd;
@InjectView(R.id.login_bt_login)
Button loginBtLogin;
@InjectView(R.id.login_tv_findPwd)
TextView loginTvFindPwd;
@InjectView(R.id.login_tv_regist)
TextView loginTvRegist;
private Subscription subscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_login);
ButterKnife.inject(this);
mLoginPresenter = new LoginPresenter(this);
initEvents();
}
private void initEvents() {
Observable<CharSequence> observableUserName = RxTextView.textChanges(loginEtUserName);
Observable<CharSequence> observablePassword = RxTextView.textChanges(loginEtUserPwd);
/**
*用combineLastest将ObservableEmail和ObservablePassword联合起来进行验证,
* 若两者都满足条件,改变登录按钮为高亮背景和可点击状态
* 可以在combineLatest继续添加其他条件
* 相当于几个addTextChangedListener(TextWatcher())的累加作用
*/
Observable.combineLatest(observableUserName, observablePassword, new Func2<CharSequence, CharSequence, Boolean>() {
@Override
public Boolean call(CharSequence userName, CharSequence password) {
return isUserNameVaild(userName.toString()) && isPasswordVaild(password.toString());
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Boolean verify) {
if (verify) {
changeLoginBtn();
} else {
resetLoginBtn();
}
}
});
/**用RxBinding的RxView和throttleFirst防止抖动点击*/
RxView.clicks(loginBtLogin)
.throttleFirst(2, TimeUnit.MILLISECONDS)//两秒内只取一个点击事件
.subscribe(new Action1<Object>() {
@Override
public void call(Object o) {
/**做一些点击按钮的响应操作*/
mLoginPresenter.login(getAccount(), getPassword(), "2");
}
});
}
/**
* 得到用户账号
*/
@Override
public String getAccount() {
return loginEtUserName.getText().toString();
}
/**
* 得到密码
*/
@Override
public String getPassword() {
return loginEtUserPwd.getText().toString();
}
/**
* 清除密码
*/
@Override
public void clearPwd() {
loginEtUserPwd.setText("");
}
/**
* 验证用户手机号是否正确
*/
@Override
public Boolean isUserNameVaild(String userName) {
/**
移动:134、135、136、137、138、139、150、151、157(TD)、158、159、187、188
联通:130、131、132、152、155、156、185、186
电信:133、153、180、189、
总结起来就是第一位必定为1,第二位必定为3或5或8,其他位置的可以为0-9
*/
String telRegex = "[1][358]\\d{9}";//"[1]"代表第1位为数字1,"[358]"代表第二位可以为3、5、8中的一个,"\\d{9}"代表后面是可以是0~9的数字,有9位。
return userName.matches(telRegex);
}
/**
* 验证密码是否可用
*/
@Override
public Boolean isPasswordVaild(String password) {
/**
* 密码最小不少于四位数字
*/
return password.length() > 4;
}
/**
* 改变登录按钮的背景为高亮
*/
@Override
public void changeLoginBtn() {
loginBtLogin.setEnabled(true);
loginBtLogin.setBackgroundResource(R.drawable.btn_shape1);
}
/**
* 恢复登录按钮初始状态
*/
@Override
public void resetLoginBtn() {
loginBtLogin.setEnabled(false);
loginBtLogin.setBackgroundResource(R.drawable.btn_shape);
}
/**
* 跳转到主页面
*/
@Override
public void startHomeActivity() {
startActivity(new Intent().setClass(LoginAct.this, HomeAct.class));
}
/**
* 跳转到找回密码页面
*/
@Override
public void findPasswordActivity() {
}
/**
* 跳转到注册页面
*/
@Override
public void startRegistActivity() {
}
@Override
public void showLoading(String msg) {
}
}
(三) Presenter
Presenter负责完成Model与View之间的交互,从view中获取需要的参数,交给model去执行业务逻辑处理,执行过程中需要的反馈及结果再通过Presenter交给view,让view做出反应和展示等。
经分析,LoginPresenter中有登录网络处理以及登录成功和失败的两种处理,LoginPresenter
类中代码如下:
public class LoginPresenter implements ResponseOnListener{
private LoginModel1 iLoginModel;
private ILoginView iLoginView;
public LoginPresenter(ILoginView iLoginView) {
this.iLoginView = iLoginView;
iLoginModel = new LoginModel1(this);
}
/**登录处理
* 显示加载进度条
* 登录网络请求
* */
public void login(String account, String password,String type)
{
iLoginView.showLoading();
iLoginModel.login(account, password,type);
}
/**登录成功的回调方法*/
@Override
public void onSuccess(Object tClass) {
iLoginView.hideLoading();
iLoginView.startHomeActivity();
Log.e("object",tClass.toString());
}
/**登录失败处理*/
@Override
public void onFailure(String throwable) {
iLoginView.hideLoading();
iLoginView.clearPwd();
Toast.makeText(iLoginView.getContext(),throwable.toString(),Toast.LENGTH_SHORT).show();
Log.e("Throwable",throwable);
}
}
MVP+Retrofit+RxJava结合的分析思路和方法就如上所示。
刚入行菜鸟的拙见,如有错误欢迎批评指出,定及时纠正,谢谢!