相信大家都或多或少的了解过MVP(全称:Model View Presenter)架构,MVP是从MVC演变过来的。MVC模式中,Activity即充当VIew又充当了Controller,使得Activity内的代码十分臃肿,难以进行维护,而MVP模式就很好的解决了这个问题。学习MVP架构对于自身的面向对象的理解也能更加深刻。
上面这张图片就可以很清晰的看出MVP各层之间的交互,其中:
- Model层:主要用来提供数据,比如从网上获取数据
- View层:即视图层,主要用来展示视图
- Presenter层:即逻辑层,View层与Model层的桥梁,处理一些逻辑操作
MVP与MVC最大的区别在与,你无法在View层看到任何逻辑处理,MVP中所有的逻辑处理都交由Presenter层来处理,View层只需要负责展示视图即可,有Presenter层的存在,实现了View层与Model层的解耦
下面我们就动手搭建一个MVP架构的登录功能吧,下面所用到的登录API是wanAndroid网站所提供的开放API
1. 项目结构
先来看看这个登录Demo的项目结构
2. 创建契约类
因为MVP有个很明显的缺点就是,接口数量太多,而契约类就很好的帮我们整理好这些接口
契约类的代码如下:
public interface IContract {
//View层
public interface ILoginView {
String getUserName();
String getPassword();
void onLoginSuccess(String msg);
void onLoginFails(String msg);
}
//Presenter层
public interface ILoginPresenter {
void requestMsg(String userName, String password);
void destroyView();
}
//Model层
public interface ILoginModel {
public interface CallBack{
void onSuccess(String msg);
void onFailed();
}
void requestData(String userName, String password, IContract.ILoginModel.CallBack callBack);
}
}
很明显的可以看出来,IContract接口托管了MVP的三层接口,其中CallBack接口用于数据获取完成之后的回调
3. View层
View层,即我们的LoginActivity,主要负责的是view的操作,所以我们的LoginActivity需要实现ILoginView接口,重写其中的方法
public class LoginActivity implements IContract.ILoginView {
@Override
public String getUserName() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public void onLoginSuccess(String msg) {
}
@Override
public void onLoginFails(String msg) {
}
}
之后我们的View层应该与Presenter层进行关联,所以我们的LoginActivity中应该持有Presenter的引用。
Activity中所有的代码如下:
public class LoginActivity extends AppCompatActivity implements IContract.ILoginView {
private EditText userNameET;
private EditText passwordET;
private Button loginButton;
private IContract.ILoginPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
loginPresenter = new LoginPresenter(this);
userNameET = findViewById(R.id.username_et);
passwordET = findViewById(R.id.password_et);
loginButton = findViewById(R.id.login_button);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginPresenter.requestMsg(getUserName(), getPassword());
}
});
}
@Override
public String getUserName() {
return userNameET.getText().toString();
}
@Override
public String getPassword() {
return passwordET.getText().toString();
}
@Override
public void onLoginSuccess(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, "用户 : " + msg + " 登录成功", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onLoginFails(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (loginPresenter != null) {
loginPresenter.destroyView();
}
}
}
可以看到我们没有在Activity中做任何逻辑相关的处理,我们把获取到的用户名和密码传到Presenter层,然后在重写的方法中更行UI就可以了,这里只是简单的给了一个Toast
4. Presenter层
同样的,首先先实现P层的接口,重写里面的方法。
public class LoginPresenter implements IContract.ILoginPresenter {
@Override
public void requestMsg(String userName, String password) {
}
@Override
public void destroyView() {
}
}
因为P层是V层与M层的桥梁,所以P层需要持有V层和M层的引用,为了防止内存泄漏,我们一个弱引用,这些引用赋值操作我们把它放在构造方法中
private IContract.ILoginView iLoginView;
private IContract.ILoginModel iLoginModel;
private WeakReference<IContract.ILoginView> iLoginViewWeakReference;
private WeakReference<IContract.ILoginModel> iLoginModelWeakReference;
private User user;
public LoginPresenter(IContract.ILoginView iLoginView) {
this.iLoginView = iLoginView;
iLoginModel = new LoginModel();
iLoginViewWeakReference = new WeakReference<>(iLoginView);
iLoginModelWeakReference = new WeakReference<>(iLoginModel);
}
我们看看重写的方法做了哪些操作,先判断View层传过来的用户名和密码是否为空,如果为空,就通知View层做相应的处理,在这里我们是传一个错误信息过去给View层做显示。如果用户民和密码都不为空,那么我们就把数据传到Model层去请求数据,然后通过CallBack这个接口回调把结果返回给View层
@Override
public void requestMsg(String userName, String password) {
if (userName.equals("") || userName.isEmpty()) {
iLoginView.onLoginFails("用户名不能为空");
} else if (password.equals("") || password.isEmpty()) {
iLoginView.onLoginFails("密码不能为空");
} else {
iLoginModel.requestData(userName, password, new IContract.ILoginModel.CallBack() {
@Override
public void onSuccess(String msg) {
parseJSONByGson(msg);
if (user.getErrorCode() == -1) {
iLoginView.onLoginFails(user.getErrorMsg());
} else {
iLoginView.onLoginSuccess(user.getData().getUsername());
}
}
@Override
public void onFailed() {
iLoginView.onLoginFails("网络错误");
}
});
}
}
destroyView就是销毁View的方法
@Override
public void destroyView() {
iLoginViewWeakReference.clear();
iLoginModelWeakReference.clear();
}
Presenter层所有代码如下:
public class LoginPresenter implements IContract.ILoginPresenter {
private IContract.ILoginView iLoginView;
private IContract.ILoginModel iLoginModel;
private WeakReference<IContract.ILoginView> iLoginViewWeakReference;
private WeakReference<IContract.ILoginModel> iLoginModelWeakReference;
private User user;
public LoginPresenter(IContract.ILoginView iLoginView) {
this.iLoginView = iLoginView;
iLoginModel = new LoginModel();
iLoginViewWeakReference = new WeakReference<>(iLoginView);
iLoginModelWeakReference = new WeakReference<>(iLoginModel);
}
@Override
public void requestMsg(String userName, String password) {
if (userName.equals("") || userName.isEmpty()) {
iLoginView.onLoginFails("用户名不能为空");
} else if (password.equals("") || password.isEmpty()) {
iLoginView.onLoginFails("密码不能为空");
} else {
iLoginModel.requestData(userName, password, new IContract.ILoginModel.CallBack() {
@Override
public void onSuccess(String msg) {
parseJSONByGson(msg);
if (user.getErrorCode() == -1) {
iLoginView.onLoginFails(user.getErrorMsg());
} else {
iLoginView.onLoginSuccess(user.getData().getUsername());
}
}
@Override
public void onFailed() {
iLoginView.onLoginFails("网络错误");
}
});
}
}
@Override
public void destroyView() {
iLoginViewWeakReference.clear();
iLoginModelWeakReference.clear();
}
public void parseJSONByGson(String jsonData) {
Gson gson = new Gson();
user = gson.fromJson(jsonData, User.class);
}
}
5. Model层
Model层,也就是负责请求数据的,同样先实现Model层接口,重写里面的方法
public class Model implements IContract.ILoginModel {
@Override
public void requestData(String userName, String password, CallBack callBack) {
}
}
之后我们通过OKHttp进行登录请求,通过post请求,把用户名和密码传到网上进行查询,并将结果返回
Model层的所有代码如下:
public class LoginModel implements IContract.ILoginModel {
@Override
public void requestData(String userName, String password, final IContract.ILoginModel.CallBack callBack) {
String url = "https://www.wanandroid.com/user/login";
RequestBody requestBody = new FormBody.Builder()
.add("username", userName)
.add("password", password)
.build();
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callBack.onFailed();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String msg = response.body().string();
callBack.onSuccess(msg);
}
});
}
}
6. 总结
- 首先创建一个契约类来管理MVP三层接口
- 让Activity继承View层接口,重写方法,并持有Presenter层的引用
- 编写Presenter层实现类,重写方法做相关逻辑处理,将处理后的数据传到Model层,需持有View层和Model层的引用
- 编写Model层实现类,重写方法进行数据获取,然后将结果返回Presenter层
- Presenter层对Model层返回的结果再进行相关逻辑处理
- 没有问题后,把结果返回View层展示