Android MVP模式的简单理解和使用
MVP模式
1、 为什么使用MVP模式?
为什么引入架构呢?引入架构的项目,必是到了一定的规模,也就是出现了一定程度的耦合与冗余,也一定意义上违反了面向对象的单一职责原则。
那么MVP解决的问题就很明显了, 那就是冗余、混乱、耦合重
。此时抛开MVP不讲,如果要我们自己想办法去解决,如何来解决呢?
分而治之
, 我们可能会想到,根据单一职责原则
,Activity或Fragment或其他组件冗余了,那么必然要根据不同的功能模块,来划分出来不同的职责模块,这样也就遵循了单一职责的原则。站在前人的智慧上,或许很多人就想到了M(Model)V(View)C(Controller)
。我们可以借鉴这一开发模式,来达到我们的目的,暂时将一个页面划分为
UI模块,也即View层
Model模块,也即数据请求模块
Logic模块, 司逻辑处理
这样划分首先职责分工就明确了,解决了混乱,冗余的问题。
- 一个项目从分包,到分类,最后拆分方法实现,都是遵从单一职责;
- 一个职责划分越具有原子性, 它的重用性就越好,当然这也要根据实际业务而定。比如以下代码:
1.1、实例说明
public class LoginActivity extends AppCompatActivity {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String userName = inputUserName.getText().toString();
final String password = inputPassword.getText().toString();
boolean isEmptyUserName = userName == null || userName.length() == 0;
boolean isEmptyPassword = userName == null || userName.length() == 0;
boolean isUserNameValid =Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(userName ).matches();
boolean isPasswordValid = Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(password ).matches();
if (isEmptyPassword || isEmptyPassword) {
Toast.makeText(LoginActivity.this, "请输入帐号密码", Toast.LENGTH_SHORT).show();
} else {
if (isUserNameValid && isPasswordValid) {
new Thread(new Runnable() {
@Override
public void run() {
// ...登录请求
boolean loginResult = false;
if (loginResult) {
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
}
}).start();
} else {
Toast.makeText(LoginActivity.this, "帐号密码格式错误", Toast.LENGTH_SHORT).show();
}
}
}
});
}
}
一个简单的登录, 包括点击事件
, 获取登录信息
, 判断是否空
, 校验是否正确
, 请求登录
, 返回处理
。
- 这样的代码结构混乱, 可读性差; 代码冗余,可重用性差;
- 不同功能的代码糅合在一起, 耦合性高。这只是很简单的一个小功能。
上面说到, 面向对象的单一职责原则, 一个模块划分越具有原子性,也即划分越细,那么重用性就越高。如果我改成这样
public class LoginActivity extends AppCompatActivity {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String userName = getEditorText(inputUserName);
final String password = getEditorText(inputPassword);
if (isEmpty(userName) || isEmpty(password)) {
showTips("请输入帐号密码");
} else {
if (isValid(userName) && isValid(password)) {
// 登录
doLogin(userName, password);
} else {
showTips("帐号密码格式错误");
}
}
}
});
}
private boolean isValid(String s) {
return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
}
private boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
private String getEditorText(EditText et) {
return et.getText().toString();
}
private void showTips(String tips) {
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
}
private void doLogin(String username, String password) {
new Thread(new Runnable() {
@Override
public void run() {
// ...登录请求
boolean loginResult = false;
// 更新UI
notifyLoginResult(loginResult);
}
}).start();
}
private void notifyLoginResult(boolean loginResult) {
if (loginResult) {
showTips("登录成功");
} else {
showTips("登录失败");
}
}
}
将源码方法进行拆分后, isEmpty, isValid, showTips…等,产生的结果有亮点:
- 1) 方法拆分后,可重用性提高了
- 2) 相比而言,浏览一遍,我能基本清楚onClick里做了什么,也就是架构清晰了
这就是单一职责原则的作用,提高可重用性, 减少代码冗余,开始露出清晰的思维脉络。
以上说明了单一职责的意义,以及带来的附加的益处。那么代码经过初步重构以后, 虽然更清晰了,消除了冗余,但是耦合的问题依旧。那怎么解决耦合问题呢?我们来看下半场
2、一步步让你理解MVP
MVP最难的难点之一: 如何正确划分各模块
- Model很简单, 数据加载的界限很明确,很简单就划分出来了, 比如数据库操作, 比如文件查询, 比如网络请求,可以连带着异步操作一起拿出来,划分为单独的Model层。
View层与Presenter层交互性很频繁,很多人不清楚这一块代码算是View,还是Presenter
- 首先, 单纯的逻辑实现必然是Presenter处理的;单纯的View初始化也必然是View处理的,如findView这些。
像登录模块,View与逻辑交错在一起,怎么区分呢 ?
首先Login功能大抵分为以下子功能:
1、取值, EditText帐号与密码(明确的View层,不涉及逻辑操作)
2、判空与校验 (Presenter但涉及View, 因为使用帐号与密码,通过传参的形式)
3、登录请求 (名副其实的Model, 处理明显在Presenter层)
4、更新UI (View层)
其实以上划分界限相对比较清晰,项目中难免遇到一些不好界限的,教你一招,难以划分的必然包含View也包含逻辑处理。那么第一步,
- 原子性拆分,将View与逻辑处理单独拆分成不同的方法。View 的部分在View层, 处理的部分在Presenter层
- 有一些Toast, Dialog等的划分,根据Context作区分。
- 可以使用Application Context实现的,可以作为Presenter层; 必须使用Activity
Context的,作为View层
- 可以使用Application Context实现的,可以作为Presenter层; 必须使用Activity
那么明确了M V P的拆分,看一下拆分结果
2.1、MVP实现第一步, 将页面拆分为M/V/P三个模块
1、View 部分
public class LoginActivity extends AppCompatActivity {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.execureLogin(getEditorText(inputUserName), getEditorText(inputPassword));
}
});
}
private String getEditorText(EditText et) {
return et.getText().toString();
}
public void showTips(String tips) {
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
}
public void notifyLoginResult(boolean loginResult) {
if (loginResult) {
showTips("登录成功");
} else {
showTips("登录失败");
}
}
}
2、Model部分
public class LoginModel {
private Handler handler;
public LoginModel() {
handler = new Handler();
}
public interface OnLoginCallback {
void onResponse(boolean success);
}
public void login(String username, String password, final OnLoginCallback callback) {
new Thread(new Runnable() {
@Override
public void run() {
// ...请求接口
boolean result = true; // 假设这是接口返回的结果
callback.onResponse(result);
}
}).start();
}
}
Presenter部分
public class LoginPresenter {
private LoginModel model;
private LoginActivity activity;
private String verifyMsg;
public LoginPresenter(LoginActivity activity) {
this.activity = activity;
model = new LoginModel();
}
public void execureLogin(String username, String password) {
boolean verifyBefore = verifyBeforeLogin(username, password);
if (verifyBefore) {
// 校验通过,请求登录
model.login(username, password, new LoginModel.OnLoginCallback() {
@Override
public void onResponse(boolean success) {
// 登录结果
activity.notifyLoginResult(success);
}
});
} else {
// 校验失败,提示
activity.showTips(verifyMsg);
}
}
private boolean verifyBeforeLogin(String username, String password) {
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty) {
verifyMsg = "请输入帐号或密码";
return false;
}
if (isValid) {
return true;
}
verifyMsg = "帐号或密码错误";
return false;
}
private boolean isValid(String s) {
return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
}
private boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
}
通过以上代码可以看出:
- 1、Toast提示, 更新登录状态等, 都拆分在View层;
- 2、校验与登录则拆分在Presenter层;
- 3、网络请求则拆分到了Model层。
这样每一层都只处理本层的业务,从大的方向上进行了单一职责拆分,从而整体符合单一职责原则。
根据MVP将页面拆分为了3层,单一职责的原则我们已经完全符合了。但是仔细看,忽然发现相互之间还存在依赖,解耦效果并不是那么理想。那我们要思考了,是什么原因导致耦合尚在?
那就是对象持有,看看我们的项目
- Presenter持有View(Activity)对象,同时持有Model对象
- View持有Presenter对象
MVP是怎么解决对象持有问题的?
- 面向接口编程
2.2、 MVP实现第2步, 使用接口通信,进一步解耦
对于面向对象设计来讲, 利用接口达到解耦目的已经是人尽皆知的了。 这次改动很小,把对象持有改为接口持有即可。
- View持有Presenter对象改为持有Presenter接口
- Presenter持有View对象改为持有View接口
既然持有接口,肯定要在View与Presenter分别实现供外部调用的接口。
- View供Presenter调用的方法有
notifyLoginResult
和showTips
; - Presenter供View调用的方法有
executeLogin
。
那么先来实现接口如何?看代码
Presenter接口
public interface IPresenter {
/**
* 执行登录
*
* @param username
* @param password
*/
void executeLogin(String username, String password);
}
View接口
public interface IView {
/**
* 更新登录结果
*
* @param loginResult
*/
void notifyLoginResult(boolean loginResult);
/**
* Toast提示
*
* @param tips
*/
void showTips(String tips);
}
接口的作用是对外部提供一种供外部调用的规范。因此这里我们把外部需要调用的方法抽象出来,加入到接口中。接口有了,且接口代表的是View或Presenter的实现,所以分别实现它们。看代码
Presenter实现接口
public class LoginPresenter implements IPresenter {
private LoginModel model;
private LoginActivity activity;
private String verifyMsg;
public LoginPresenter(LoginActivity activity) {
this.activity = activity;
model = new LoginModel();
}
@Override
public void executeLogin(String username, String password) {
boolean verifyBefore = verifyBeforeLogin(username, password);
if (verifyBefore) {
// 校验通过,请求登录
model.login(username, password, new LoginModel.OnLoginCallback() {
@Override
public void onResponse(boolean success) {
// 登录结果
activity.notifyLoginResult(success);
}
});
} else {
// 校验失败,提示
activity.showTips(verifyMsg);
}
}
private boolean verifyBeforeLogin(String username, String password) {
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty) {
verifyMsg = "请输入帐号或密码";
return false;
}
if (isValid) {
return true;
}
verifyMsg = "帐号或密码错误";
return false;
}
private boolean isValid(String s) {
return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
}
private boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
}
View实现接口
public class LoginActivity extends AppCompatActivity implements IView{
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.executeLogin(getEditorText(inputUserName), getEditorText(inputPassword));
}
});
}
private String getEditorText(EditText et) {
return et.getText().toString();
}
@Override
public void showTips(String tips) {
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
}
@Override
public void notifyLoginResult(boolean loginResult) {
if (loginResult) {
showTips("登录成功");
} else {
showTips("登录失败");
}
}
}
在接口中提供对外部调用的方法,然后分别在View和Presenter中实现它们。接口与实现都有了,还记得我们的目的是什么吗?是把持有的对象替换为接口
,撸起来,看代码
// 这是View持有的接口,在onCreate中初始化的对象由原来的LoginPresenter改为了IPresenter。
public class LoginActivity extends AppCompatActivity implements IView{
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
IPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
...
}
// 这是Presenter持有的接口,在构造中由原来的LoginActivity改为了IView
public class LoginPresenter implements IPresenter {
private LoginModel model;
private String verifyMsg;
private IView activity;
public LoginPresenter(IView activity) {
this.activity = activity;
model = new LoginModel();
}
...
2.2.1、MVP遵从的面向对象原则
1) 单一职责
- 每个模块只负责该模块的本职工作,不越界。 如View负责UI初始化与更新, Model负责数据查询与异步,至于逻辑判断,业务实现,放心的扔给Presenter中就好了。
2) 面向接口通信
- 对象的持有是造成耦合的本质原因之一,因此要达到解耦的目的,替换为接口持有最是合适不过。
2.3、 从头到尾封装一个完整的MVP框架
2.3.1、业务分析
这里以登录为例。先来分析下业务:
1、EditText取得输入的UserName与Password
2、校验(包括判断是否是空的, 是否符合输入规范比如不允许输入特殊字符)
3、校验通过, 执行登录请求; 不通过,直接提示错误
4、登录请求
5、根据登录结果,提示登录成功或失败
6、伴随着登录结果更新UI
2.3.2、BaseInterface 与 Contract的概念
MVP引入了BaseInterface 与Contract的概念。如果单纯的mvp,可能很多人都理解,但是加上这两个概念,加深了理解难度。
base-interface 就是我们常用的base的概念,目的就是规范统一的操作。比如显示一个Toast, 判断网络是否连接,跳转动画等,我们都放在BaseActivity中,因为所有的Activity都需要这些。接口的继承也是这个目的。如登录功能
- 1) 我们需要一个Presenter,于是有了LoginPresenter
- 2)我们需要一个LoginPresenter的接口,为View层提供调用,于是有了ILoginPresenter
- 3)无论登录,还是注册,还有其他功能,所有的Presenter都需要一个功能start, 于是有了IPresenter
- IPresenter提供了一个所有Presenter接口共有的操作,就是start,也即初始化的加载 Contract的概念
这个概念的引入只是为了统一管理一个页面的View和Presenter接口。每个页面对应
1个View(Activity或Fragment), 一个IView(View接口)
,1个Presenter,一个IPresenter(Presenter接口)
,1个Contract(一个包含View接口和Presenter接口的接口)
。
如
public interface LoginContract {
interface View {
void notifyLoginResult();
}
interface Presenter {
void login(String username, String password);
}
}
2.3.3、从头到尾封装一个完整的MVP框架。
1) 首先来思考,我们最先定义的应该是什么? 当然是公共接口。
View的公共接口(MVP-Samples中的IView)没有公共的操作,我们定义一个空的接口,用于统一规范。
public interface IView {
}
Presenter的公共接口(MVP-Samples中的IPresenter)也没有公共的操作,在mvp提供的samples中是带了一个start的,但是这里不需要。
- 为什么呢?因为我们还要来一个BasePresenter。所以我们还是定义一个空的接口,用于统一规范。
public interface IPresenter {
}
以上两个接口,是用于给View与Presenter的接口继承的
,注意,不是View或Presenter本身继承。因为它定义的是接口的规范, 而接口才是定义的类的规范。
2)定义的接口要继承IView与IPresenter, 而且由Contract统一管理
public interface LoginContract {
interface View extends IView {
// View中的2个功能:
// 1) 取得登录需要的username, password # 不需要对Presenter层提供调用
// 2) 提示错误信息, 提示登录结果 # 需要Presenter层调用,因为校验和登录都是在Presenter层的
// 因此2)是View层提供的对外方法,需要在接口中定义
/**
* 提示一个Toast
*
* @param msg
*/
void showToast(String msg);
}
interface Presenter extends IPresenter {
// Presenter中的2个功能:
// 1) 校验 # 看你怎么写,既可以在View层中调用校验方法,也可以在Presenter层中,这里定义为直接在Presenter中校验,彻底和View解耦
// 2) 登录 # 先执行校验,再执行登录,需要在View层点击登录时调用
// 因此2)是Presenter对外层提供的方法,需要在接口中定义
/**
* 登录操作
*
* @param username
* @param password
*/
void login(String username, String password);
}
}
以上Contract(称之为功能仓库)分别定义了View与Presenter接口,并添加了接口的定义过程分析。
3) 接口定义完成了,下一步是什么呢? 肯定是实现接口,加入功能吧。定义Presenter与View分别实现接口,加入对应功能。
public class LoginActivity extends AppCompatActivity implements LoginContract.View {
LoginContract.Presenter mPresenter;
Button loginBtn;
EditText etUser, etPwd;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = new LoginPresenter(this);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.login(etUser.getText().toString(), etPwd.getText().toString());
}
});
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
*********************************************************************************************************
public class LoginPresenter implements LoginContract.Presenter {
LoginContract.View mView;
public LoginPresenter(LoginContract.View mView) {
this.mView = mView;
}
@Override
public void login(String username, String password) {
// 校验直接放在登录流程
boolean isVerifySuccessully = verifyLoginInfo();
if (isVerifySuccessully) {
// 请求登录
LoginModel.requestLogin(username, password, new LoginModel.RequestCallback() {
@Override
public void onResponse(boolean result) {
if (result) {
// 提示登录成功
mView.showToast("登录成功");
} else {
// 提示登录失败
mView.showToast("登录失败");
}
}
});
} else {
// 校验失败,提示错误
mView.showToast("无效的帐号或密码");
}
}
private boolean verifyLoginInfo() {
// 这里校验登录信息
// 校验帐号,密码是否为空
// 校验帐号,密码是否符合要求
return true;
}
}
注意:LoginActivity在相对的生命周期中需要销毁Presenter引用,由于后面会封装,这里没加。
- 可能出现内存泄漏,如果model层数据没有回调完成,activity退出,以为presenter持有当前activity,所以gc不了,出现内存泄漏
走到这一步基本就是一个完整的MVP开发模式了,从划分层次到接口通信,其实还是挺简单的,不是么?下面继续来优化这个框架,我们考虑以下几个问题:
- 每个Activity或者Fragment都要初始化或管理Presenter,累不累?
- 同样的,每个Presenter都要管理View,累不累?
2.4、 最终优化版
Presenter基类抽取,公共元素有哪些 :
- Presenter公共元素,其实主要有两个:
Context
,View
接口。- 注意:Presenter不要传入Activity的Context;
- 如果需要用到Activity的Context,那么Presenter层就不单纯了。那么只能是Application的Context。
- 我们获取Application Context的方式有两种,AppContext(你的Application)的静态获取 和 Activity的getApplicationContext。这里使用传入的Application Context吧!
- 注意:Presenter不要传入Activity的Context;
很多网上View的获取是定义一个AttachView的方法, 这里使用在构造中直接传入。
public abstract class BasePresenter<AttachView extends IView> {
private Context mContext;
private AttachView mView;
public BasePresenter(Context context, AttachView view) {
if (context == null) {
throw new NullPointerException("context == null");
}
mContext = context.getApplicationContext();
mView = view;
}
/**
* 获取关联的View
*
* @return
*/
public AttachView getAttachedView() {
if (mView == null) {
throw new NullPointerException("AttachView is null");
}
return mView;
}
/**
* 获取关联的Context
*
* @return
*/
public Context getContext() {
return mContext;
}
/**
* 清空Presenter
*/
public void clearPresenter() {
mContext = null;
mView = null;
}
/**
* View是否关联
*
* @return
*/
public boolean isViewAttached() {
return mView != null;
}
/**
* 网络是否连接
*
* @return
*/
public boolean isNetworkConnected() {
if (mContext == null) {
throw new NullPointerException("mContext is null");
}
return NetworkHelper.isNetworkConnected(mContext);
}
public abstract void start();
public abstract void destroy();
}
以上是我们抽取的Presenter基类。实现了:
1、初始化时绑定View接口,并在clear时清除接口
2、自动获取ApplicationContext(还是建议不这样,直接传Application的Context) 3、View状态判定
4、网络连接判断(因为Presenter中执行网络请求比较频繁,你可以根据业务自定义多个方法)
5、satrt初始化方法与destroy销毁方法(结合后面的MVPCompatActivity自动销毁)
注意:在使用View时,请先判断View状态
,否则View异常销毁时会报NullPoiterException
。如果有线程或者Handler一定要在destroy中销毁,避免造成内存泄漏
。
for Activity
/**
* MVP - Activity基类
*/
public abstract class MVPCompatActivity<T extends BasePresenter> extends RootActivity {
protected T mPresenter;
@Override
protected void onStart() {
super.onStart();
if (mPresenter == null) {
mPresenter = createPresenter();
}
mPresenter.start();
}
@Override
protected void onStop() {
super.onStop();
mPresenter.clearPresenter();
mPresenter = null;
}
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
mPresenter.clearPresenter();
mPresenter = null;
}
/**
* 创建一个Presenter
*
* @return
*/
protected abstract T createPresenter();
}
for Fragment
public abstract class MVPCompatFragment<T extends BasePresenter> extends RootFragment {
protected T mPresenter;
@Override
public void onStart() {
super.onStart();
if (mPresenter == null) {
mPresenter = createPresenter();
}
mPresenter.start();
}
@Override
public void onStop() {
super.onStop();
if (mPresenter != null) {
mPresenter.clearPresenter();
mPresenter = null;
}
}
protected abstract T createPresenter();
}
for Layout
public abstract class MVPCompatLayout<T extends BasePresenter> extends RootLayout {
protected T mPresenter;
public MVPCompatLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPresenter = createPresenter();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mPresenter != null) {
mPresenter.clearPresenter();
mPresenter = null;
}
}
protected abstract T createPresenter();
}
for Adapter
public abstract class MVPCompatRecyclerAdapter<T, P extends BasePresenter> extends RootRecyclerAdapter<T> {
protected P mPresenter;
public MVPCompatRecyclerAdapter(Context context, List data) {
super(context, data);
}
protected abstract P createPresenter();
@Override
public void onViewAttachedToWindow(RecyclerViewHolder holder) {
super.onViewAttachedToWindow(holder);
mPresenter = createPresenter();
}
@Override
public void onViewDetachedFromWindow(RecyclerViewHolder holder) {
super.onViewDetachedFromWindow(holder);
if (mPresenter != null) {
mPresenter.clearPresenter();
mPresenter = null;
}
}
}
通过继承以上View的Base, 可以自由实现初始化以及销毁。轻松实现MVP。
最后,补充的RootActivity, 作为一个Base的Activity,是根据不同的业务决定里面的内容的,因此这里很少
public abstract class RootActivity extends AppCompatActivity {
protected Context mContext;
protected Context mAppContext;
private View mContentView;
private Bundle mBundleObj;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAppContext = getApplicationContext();
mContext = this;
mContentView = getLayoutInflater().inflate(getLayoutRes(), null);
setContentView(mContentView);
ButterKnife.bind(this);
init();
}
protected abstract int getLayoutRes();
protected abstract void init();
/**
* findViewById
*
* @param resId
* @param <T>
* @return
*/
protected <T extends View> T $(int resId) {
return (T) findViewById(resId);
}
/**
* Toast
*
* @param toast
*/
protected void showToast(String toast) {
Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
}
/**
* get a bundle from reuse.
*
* @return
*/
protected Bundle obtainBundle() {
if (mBundleObj == null) {
mBundleObj = new Bundle();
} else {
mBundleObj.clear();
}
return mBundleObj;
}
}
3、具体实例
3.1、MyUser
public class MyUser extends BmobUser {
private String image;
private String gender;
private String age;
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
3.2、BaseContract
public interface BaseContract {
interface BasePresenter<T> {
void attachView(T view);
void detachView();
}
interface BaseView {
void onSuccess();
void onFailure(Throwable e);
}
}
3.3、LandContract
public interface LandContract extends BaseContract {
interface View extends BaseContract.BaseView {
void landSuccess(MyUser user);
}
interface Presenter extends BaseContract.BasePresenter<View>{
/**
* 用户登陆
*/
void login(String username, String password);
/**
* 用户注册
*/
void signup(String password, String mail);
}
}
3.4、BaseMVPActivity
public abstract class BaseMVPActivity<T extends BaseContract.BasePresenter> extends BaseActivity{
protected T mPresenter;
protected abstract T bindPresenter();
@Override
protected void processLogic() {
attachView(bindPresenter());
}
private void attachView(T presenter){
mPresenter = presenter;
mPresenter.attachView(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
}
3.5、LoginActivity
public class LoginActivity extends BaseMVPActivity<LandContract.Presenter>
implements LandContract.View, View.OnFocusChangeListener, View.OnClickListener {
private OwlView mOwlView;
private EditText usernameET;
private EditText passwordET;
private EditText rpasswordET;
private TextView signTV;
private TextView forgetTV;
private Button loginBtn;
//是否是登陆操作
private boolean isLogin = true;
@Override
protected int getLayoutId() {
return R.layout.activity_user_land;
}
@Override
protected void initWidget() {
super.initWidget();
mOwlView=findViewById(R.id.land_owl_view);
usernameET=findViewById(R.id.login_et_username);
passwordET=findViewById(R.id.login_et_password);
rpasswordET=findViewById(R.id.login_et_rpassword);
signTV=findViewById(R.id.login_tv_sign);
forgetTV=findViewById(R.id.login_tv_forget);
loginBtn=findViewById(R.id.login_btn_login);
}
@Override
protected void initClick() {
super.initClick();
passwordET.setOnFocusChangeListener(this);
rpasswordET.setOnFocusChangeListener(this);
signTV.setOnClickListener(this);
forgetTV.setOnClickListener(this);
loginBtn.setOnClickListener(this);
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
mOwlView.open();
} else {
mOwlView.close();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.login_btn_login: //button
if (isLogin) {
login(); //登陆
} else {
sign(); //注册
}
break;
case R.id.login_tv_sign: //sign
if (isLogin) {
//置换注册界面
signTV.setText("登录");
loginBtn.setText("注册");
rpasswordET.setVisibility(View.VISIBLE);
usernameET.setVisibility(View.GONE);
} else {
//置换登陆界面
signTV.setText("注册");
loginBtn.setText("登录");
usernameET.setVisibility(View.VISIBLE);
rpasswordET.setVisibility(View.GONE);
}
isLogin = !isLogin;
break;
case R.id.login_tv_forget: //忘记密码
showForgetPwDialog();
break;
default:
break;
}
}
/**
* 执行登陆动作
*/
public void login() {
String username = usernameET.getText().toString();
String password = passwordET.getText().toString();
if (username.length() == 0 || password.length() == 0) {
SnackbarUtils.show(mContext, "用户名或密码不能为空");
return;
}
ProgressUtils.show(this, "正在登陆...");
mPresenter.login(username, password);
}
/**
* 执行注册动作
*/
public void sign() {
String email = usernameET.getText().toString();
String password = passwordET.getText().toString();
String rpassword = rpasswordET.getText().toString();
if (email.length() == 0 || password.length() == 0 || rpassword.length() == 0) {
SnackbarUtils.show(mContext, "请填写必要信息");
return;
}
if (!StringUtils.checkEmail(email)) {
SnackbarUtils.show(mContext, "请输入正确的邮箱格式");
return;
}
if (!password.equals(rpassword)) {
SnackbarUtils.show(mContext, "两次密码不一致");
return;
}
ProgressUtils.show(this, "正在注册...");
usernameET.setText(email);
mPresenter.signup(password,email);
}
/***********************************************************************/
@Override
protected LandContract.Presenter bindPresenter() {
return new LandPresenter();
}
@Override
public void landSuccess(MyUser user) {
ProgressUtils.dismiss();
if (isLogin) {
setResult(RESULT_OK, new Intent());
Intent intent = new Intent(this, MainActivity1.class);
startActivity(intent);
finish();
} else {
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
//置换登陆界面
signTV.setText("注册");
loginBtn.setText("登录");
rpasswordET.setVisibility(View.GONE);
usernameET.setVisibility(View.VISIBLE);
isLogin=true;
}
Log.i(TAG,user.toString());
}
@Override
public void onSuccess() {
ProgressUtils.dismiss();
}
@Override
public void onFailure(Throwable e) {
ProgressUtils.dismiss();
SnackbarUtils.show(mContext, e.getMessage());
Log.e(TAG,e.getMessage());
}
/**
* 显示忘记密码对话框
*/
public void showForgetPwDialog() {
new MaterialDialog.Builder(this)
.title("找回密码")
.inputType(InputType.TYPE_CLASS_TEXT)
.input("请输入注册邮箱", null, (dialog, input) -> {
String inputStr = input.toString();
if (input.equals("")) {
SnackbarUtils.show(mContext, "内容不能为空!");
} else if(!StringUtils.checkEmail(inputStr)) {
Toast.makeText(LoginActivity.this,
"请输入正确的邮箱格式", Toast.LENGTH_LONG).show();
} else {
//找回密码
BmobUser.resetPasswordByEmail(input.toString(), new UpdateListener() {
@Override
public void done(BmobException e) {
if (e == null) {
ToastUtils.show(mContext, "重置密码请求成功,请到邮箱进行密码重置操作");
} else {
ToastUtils.show(mContext, "重置密码请求失败,请确认输入邮箱正确!");
}
}
});
}
})
.positiveText("确定")
.negativeText("取消")
.show();
}
}
3.6、LandPresenter
public class LandPresenter extends RxPresenter<LandContract.View> implements LandContract.Presenter{
private String TAG="LandPresenter";
@Override
public void login(String username, String password) {
MyUser.loginByAccount(username, password, new LogInListener<MyUser>() {
@Override
public void done(MyUser myUser, BmobException e) {
if(e == null) {
mView.landSuccess(myUser);
}else {
String error=e.toString();
if(error.contains("incorrect")){
Toast.makeText(getApplicationContext(), "账号或者密码错误!", Toast.LENGTH_SHORT).show();
mView.onSuccess();
}else {
mView.onFailure(e);
}
}
}
});
}
@Override
public void signup(String password, String mail) {
MyUser myUser =new MyUser();
myUser.setPassword(password);
myUser.setEmail(mail);
myUser.signUp(new SaveListener<MyUser>() {
@Override
public void done(MyUser myUser, BmobException e) {
if(e == null)
mView.landSuccess(myUser);
else{
String error = e.toString();
if(error.contains("already")){
Toast.makeText(getApplicationContext(), "邮箱已经被注册,请重新填写!", Toast.LENGTH_SHORT).show();
mView.onSuccess();
}else {
mView.onFailure(e);
}
}
}
});
}
}