参考:android架构设计—mvp模式封装
很简单,M:数据, V:界面, P:一个使唤数据(M)和界面(V)干活的大管家。
特点:在P的管理下,P可以直接支配V和M做一些事情。但是V,与M,你可以想象成两者可以是八竿子打不着的关系,生生世世不相见,全靠P在中间呼风唤雨,左手控制着V,右手操纵着M,就这样用一双神来之手将界面调剂成你想要的样子。。。
总结起来看起来很简单的样子哈。
但是,没有一个例子显然是不太有说服力的,MVP到底是个什么样子呢?代码袭来。。。
首先看下结构:
接口:M方面
package com.example.forev.mvpprojectdemo.model;
/**
* Created by forev on 2018/7/8.
* 定义一个model总接口
*/
public interface IModel {
}
接口: V 方面:
package com.example.forev.mvpprojectdemo.view;
/**
* Created by forev on 2018/7/8.
* 定义一个view总接口
*/
public interface IView {
}
父类:Presenter方面,表现持有关系。
package com.example.forev.mvpprojectdemo.presenter;
import com.example.forev.mvpprojectdemo.model.IModel;
import com.example.forev.mvpprojectdemo.view.IView;
import java.lang.ref.WeakReference;
/**
* Created by forev on 2018/7/8.
* 所有presenter的父类,因为presenter会持有View 以及Model部分,所以
* 索性就写到总父类里面去吧
*/
public class PresenterFather {
protected IModel mIModel;
//此处View个人感觉最好用一个弱引用。
protected WeakReference mViewReference;
}
好,接口相关的关系定义完了,接下来就看看MVP 怎么表现的。
M实际逻辑:
package com.example.forev.mvpprojectdemo.model;
import com.example.forev.mvpprojectdemo.model.Lisentener.LoginLisentener;
/**
* Created by yayali on 2018/7/8.
* model 负责的是数据以及业务逻辑
*/
public class LoginMode implements IModel {
//model 负责数据以及业务逻辑。
private String mUserName = "yayali";
private String mPassWord = "123";
public void login(String username, String password, LoginLisentener lisentener) {
if (lisentener == null) {
return;
}
if (mUserName.equals(username) && mPassWord.equals(password)){
lisentener.onSeccess();
} else {
lisentener.onFails();
}
}
}
以上可以看出,这个Model里面只有数据和登录的逻辑,就算算出了结果,也用一个回调,回调出去了。写的是相当封闭了。与View无任何关系。
View实际逻辑,首先先写一个根据距离的情景得出的接口。例如总得知道用户在用户名密码编辑框里输入了什么信息吧。这个是必须在View里面获取的。然后,登录成功或者失败总得能给个提示之类的吧。这些具体的提示也是在View里面需要做的。那么写了这几个方法,很明显就是给Presenter来调用的。因为Presenter会作为一个桥梁,来控制View和model之间的联系。从Model那里计算出登录结果,然后控制下面的接口,做相关调用。
package com.example.forev.mvpprojectdemo.view;
/**
* Created by forev on 2018/7/8.
* 写了一个接口继承自IView,这点关系很重要
*/
public interface ILoginView extends IView {
String getUserName();
String getPassword();
void onLoginSeccess();
void onLoginFails();
}
首先写了一个关于View的接口!为什么呢?因为Presenter是个桥接角色,那么它势必会调用Model的业务逻辑,得出结果,然后再操纵其持有的View来进行View上的变更。但是界面呀,毕竟都在Activity里面呆着呢。实际界面变化在Activity里做最合适的。所以就专门写了接口,专门获取用户输入信息,以及界面变化回调。
下面真正的View:
package com.example.forev.mvpprojectdemo.activity;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.forev.mvpprojectdemo.R;
import com.example.forev.mvpprojectdemo.presenter.LoginPresenter;
import com.example.forev.mvpprojectdemo.view.ILoginView;
/**
* LoginActivity展现了一个界面,尤其是它实现了ILoginView,代表他是MVP中的V
*/
public class LoginActivity extends AppCompatActivity implements ILoginView{
private EditText mUserNameEdit;
private EditText mPasswordEdit;
private Button mLoginBtn;
private LoginPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setView();
setData();
}
private void setView() {
this.mUserNameEdit = findViewById(R.id.login_act_edit_user_name);
this.mPasswordEdit = findViewById(R.id.login_act_edit_user_pass);
this.mLoginBtn = findViewById(R.id.login_act_btn_login);
mLoginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//隐藏软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
mPresenter.login();
}
});
}
private void setData() {
this.mPresenter = new LoginPresenter(this);
}
@Override
public String getUserName() {
return mUserNameEdit.getText().toString();
}
@Override
public String getPassword() {
return mPasswordEdit.getText().toString();
}
@Override
public void onLoginSeccess() {
Toast.makeText(getApplicationContext(), "登陆成功!", Toast.LENGTH_LONG).show();
}
@Override
public void onLoginFails() {
Toast.makeText(getApplicationContext(), "登录失败!", Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
//记得在销毁的时候断掉引用链,养成良好的习惯
this.mPresenter = null;
}
}
看吧,界面上没有任何具体的业务逻辑,业务逻辑相关的都交给Presenter去处理了,View顶多就是在回调里面做做界面响应啥的。
Presenter:连接View和Model的桥梁
package com.example.forev.mvpprojectdemo.presenter;
import com.example.forev.mvpprojectdemo.model.Lisentener.LoginLisentener;
import com.example.forev.mvpprojectdemo.model.LoginMode;
import com.example.forev.mvpprojectdemo.view.ILoginView;
import com.example.forev.mvpprojectdemo.view.IView;
import java.lang.ref.WeakReference;
/**
* Created by forev on 2018/7/8.
*/
public class LoginPresenter extends PresenterFather {
public LoginPresenter(ILoginView loginView) {
this.mIModel = new LoginMode();
this.mViewReference = new WeakReference(loginView);
}
public void login() {
if (mIModel != null && mViewReference != null && mViewReference.get() != null) {
ILoginView loginView = (ILoginView) mViewReference.get();
String name = loginView.getUserName();
String passWord = loginView.getPassword();
loginView = null;
//此时LoginListener作为匿名内部类是持有外部类的引用的。
((LoginMode)mIModel).login(name, passWord, new LoginLisentener() {
@Override
public void onSeccess() {
if (mViewReference.get() != null) {
((ILoginView)mViewReference.get()).onLoginSeccess();
}
}
@Override
public void onFails() {
if (mViewReference.get() != null) {
if (mViewReference.get() != null) {
((ILoginView)mViewReference.get()).onLoginFails();
}
}
}
});
}
}
}
这样看Presenter就是根据用户的操作行为来决定怎么调业务逻辑,并且根据业务逻辑的结果再决定怎么调用View。
接下来是Lisentener:
package com.example.forev.mvpprojectdemo.model.Lisentener;
/**
* Created by forev on 2018/7/8.
*/
public interface LoginLisentener {
void onSeccess();
void onFails();
}
代码大概就是这样的,重点就是持有关系,以及各自的逻辑要拎得清。。presenter就得 持有View和Model,并且View就是界面展现,Model就是纯业务逻辑。View和Model无任何联系。那么为什么要这么写呢?有什么优点呢?
优点: 单一职责, Model, View, Presenter只处理单一逻辑
解耦:Model层的修改和View层的修改互不影响
面向接口编程,依赖抽象:Presenter和View互相持有抽象引用,对外隐藏内部实现细节。
可能存在的问题:
Model进行一步操作的时候,获取结果通过Presenter会传到View的时候,出现View引用的空指针异常。
Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。