最近在学习Android的MVP架构,在网上找了许多资料都没有一个清晰的认识,偶然看到简书上骆驼骑士前辈的文章,对MVP的实现过程有了一个较为清晰的认识。后又研究了一下Google官方Demo,分别对两种实现方式做了一个Demo进行对比。
对于Android MVP实现方式,借用一下骆驼骑士前辈的分析
目前常见的MVP在Android里的实践有两种解决方案:
1.直接将Activity看作View,让它只承担View的责任。
2.将Activity看作一个MVP三者以外的一个Controller,只控制生命周期。
下面我们来讲述一下这两种方式的实现过程。
第一种:Activity承担MVP中V的角色
我们以一个不需要网络请求的登录作为示例进行实现。
在这个MVP架构中,我们需要以下几个文件:
类型 | 文件名 |
---|---|
Model 接口 | ILoginModel |
View 接口 | ILoginView |
Presenter 接口 | ILoginPresenter |
Model 接口实现 | ILoginModelImpl |
View 接口实现 | LoginActivity |
Presenter 接口实现 | ILoginPresenterImpl |
首先,我们先定义一个实体类UserEntity,模拟服务器返回的数据。
UserEntity.java
public class UserEntity {
/**
* 响应码
*/
private int code;
/**
* 提示信息
*/
private String msg;
/**
* 用户名
*/
private String user_name;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public String getUser_name() {
return user_name;
}
public void setCode(int code) {
this.code = code;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
}
接下来,我们需要编写ILoginModel
、ILoginView
、ILoginPresenter
三个接口
ILoginModel.java
import com.example.mvp.login.CallBack;
public interface ILoginModel {
/**
* 登录相关的业务逻辑在此处理
*
* @param username 账号
* @param password 密码
* @param callBack 回调接口
*/
void login(String username, String password, CallBack callBack);
}
ILoginView.java
import com.example.mvp.login.entity.UserEntity;
public interface ILoginView {
/**
* 登录成功的回调显示
*
* @param userEntity 实体类
*/
void showLoginSuccessMsg(UserEntity userEntity);
/**
* 登录失败的回调显示
*
* @param errorMsg 失败信息
*/
void showLoginFailMsg(String errorMsg);
}
ILoginPresenter.java
public interface ILoginPresenter {
/**
* 连接 Model 和 View 的登录方法
*
* @param username 账号
* @param password 密码
*/
void login(String username, String password);
}
因为在Model中用到了一个回调接口CallBack,所以我们接下来定义一下这个接口:
CallBack.java
import com.example.mvp.login.entity.UserEntity;
public interface CallBack {
/**
* 登录成功的回调
*
* @param userEntity 实体类
*/
void onSuccess(UserEntity userEntity);
/**
* 登录失败的回调
*
* @param errorMsg 错误信息
*/
void onFail(String errorMsg);
}
至此,MVP架构的准备工作都做好了,接下来我们开始实现这些接口,将各个接口连接起来,实现一个登录功能。
大家知道,Model是承担了数据处理和业务逻辑的功能,所以我们来实现一下ILoginModel
这个接口。在这个类中,我们假设当账号为 user,密码为 123456 时即登录成功。
LoginModelImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;
public class LoginModelImpl implements ILoginModel {
@Override
public void login(String username, String password, CallBack callBack) {
if (username.equals("user") && password.equals("123456")) {
// 模拟服务器返回的数据
UserEntity userEntity = new UserEntity();
userEntity.setCode(1);
userEntity.setMsg("登录成功");
userEntity.setUser_name("Vip User");
callBack.onSuccess(userEntity);
} else {
callBack.onFail("用户名或密码错误");
}
}
}
接下来我们实现一下作为中间联系人的ILoginPresenter
接口:
LoginPresenterImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.ILoginModel;
import com.example.mvp.login.view.ILoginView;
public class LoginPresenterImpl implements ILoginPresenter {
/**
* View
*/
private ILoginView view;
/**
* Model
*/
private ILoginModel model;
/**
* 构造方法
*
* @param view 实现接口的类
* @param model 实现接口的类
*/
public LoginPresenterImpl(ILoginView view, ILoginModel model) {
this.view = view;
this.model = model;
}
@Override
public void login(String username, String password) {
//主动调用model中的login方法实现登录
model.login(username, password, new CallBack() {
@Override
public void onSuccess(UserEntity userEntity) {
//如果登录成功,将登录成功的数据传递给View进行显示
view.showLoginSuccessMsg(userEntity);
}
@Override
public void onFail(String errorMsg) {
//如果登录失败,将登陆失败的数据传递给View显示
view.showLoginFailMsg(errorMsg);
}
});
}
}
最后一步,新建一个LoginActivity,实现ILoginView
,该activity的内容非常简单,只有两个EditText用于输入账号和密码,一个Button用于点击登录,在这里,只贴出Activity的代码:
LoginActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.LoginModelImpl;
public class LoginActivity extends AppCompatActivity implements ILoginView {
/**
* Presenter
*/
private ILoginPresenter presenter;
/**
* 登录按钮
*/
private Button btn;
/**
* 用户名输入框
*/
private EditText etName;
/**
* 密码输入框
*/
private EditText etPwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//实例化Presenter
presenter = new LoginPresenterImpl(this, new LoginModelImpl());
//绑定控件
btn = findViewById(R.id.btn);
etName = findViewById(R.id.et_name);
etPwd = findViewById(R.id.et_pwd);
//实现登录的点击事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = etName.getText().toString();
String password = etPwd.getText().toString();
//检测账号密码均不为空时调用presenter的登录方法
if ("".equals(username) || "".equals(password)) {
Toast.makeText(LoginActivity.this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
} else {
presenter.login(username, password);
}
}
});
}
@Override
public void showLoginSuccessMsg(UserEntity userEntity) {
Toast.makeText(this, userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
}
@Override
public void showLoginFailMsg(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
以上就是本例的全部代码,现在我们来整理一下整个创建流程:
- 创建一个实体类UserEntity,用于模拟服务器返回的数据;
- 创建
ILoginModel
、ILoginView
、ILoginPresenter
接口; - 创建一个
CallBack
接口,用于登录回调; - 实现M、P、V接口,创建
LoginModelImpl
、LoginPresenterImpl
、LoginActivity
,实现登录功能。
实现的效果如下:
我们再来整理一下整个登录过程是如何实现的:
- 在 LoginActivity 中声明一个 ILoginPresenter,初始化为 LoginPresenterImpl,向构造函数中传入两个参数—— LoginActivity、LoginModelImpl。
presenter = new LoginPresenterImpl(this, new LoginModelImpl());
- 点击 button,调用 presenter.login() ——
实际上调用的是 LoginPresenterImpl.login()
;
presenter.login(username, password);
- 在 LoginPresenterImpl.login() 中调用 model.login(),实现 CallBack 接口,将 CallBack 回调的数据推送给 View 进行显示 ——
实际上调用的是 LoginModelImpl.login()
;
model.login(username, password, new CallBack() {
@Override
public void onSuccess(UserEntity userEntity) {
//如果登录成功,将登录成功的数据传递给View进行显示
view.showLoginSuccessMsg(userEntity);
}
@Override
public void onFail(String errorMsg) {
//如果登录失败,将登陆失败的数据传递给View显示
view.showLoginFailMsg(errorMsg);
}
});
- 在 model.login() 中实现登录判断,将登录结果通过 CallBack 接口提交给 Presenter;
if (username.equals("user") && password.equals("123456")) {
// 模拟服务器返回的数据
UserEntity userEntity = new UserEntity();
userEntity.setCode(1);
userEntity.setMsg("登录成功");
userEntity.setUser_name("Vip User");
callBack.onSuccess(userEntity);
} else {
callBack.onFail("用户名或密码错误");
}
- 在 LoginActivity 实现的 onLoginSuccessMsg() 、onLoginFailMsg() 中处理登陆成功和失败的提示。
@Override
public void showLoginSuccessMsg(UserEntity userEntity) {
Toast.makeText(this, userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
}
@Override
public void showLoginFailMsg(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
第二种:将Activity看作一个MVP三者以外的一个Controller,只控制生命周期
我们仍旧以一个不需要网络请求的登录作为示例进行实现。
在这个个MVP架构中,我们需要以下几个文件:
类型 | 文件名 |
---|---|
Model 接口 | ILoginModel |
View 接口 | ILoginView |
Presenter 接口 | ILoginPresenter |
Model 接口实现 | ILoginModelImpl |
View 接口实现 | LoginFragment |
Presenter 接口实现 | ILoginPresenterImpl |
Controller | LoginActivity |
大家可以看到,和上一个例子相比,我们多了一个 LoginFragment
,并且对 View 接口的实现并不是 LoginActivity
来实现了,而是改为LoginFragment
。( 其实在Google官方Demo里,View 和 Presenter 的接口是放在一个名为 Contract 的接口文件里的,此处为了更好的理解,将 View 和 Presenter 分别放置在一个文件里。)
因为这个Demo和上一个Demo并没有太大的区别,所以此处只贴出不同部分的代码片段。
LoginModelImpl.java (增加如下代码)
/**
* 生成实例
*/
private static LoginModelImpl instance = null;
public static LoginModelImpl getInstance() {
if (instance == null) {
instance = new LoginModelImpl();
}
return instance;
}
private LoginModelImpl() { }
LoginPresenterImpl.java(修改变量和构造方法)
/**
* View
*/
private ILoginView view;
/**
* Model
*/
private LoginModelImpl model;
/**
* 构造方法
*
* @param view 实现接口的类
* @param model 实现接口的类
*/
public LoginPresenterImpl(ILoginView view, LoginModelImpl model) {
this.view = view;
view.setPresenter(this);
this.model = model;
}
ILoginView.java(增加如下方法)
/**
* 设置Presenter
*
* @param presenter
*/
void setPresenter(ILoginPresenter presenter);
下面贴出 LoginActivity
和 LoginFragment
的代码供大家进行比较:
LoginActivity.java
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.mvp.R;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.model.LoginModelImpl;
public class LoginActivity extends AppCompatActivity {
/**
* Presenter
*/
LoginPresenterImpl presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 绑定Fragment
FragmentManager fragmentManager = getSupportFragmentManager();
LoginFragment loginFragment = (LoginFragment) fragmentManager.findFragmentById(R.id.frame_layout);
if (loginFragment == null) {
loginFragment = LoginFragment.newInstance();
if (fragmentManager != null && loginFragment != null) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.frame_layout, loginFragment);
transaction.commit();
}
}
//实例化Presenter
presenter = new LoginPresenterImpl(loginFragment, LoginModelImpl.getInstance());
}
}
LoginFragment.java
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.entity.UserEntity;
public class LoginFragment extends Fragment implements ILoginView {
/**
* presenter
*/
private ILoginPresenter presenter;
/**
* 登录按钮
*/
private Button btn;
/**
* 用户名输入框
*/
private EditText etName;
/**
* 密码输入框
*/
private EditText etPwd;
/**
* 生成实例
*
* @return loginFragment
*/
public static LoginFragment newInstance() {
return new LoginFragment();
}
public LoginFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_login, container, false);
//绑定控件
btn = view.findViewById(R.id.btn);
etName = view.findViewById(R.id.et_name);
etPwd = view.findViewById(R.id.et_pwd);
//实现登录的点击事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = etName.getText().toString();
String password = etPwd.getText().toString();
//检测账号密码均不为空时调用presenter的登录方法
if ("".equals(username) || "".equals(password)) {
Toast.makeText(getContext(), "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
} else {
presenter.login(username, password);
}
}
});
return view;
}
@Override
public void setPresenter(ILoginPresenter presenter) {
this.presenter = presenter;
}
@Override
public void showLoginSuccessMsg(UserEntity userEntity) {
Toast.makeText(getContext(), userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
}
@Override
public void showLoginFailMsg(String errorMsg) {
Toast.makeText(getContext(), errorMsg, Toast.LENGTH_SHORT).show();
}
}
在本例中,大家可以看到,真正的 View 角色是由 Fragment 承担的,View 相关的操作都在 Fragment 中进行,Activity 不再承担View的角色,而是独立于MVP之外,成为一个单独的个体:
每一个Activity都拥有一个Fragment来作为View,然后每个Activity也对应一个Presenter,在Activity里只处理与生命周期有关的内容,并跳出MVP之外,负责实例化Model,View,Presenter,并负责将三者合理的建立联系,承担的就是一个上帝视角
上述两种模式,一种是实际操作上的简便,将Activity看做View,直接在Activity中实例化Presenter和Model;另一种是建立一个规范且正确的依赖关系,让Activity跳出MVP,负责建立M、V、P 三者的联系,相应的,操作上也会相对复杂,每个Activity都必须有一个独立的Fragment。两种模式,大家根据自己的需要进行取舍即可。
以上呢,即是本人的一些浅薄的见解,如有不全之处,欢迎大家指正。