前言
今天给大家带来的是一个mvp框架,nucleus。这个框架是由国外的一位大神konmik搭建的,对mvp进行了一个封装,那么就先给大家说说MVP模式。
MVP模式
MVP是从经典的模式MVC演变而来,不同的是MVP中Model层并不会直接与View层有任何关系,而是通过Presenter来进行交互,至于MVP的好处这里我也不多陈述,网上有很多文章都对MVP的优点进行介绍,下面就先以传统的MVP给大家写一个登录的Demo感受一下。
MVP结构传统MVP登录
目录结构首先给大家看一下目录结构,当然,不同的人有不同的分包习惯,有的以功能模块分包,有的以传统分包,这个根据自己喜好来就行,这里是以Contract模式来分包,使用contract的好处是因为我们有一个插件,叫MVPHelper,一键生成Presenter和Model层的代码。
LoginContract
LoginContract结构LoginActivity
首先用LoginActivity实现LoginContract.View并重写里面的方法,然后在onCreate中拿到对应Presenter层的引用,大家可以看到这里没有任何的逻辑判断,只有界面相关的操作。
package cn.lxt.mvpdemo.view.activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import cn.lxt.mvpdemo.R;
import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.presenter.LoginPresenter;
//首先要实现LoginContract.View
public class LoginActivity extends AppCompatActivity implements LoginContract.View, View.OnClickListener {
private ProgressDialog mProgressDialog;
private EditText mEtName;
private EditText mEtPsw;
private LoginContract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//拿到Presenter层的引用
mPresenter = new LoginPresenter(this);
mEtName = (EditText) findViewById(R.id.et_name);
mEtPsw = (EditText) findViewById(R.id.et_psw);
Button button = (Button) findViewById(R.id.btn_login);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
//点击登录
String name = mEtName.getText().toString().trim();
String psw = mEtPsw.getText().toString().trim();
mPresenter.login(name, psw);
break;
}
}
//登陆成功后会走这里
@Override
public void loginSuccess() {
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
}
//登陆失败后会走这里
@Override
public void loginFailed(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void showDialog() {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("登陆中");
mProgressDialog.show();
}
@Override
public void hideDialog() {
if (mProgressDialog != null && mProgressDialog.isShowing())
mProgressDialog.dismiss();
}
}
LoginPresenter
用MvpHelper这个插件一键生成该类,同样的重写方法,在构造中拿到对应Model层的引用。
package cn.lxt.mvpdemo.presenter;
import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.model.LoginModel;
import cn.lxt.mvpdemo.view.activity.LoginActivity;
/**
* Created by Administrator on 2017/8/17 0017.
*/
//这个类是自动生成的
public class LoginPresenter implements LoginContract.Presenter {
private final LoginActivity loginActivity;
private final LoginContract.Model mModel;
public LoginPresenter(LoginActivity loginActivity) {
this.loginActivity = loginActivity;
mModel = new LoginModel(this);
}
@Override
public void login(String name, String psw) {
//通知view层显示dialog
loginActivity.showDialog();
//通知model层调用登录
mModel.login(name, psw);
}
@Override
public void loginSuccess() {
//登陆成功之后的回调
loginActivity.hideDialog();
loginActivity.loginSuccess();
}
@Override
public void loginFailed(String msg) {
//登陆失败之后的回调
loginActivity.hideDialog();
loginActivity.loginFailed(msg);
}
}
LoginModel
这个类也是插件一键生成的,同样的,在构造中拿到Presenter的引用,在model层处理业务逻辑。
package cn.lxt.mvpdemo.model;
import android.text.TextUtils;
import java.util.concurrent.TimeUnit;
import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.presenter.LoginPresenter;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
/**
* Created by Administrator on 2017/8/17 0017.
*/
public class LoginModel implements LoginContract.Model {
private final LoginPresenter loginPresenter;
public LoginModel(LoginPresenter loginPresenter) {
this.loginPresenter = loginPresenter;
}
@Override
public void login(final String name, final String psw) {
//这里我们做逻辑处理
if (TextUtils.isEmpty(name)) {
loginPresenter.loginFailed("姓名不允许为空");
} else if (TextUtils.isEmpty(psw)) {
loginPresenter.loginFailed("密码不允许为空");
} else {
//这里模拟登录过程
Observable.timer(2, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
loginPresenter.loginSuccess();
}
});
}
}
}
演示
登录演示效果很明显,view层没有任何的逻辑处理,有的只是页面的显示,presenter层只是负责view层和model层的交互,model层只是负责逻辑的处理,分工明确,当然,这只是一个最基础的MVP模式,还有很多情况没有考虑到,那么接下来给大家带来一套成熟的MVP框架,也就上面说的nucleus。
Nucleus
Nucleus 是一个实现MVP+Rxjava的框架,首先给大家说一下他的好处
- 它支持在View/Fragment/Activity的Bundle中保存/恢复Presenter的状态,一个Presenter可以保存它的请求参数到bundles中,以便之后重启它们
- 它允许一个View实例持有多个Presenter对象
- 快速实现View和Presenter的绑定
- 提供线程的基类以便复用
- 支持在进程重启后,自动重新发起请求,在onDestroy方法中,自动退订RxJava订阅
- 使用相当简单
那么我们首先来看看他的整体目录结构
nucleus结构整体逻辑都在第一个,下面两个是对v7和v4的扩展。
nucleus整体结构nucleus使用(基于rxjava,不会rxjava的小伙伴请看我之前的文章)
第一步,添加依赖
compile 'info.android15.nucleus5:nucleus:5.0.0-beta1'
compile 'info.android15.nucleus5:nucleus-support-v4:5.0.0-beta1'
compile 'info.android15.nucleus5:nucleus-support-v7:5.0.0-beta1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
第二步,继承
用你项目中的BaseActivity去继承他的NucleusAppCompatActivity,比如
继承关系这样你所有继承BaseActivity的Activity就都可以使用了。
用你项目中的BaseFragment去继承他的NucleusSupportFragment
继承关系用你的BasePresenter继承他的RxPresenter
继承关系到此为止准备工作已经做完了。
第三步,网络请求
还是以刚才的登录为例子,不同的是这里用到了Retrofit+RxJava的模式做的联网请求,我这里用的是真实的网络请求,不是模拟的了。
首先,在LoginActivity中增加一个注解,里面的参数传入对应的Presenter类,直接上代码。
package cn.lxt.nucleusdemo.view.activity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import cn.lxt.nucleusdemo.R;
import cn.lxt.nucleusdemo.base.BaseActivity;
import cn.lxt.nucleusdemo.presenter.LoginPresenter;
import nucleus5.factory.RequiresPresenter;
//添加一个注解,参数为这个类所对应的Presenter
@RequiresPresenter(LoginPresenter.class)
public class LoginActivity extends BaseActivity<LoginPresenter> implements View.OnClickListener {
private EditText mEtName;
private EditText mEtPsw;
private Button mButton;
@Override
protected void initLayout() {
setContentView(R.layout.activity_main);
}
@Override
protected void initView() {
mEtName = (EditText) findViewById(R.id.et_name);
mEtPsw = (EditText) findViewById(R.id.et_psw);
mButton = (Button) findViewById(R.id.btn_login);
}
@Override
protected void initData() {
}
@Override
protected void initClickListener() {
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
String name = mEtName.getText().toString().trim();
String psw = mEtPsw.getText().toString().trim();
//注意,当你点击登录时,可以调用getPresenter()拿到对应P层的引用
getPresenter().login(this, name, psw);
break;
}
}
public void loginSuccess() {
Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
}
public void loginFailed() {
Toast.makeText(this, "登陆失败", Toast.LENGTH_SHORT).show();
}
}
重头戏来了,Presenter里面的逻辑代码
还是同样的,继承你自己的BasePresenter,不同的是你可以使用nucleus帮你封装的生命周期方法了,重写onCreate方法。
在你需要调用请求的地方,调用start()方法,参数为一个int值,你可以自己定义。
package cn.lxt.nucleusdemo.presenter;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import cn.lxt.nucleusdemo.api.Service;
import cn.lxt.nucleusdemo.base.BasePresenter;
import cn.lxt.nucleusdemo.response.LoginResponse;
import cn.lxt.nucleusdemo.retrofit.RetrofitUtil;
import cn.lxt.nucleusdemo.view.activity.LoginActivity;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.BiConsumer;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import nucleus5.presenter.Factory;
/**
* Created by Administrator on 2017/8/17 0017.
*/
public class LoginPresenter extends BasePresenter<LoginActivity> {
private String name, psw;
private final int REQUSET_LOGIN = 0;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
//调用该方法开始一个请求,第一个参数就是你start里面传入的int值,第二个参数就是一个Factory,所有的联网逻辑都写在里面,这里结合了Retrofit,第三个参数就是请求成功的回调,第四个参数就是请求失败的回调
restartableLatestCache(REQUSET_LOGIN, new Factory<Observable<LoginResponse>>() {
@Override
public Observable<LoginResponse> create() {
return RetrofitUtil.getRetrofit(context)
.create(Service.class)
.login(name, psw, "APP")
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
((LoginActivity) context).showDialog();
}
})
.observeOn(AndroidSchedulers.mainThread());
}
}, new BiConsumer<LoginActivity, LoginResponse>() {
@Override
public void accept(LoginActivity loginActivity, LoginResponse loginResponse) throws Exception {
loginActivity.loginSuccess();
loginActivity.hideDialog();
//请求成功之后调用stop(),参数为start里面传入的参数
stop(REQUSET_LOGIN);
}
}, new BiConsumer<LoginActivity, Throwable>() {
@Override
public void accept(LoginActivity loginActivity, Throwable throwable) throws Exception {
loginActivity.loginFailed();
loginActivity.hideDialog();
//请求失败之后调用stop(),参数为start里面传入的参数
stop(REQUSET_LOGIN);
}
});
}
public void login(Context context, String name, String psw) {
if (TextUtils.isEmpty(name)) {
Toast.makeText(context, "用户名不能为空", Toast.LENGTH_SHORT).show();
} else if (TextUtils.isEmpty(psw)) {
Toast.makeText(context, "密码不能为空", Toast.LENGTH_SHORT).show();
} else {
this.context = context;
this.name = name;
this.psw = psw;
start(REQUSET_LOGIN);
}
}
}
演示
这里不是模拟的登录了,而是真实的登录
Nucleus登录成功
Nucleus源码浅析
到了这里,可能很多人就说,我做这么多道理有什么用呢?别着急,接下来带大家看看他的源码。
首先,我们看看继承他的NucleusAppCompatActivity做了什么事
在这个类中有一句这样的代码,拿到了我们定义的presenter层对象
然后把这个presenter对象与我们的activity的生命周期进行绑定,大家可以看到,在onSaveInstanceState里面,他帮我们保存了数据,这也是上面说的它支持在View/Fragment/Activity的Bundle中保存/恢复Presenter的状态,一个Presenter可以保存它的请求参数到bundles中,以便之后重启它们
再看RxPresenter又做了一些什么事
首先我们找到了start方法,首先不管有没有开启,nucleus先帮我们停止了任务,然后在把我们的id添加到了requested这个集合中,然后把id和我们即将开启的Factory放到了hashmap中进行对应,也就是第二个参数中的Factory,这也是为什么我们能通过stop(id)或者start(id)来控制的原因
接下来看看我们在onCreate中调用的restartableLatestCache方法
restartable方法就是把id和factory进行绑定,然后开启了rxjava并将onNext和onError回调给我们,也就是我们传入的第三个和第四个参数。
总结
今天的Nucleus框架使用就给大家讲到这里,如果本文中有任何错误欢迎指出,同时也欢迎喜欢这个框架的朋友一起讨论,我们一起学习一起进步。
网友评论