第一、基本概念
MVP是模型(Model)、视图(View)、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。
模型(Model):负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;
视图(View):负责界面数据的展示,与用户进行交互;
主持人(Presenter):相当于协调者,是模型与视图之间的桥梁,将模型与视图分离开来。
MPV 是从经典的MVC模式演变过来的,其基本思路都是相通的。其中M是model模型,提供业务数据;P和MVC中的C担当的角色相似,是Presenter主持人,进行逻辑处理。V是View视图,显示页面操作。
在MVP中View并不直接使用Model,它们之间的通信是通过Presenter 来进行的,所有的交互都发生在Presenter这个类内部,在代码中可以发现Presenter类既有View对象,又有Model对象,但是在Activity中是没有Model对象的,是使用Presenter对象来调用Model对象;Activity是要有View对象和Presenter对象的,Presenter对象怎么来呢,这个是要在构造方法中去创建。View对象怎么来呢,其实是Activity实现这个View,这个Activity就相对于这个View对象,并且需要重写View接口的方法!而且还有一个重要的就是要用这个Presenter来绑定Activity(其实是为了绑定View接口的方法)。这样在Presenter用model处理完数据后,Presenter就会调用View接口的方法,实现页面的操作。
如下图所示,View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有Viwe层以及Model层的Interface的引用,而View层持有Presenter层Interface的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载完毕,最后Presenter层再调用View层的接口将加载后的数据展示给用户。这就是MVP模式的整个核心过程。
这样分层的好处就是大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。另一方面Model层可以封装复用,可以极大的减少代码量。
注意:之前我也是有这样一个疑问:view和ui这两个不是都是放视图的包吗?
其实是这样的:MVP中的View的包下放的并不是activity这样的视图类,而是activity视图类的操作,比如这个登录的activity下,会有获得用户的账号,密码的需要,也有点击登录的需要等等,这就需要定义这几个操作的方法,其实这里view包下定义的是接口类,里面的方法都是抽象方法,定义这个页面需要的操作,具体页面内容的实现还是要在Activity中来!
2.mvp包下的文件设计
(1)model包名下的类
设计数据处理的逻辑,这里一般还设计一个内部接口类,让presenter对象可以设置监听并得到数据。因为好多数据处理都是需要子线程的,不能马上就返回数据。
(2)view包名下的类
设计视图中需要的逻辑,都是接口方法,该类也是接口类。这个接口类也是需要固定的Activity页面来实现,并重写里面的方法,在方法里面写入页面的操作,这些重写方法方法能否得到回调都是Presenter对象来控制的,一般都是数据或逻辑处理完后再判断相应的方法回调。
(3)presenter包名下的类
设计activity和model的交互,既要有View对象,也要有model对象。
Activity中把数据相关的逻辑操作都扔给了Presenter去做,View只负责处理与用户界面操作。而Presenter调用Model处理完数据之后,再通过View接口方法更新View显示的信息。
第二、在项目中的使用
项目地址:https://github.com/lantian0314/MVP
2.1 布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名" />
<EditText
android:id="@+id/et_userpwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
/>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="loginClick"
android:text="登录" />
</LinearLayout>
2.2 Activity文件
/**
* Created by xingyatong on 2018/3/13.
* Model层的定义
*/
public class LoginModel {
/**
* 处理登录逻辑判断,验证用户名以及密码
*
* @param user
* @param onLoginResultListener
*/
public void Login(User user, onLoginResultListener onLoginResultListener) {
String name = user.getUserName();
String pwd = user.getUserPwd();
if (name.equals("mvp") && pwd.equals("123456")) {
if (onLoginResultListener != null) {
onLoginResultListener.loginSuccess(user);//登陆成功的回调
}
} else {
if (onLoginResultListener != null) {
onLoginResultListener.loginFail();//登陆失败
}
}
}
/**
* 网络检查后的回调
*/
public interface onLoginResultListener {
void loginSuccess(User user);
void loginFail();
}
}
/**
* Created by xingyatong on 2018/3/13.
* mvp中Presenter中的设计
* 也是比较难,需要重点理解的一个
* presenter是主持人的意思,view和model的中间者
* 需要同时要有View的对象和Model的对象!一般做法是:在构造方法中创建model对象,并创建一个方法绑定View接口
* 这里可以发现数据处理后或者逻辑判断完后都是给mvp中的View对象来做操作的!
*/
public class LoginPresenter extends BasePresenter {
private LoginModel loginModel;
private LoginView loginView;
private User user;
public LoginPresenter() {
loginModel = new LoginModel();
user = new User();
}
@Override
public void bindView(Object mvpview) {
super.bindView(mvpview);
loginView = (LoginView) mvpview;
}
public void login() {
user.setUserName(loginView.getUserName());
user.setUserPwd(loginView.getUserPwd());
if (isParamsOk()) {
loginModel.Login(user, new LoginModel.onLoginResultListener() {
@Override
public void loginSuccess(User user) {
if (loginView != null) {
loginView.loginSuccess(user);
}
}
@Override
public void loginFail() {
if (loginView != null) {
loginView.showVerifyFail();
}
}
});
}
}
private boolean isParamsOk() {
if (TextUtils.isEmpty(user.getUserName()) || TextUtils.isEmpty(user.getUserPwd())) {
if (loginView != null) {
loginView.showVerifyFail();
}
}
return true;
}
}
/**
* Created by xingyatong on 2018/3/13.
*/
public class BasePresenter {
public void bindView(Object mvpview) {
}
}
/**
* Created by xingyatong on 2018/3/13.
*/
public interface LoginView {
String getUserName();
String getUserPwd();
void loginSuccess(User user);
void showNetEror();//网络错误
void showVerifyFail();//信息验证失败
}
**
* Created by xingyatong on 2018/3/13.
* 这里需要创建presenter对象,presenter对象中是有view对象和model对象的!
*/
public class LoginActivity extends AppCompatActivity implements LoginView {
@BindView(R.id.et_username)
EditText et_username;
@BindView(R.id.et_userpwd)
EditText et_userpwd;
@BindView(R.id.btn_login)
Button btn_login;
private LoginPresenter loginPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_main);
ButterKnife.bind(this);
loginPresenter = new LoginPresenter();
loginPresenter.bindView(LoginActivity.this);//绑定View和Presenter,因为这个Activity已经实现了接口,已经包含了View对象
}
@Override
public String getUserName() {
return et_username.getText().toString();
}
@Override
public String getUserPwd() {
return et_userpwd.getText().toString();
}
@Override
public void loginSuccess(User user) {
Toast.makeText(LoginActivity.this, "登陆成功", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
}
@Override
public void showNetEror() {
}
@Override
public void showVerifyFail() {
Toast.makeText(LoginActivity.this, "验证失败", Toast.LENGTH_SHORT).show();
}
@OnClick({R.id.btn_login})
public void loginClick(Button button) {
switch (button.getId()) {
case R.id.btn_login:
//需要对登录的数据验证判断,用到presenter
loginPresenter.login();
break;
}
}
}
第三、性能优化
Presenter经常执行一些耗时的操作,而presenter持有Activity的强引用,如果在请求结束之前Activity被销毁,网络请求还没有返回,导致presenter一直持有activity的对象,使得activity对象无法被回收,此时发生内存泄漏,因此要对persenter基类进行弱引用
/**
* Created by xingyatong on 2018/3/13.
*/
public class BasePresenter<T> {
private Reference<T> mReference;
public void bindView(T mvpview) {
//建立关联
mReference = new WeakReference<T>(mvpview);
}
public T getView() {
return mReference.get();
}
/**
* 接触弱引用关联
*/
public void detechView() {
if (mReference != null) {
mReference.clear();
mReference = null;
}
}
}
public class LoginPresenter extends BasePresenter<LoginView> {
private LoginModel loginModel;
private LoginView loginView;
private User user;
public LoginPresenter() {
loginModel = new LoginModel();
user = new User();
}
public void login() {
loginView = getView();
user.setUserName(loginView.getUserName());
user.setUserPwd(loginView.getUserPwd());
if (isParamsOk()) {
loginModel.Login(user, new LoginModel.onLoginResultListener() {
@Override
public void loginSuccess(User user) {
if (loginView != null) {
loginView.loginSuccess(user);
}
}
@Override
public void loginFail() {
if (loginView != null) {
loginView.showVerifyFail();
}
}
});
}
}
private boolean isParamsOk() {
if (TextUtils.isEmpty(user.getUserName()) || TextUtils.isEmpty(user.getUserPwd())) {
if (loginView != null) {
loginView.showVerifyFail();
}
}
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter.detechView();
}