MVP开发模式
- Model: 主要用于业务操作,如:网络请求,数据存储等
- Presenter: 主要用于逻辑处理,沟通 M 与 V ,尽可能不包含Android的代码
- View: 主要用于规定界面的行为
优点
- 由于Presenter层的出现,减少了View的逻辑操作和负担。这样使 View 与 Model 之间耦合度低
- 合理规划的话,模块分明,模块复用率高,便于测试
缺点
- 由于抽出了一层 Presenter,所以导致类和代码有一定的增加
- 如果不能进行合理规划的将会导致后期模块杂乱,代码冗余度高
- 纠结将 服务 和 广播 放在何处
- Presenter 会持有 View 的引用,如果不进行解绑会造成内存泄露
说到最后,其实 MVP 只是一种思想,没有什么固定的代码,固定的格式。因为多在实践中,慢慢理解和多多总结。不过在开发前一定要做好项目分析规划,切忌立马动手写代码。
MVP架构封装使用(登录功能示范)
GitHub传送门 --->MVPzzz(包含以下示例)
MVPzzz架构封装(未完成)
导包
Step 1.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
- 在所有的 repositories 中都加入上面语句,否则无法导入成功
Step 2.
dependencies {
compile 'com.github.KittoZZZ:MVPzzz:v.0.0.2'
}
包含契约类
1.建立View和Presenter的契约类
public class LoginContract {
public interface ILoginView extends IBaseView {
void LoginSuccess();
void loginFail(String msg);
}
public interface ILoginPresenter {
void toLogin(User user);
}
}
View层接口必须继承IBaseView
可以很明显的看出 View 和 Presenter 的关系
2.新建Model类(结合RxJava)
public class UserModel extends BaseModel {
public Observable<String> toLogin(User user) {
//Retrofit2
String result = "fail";
if ("zzz".equals(user.getAccount()) && "123".equals(user.getPassword())) {
result = "success";
}
return Observable.just(result);
}
}
- BaseModel中并为没有实现什么功能,只是先留出,后序可能进行一些更改
- 只是用于模拟所以并没有进行网络请求,写死数据
3.新建Presenter类实现契约类中的P层接口
public class LoginPresenter extends BasePresenter<LoginContract.ILoginView> implements ILoginContract.ILoginPresenter {
@InjectModel
private UserModel userModel;
@Override
public void toLogin(User user) {
userModel.toLogin(user)
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Exception {
if ("success".equals(result)) {
getView().LoginSuccess();
} else {
getView().loginFail(result);
}
}
});
}
}
- getView() 方法用于调用 View 层的接口方法,如:LoginSuccess(),loginFail()
- 必须要写BasePresenter泛型,LoginContract.ILoginView,否则无法调用它的方法
- 使用注解 @InjectModel 进行注入 Model 对象
4.新建Activity类实现契约类中的V层接口
public class LoginActivity extends BaseMvpActivity implements LoginContract.ILoginView {
private Button btnLogin;
private EditText etAccount;
private EditText etPassword;
@InjectPresenter
private LoginPresenter loginPresenter;
@Override
protected int setContentView() {
return R.layout.activity_login;
}
@Override
protected void initView() {
etAccount = this.findViewById(R.id.et_account);
etPassword = this.findViewById(R.id.et_password);
btnLogin = this.findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = etAccount.getText().toString().trim();
String password = etPassword.getText().toString().trim();
User user = new User(account, password);
loginPresenter.toLogin(user);
}
});
}
@Override
protected void initData() {
}
@Override
public void LoginSuccess() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
@Override
public void loginFail(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
- 继承 BaseMvpActivity 实现 LoginContract.ILoginView ,重写方法
- 使用注解 @InjectPresenter 进行注入 Presenter 对象
去除契约类(多个Presenter例子)
"根据设计原则 第一条 单一原则 LoginContract 这个类就把v和p耦合了 应该把view接口 和 presenter分离开 作为两个独立接口 然后分别由view和presenter子类实现"
上面是来自大牛的评论,经过思考确实将 V 层和 P 层的耦合了,所以思考将契约类去除的做法。
假设,在登录界面中的记住密码功能,需要在登录成功后将账号和密码进行保存在本地。下次登录的时候,直接读取显示在对应的输入框中。
- 从目录结构中可以看出多了 UserDataPresenter 与 DataModel 两个关键的类
- 主要是 V 层在初始化的时候调用 UserDataPresenter 去读取,登录成功的时候保存数据
- 由于只是个例子所以 DataModel 使用 SP 进行数据的存储
- AppContext 只是用于在 M 层获取 ApplicationContext 的工具类
LoginActivity部分代码
public class LoginActivity extends BaseMvpActivity implements ILoginView, IUserDataView {
...
@InjectPresenter
private LoginPresenter loginPresenter;
@InjectPresenter
private UserDataPresenter userDataPresenter;
...
@Override
protected void initData() {
btnLogin.setClickable(false);
userDataPresenter.loadLastData();
}
@Override
public void showLoginLoading() {
Log.i("login", "showLoginLoading: 正在登陆");
}
@Override
public void hideLoginLoading() {
Log.i("login", "hideLoginLoading: 登录结束");
}
@Override
public void loginSuccess() {
Log.i("login", "loginSuccess: 登录成功");
...
userDataPresenter.saveData(user);
}
@Override
public void loginFail(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void saveDataSuccess() {
Log.i("login", "saveDataSuccess: 保存数据成功");
...
}
@Override
public void saveDataFail(String msg) {
Log.i("login", "saveDataFail: 保存数据失败 " + msg);
}
@Override
public void readDataSuccess(User user) {
Log.i("login", "readDataSuccess: 读取数据成功");
btnLogin.setClickable(true);
if (!TextUtils.isEmpty(user.getAccount()) && !TextUtils.isEmpty(user.getPassword())) {
etAccount.setText(user.getAccount());
etPassword.setText(user.getPassword());
// loginPresenter.toLogin(user);
}
}
@Override
public void readDataFail(String msg) {
Log.i("login", "loadDataFail: 读取数据失败" + msg);
}
}
- 在界面打开的时候开始读取保存本地的数据,此时登录按钮为不可点击
- 读取成功,将登录按钮设为可点击,并且将数据现在输入框中
- 如果自动登录的话,如果满足条件(账号密码不为空)则直接调用 P 层去登录
UserDataPresenter代码
public class UserDataPresenter extends BasePresenter<IUserDataView> implements IUserDataPresenter {
@InjectModel
private DataModel dataModel;
@Override
public void saveData(User user) {
dataModel.saveUserData(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Exception {
if ("success".equals(result)) {
getView().saveDataSuccess();
} else {
getView().saveDataFail("保存失败");
}
}
});
}
@Override
public void loadLastData() {
dataModel.readUserData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<User>() {
@Override
public void accept(User user) throws Exception {
getView().readDataSuccess(user);
}
});
}
}
DataModel代码就不展示,主要理解思想,详情可以看 --->MVPzzz(包含示例)
- 上面展示的是多个 Presenter 情况使用注解来实例化,这种方式同样适用于多个 Model 的情况
- 正是这个例子,让我觉得V层与P层是一一对应的形式存在,可以说是耦合的
- 保留着契约类可以清晰的看出V和P的关系,可能是个人水平较低,我还是会选择保留契约类的做法
- 目前对于 MVP 开发模式的疑惑开始增多了.......
- Fragment 的使用与 Activity 类似