简介
关于Android程序的构架, 主流的不外乎以下几种:MVC、MVP和MVVM。
MVC:相对于较为落后,耦合度太高、职责不明确,不易于维护。
MVVM:使用DataBinding,普及性不如MVP。
此外,Google官方提供了Sample代码来展示MVP模式的用法,因此主流还是选择MVP架构。
因本文主要讲的是MVP模式的优雅封装,MVVM模式在此就不作赘述,后续文章会讲到。
MVC:
提到MVP就不得不提到MVC,关于MVC架构,可以看下面这张图
MVC工作原理:
MVC即Model View Controller,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见上图。当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。
这种原理就会造成一个一个致命的缺陷:当我们把很多业务逻辑写在activity中时,activity既充当了View层,又充当了Controller层。因此,耦合性极高,各种业务逻辑代码和View代码混合在一起你中有我我中有你,如果要修改一个需求,改动的地方可能相当多,维护起来十分不便。
作为一个追求优雅的程序猿,这种架构必然要被抛弃。
MVP:
概念
MVP即Model、View、Presenter
View:负责视图部分展示、视图事件处理。Activity、Fragment、Dialog、ViewGroup等呈现视图的组件都可以承担该角色。
Model:负责数据的请求、解析、过滤等数据层操作。
Presenter:View和Model交互的桥梁。
优势
单一职责
Model、View、Presenter只处理某一类逻辑
解耦
Model层修改和View层修改互不影响
面向接口编程,依赖抽象
Presenter和View互相持有抽象引用,对外隐藏内部实现细节
可能存在的问题
1、Model进行异步操作,获取结果通过Presenter回传到View时,出现View引用的空指针异常
2、Presenter和View互相持有引用,解除不及时造成的内存泄漏。
因此,在进行MVP架构设计时需要考虑Presenter对View进行回传时,View是否为空?
Presenter与View何时解除引用即Presenter能否和View层进行生命周期同步?
好了,说了这么多废话,总之一句话,MVP好。下面我们来看看具体如何优雅的实现MVP的封装。
MVP架构优雅的封装
1、首先,我们定义一个BaseView
/**
* 视图基类
*/
public interface BaseView {
}
上面说过,
如果Presenter与View不及时解除引用关系,那么内存泄漏乃至内存溢出就是必然。
具体来说,
当Presenter对象持有一个或多个大型Activity的引用,如果该对象(P)不能被系统回收,那么当这些Activity不再使用时,这个Activity也不会被系统回收,这样一来便出现了内存泄漏的情况。在应用中内出现一次两次的内存泄漏或许不会出现什么影响,但是在应用长时间使用以后,若是这些占据大量内存的Activity无法被GC回收的话,最终会导致OOM的出现,就会直接Crash应用。
我们当然不会坐视这种情况的发生,解决的思路就是,
我们将Presenter的生命周期和View层的生命周期绑定在一起,给Presenter定义两个方法,一个绑定View层,一个解绑View层,在需要的时候进行绑定,不需要的时候进行解绑就可以了。
于是就有了下面这个定义。
2、将Presenter的生命周期和View层的生命周期绑定
/**
* 控制器接口:
* 定义P层生命周期与 V层同步
*/
public interface IPresenter {
void onMvpAttachView(V view, Bundle savedInstanceState);
void onMvpStart();
void onMvpResume();
void onMvpPause();
void onMvpStop();
void onMvpSaveInstanceState(Bundle savedInstanceState);
void onMvpDetachView(boolean retainInstance);
void onMvpDestroy();
}
为了代码的优雅性,我们对它进行一次封装
/**
* 控制器基类:
* Presenter生命周期包装、View的绑定和解除,P层实现的基类
*/
public class BasePresenter implements IPresenter {
private WeakReference viewRef;
protected V getView() {
return viewRef.get();
}
protected boolean isViewAttached() {
return viewRef != null && viewRef.get() != null;
}
private void _attach(V view, Bundle savedInstanceState) {
viewRef = new WeakReference(view);
}
@Override
public void onMvpAttachView(V view, Bundle savedInstanceState) {
_attach(view, savedInstanceState);
}
@Override
public void onMvpStart() {
}
@Override
public void onMvpResume() {
}
@Override
public void onMvpPause() {
}
@Override
public void onMvpStop() {
}
@Override
public void onMvpSaveInstanceState(Bundle savedInstanceState) {
}
private void _detach(boolean retainInstance) {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
@Override
public void onMvpDetachView(boolean retainInstance) {
_detach(retainInstance);
}
@Override
public void onMvpDestroy() {
}
}
3、对于View层,我们一般都会写一个BaseActivity
/**
* @description 在此类中添加自己的基类功能
*/
public class BaseActivity extends FragmentActivity {
protected void openActivity(String action) {
openActivity(action, null);
}
public void showEnsureDialog(String message) {
}
}
4、我们再写一个绑定生命周期的BaseMvpActivity包装类
/**
* MVP的Activity基类:
* 纯粹的 MVP 包装,不要增加任何View层基础功能
* 如果要添加基类功能,请在{@link BaseActivity} 中添加
*/
public abstract class BaseMvpActivity extends BaseActivity implements BaseView {
protected P mPresenter;
protected abstract P createPresenter();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
if (mPresenter == null) {
throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?");
}
mPresenter.onMvpAttachView(this, savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
if (mPresenter != null) {
mPresenter.onMvpStart();
}
}
@Override
protected void onResume() {
super.onResume();
if (mPresenter != null) {
mPresenter.onMvpResume();
}
}
@Override
protected void onPause() {
super.onPause();
if (mPresenter != null) {
mPresenter.onMvpPause();
}
}
@Override
protected void onStop() {
super.onStop();
if (mPresenter != null) {
mPresenter.onMvpStop();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mPresenter != null) {
mPresenter.onMvpSaveInstanceState(outState);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onMvpDetachView(false);
mPresenter.onMvpDestroy();
}
}
}
5、我们以登录为例,定义一个契约类
/**
* 契约接口类:
* P层与 V层接口定义
*/
public class LoginContract {
public interface ILoginView extends BaseView {
/**
* 登录成功
*/
void LoginSuccess();
/**
* 登录失败
*
* @param msg
*/
void LoginFailed(String msg);
}
public interface ILoginPresenter extends IPresenter {
/**
* 登录
*/
void login(String username, String password);
}
}
6、我们再定义一个登录的Presenter的实现类,在这个类中,完成互相访问。
/**
* 控制器实现类
*/
public class LoginPresenterImpl extends BasePresenter implements LoginContract.ILoginPresenter {
@Override
public void login(String username, String password) {
//先进行非空判断
if (isViewAttached()) {
handleLogin(getView(), username, password);
}
}
private void handleLogin(LoginContract.ILoginView view, String username, String password) {
if (username.isEmpty() || password.isEmpty()) {
view.LoginFailed("账号和密码不能为空");
} else if (password.length() < 6 || password.length() > 20) {
view.LoginFailed("密码须在6-20位之间");
} else {
if (username.equals("mvp")) {
if (password.equals("123456")) {
view.LoginSuccess();
} else {
view.LoginFailed("密码错误");
}
} else {
view.LoginFailed("用户名错误");
}
}
}
@Override
public void onMvpAttachView(LoginContract.ILoginView view, Bundle savedInstanceState) {
super.onMvpAttachView(view, savedInstanceState);
}
/**
* 重写P层需要的生命周期,进行相关逻辑操作
*/
@Override
public void onMvpResume() {
super.onMvpResume();
}
}
7、到这里,我们的封装基本完成,我们现在来看看我们的LoginActivity是怎么样的。
public class LoginActivity extends BaseMvpActivity implements LoginContract.ILoginView {
@BindView(R.id.et_username)
EditText etUsername;
@BindView(R.id.et_password)
EditText etPassword;
@BindView(R.id.btn_login)
Button btnLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
ButterKnife.bind(this);
}
@Override
protected LoginContract.ILoginPresenter createPresenter() {
return new LoginPresenterImpl();
}
@OnClick({R.id.btn_login})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_login:
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
mPresenter.login(username, password);
break;
}
}
@Override
public void LoginSuccess() {
Toast.makeText(this, "LoginSuccess", Toast.LENGTH_SHORT).show();
}
@Override
public void LoginFailed(String msg) {
Toast.makeText(this, "LoginFailed", Toast.LENGTH_SHORT).show();
}
}
这样,是不是看起来,清爽多了。