这几天都在研究如何搭建一个实用稳固的MVP架构作为快速开发的基底。 也纠结了很久Presenter层该如何复用,在网上查阅了很多资料之后仍然没能找到一个适用的办法,有的写法单纯是为了presenter的复用而写,却给其他模块增负担;有的实现的手法过于僵硬,不符合写代码的原则。 在看完各种奇奇怪怪的实现思路之后,自己内心也有了一个实现presenter复用的一套方法,不过还不知道可不可行,到时撸完了可行再贴出来。
走过路过点歌Start O(∩_∩)O Github项目地址
这篇文章先撸一遍MVP的基本框架搭建,看完这篇文章你能学会:
- 一个还不错的Mvp框架结构是怎样的
- Mvp框架如何避免内存泄漏
- Presenter层如何复用?这一个以后确定可行再撸
顺着我的思路来一遍,先构造基类: 首相是对View层的基类下手, IBaseView
package com.example.administrator.mvpframedemo.base;
public interface IBaseView {
}
复制代码
实不相瞒,这个我一个方法都没定义。看到网上有很多人会把showToast()等这样的方法定义在这里喔我就不同意了,因为这些太重复固定的我觉得放在基类让每个子类去实现实在麻烦,所以我怎么做呢?我把showToast这样重复的实现放在BaseActivity;下面一起看一下 BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
initBeforeCreate();
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
init(savedInstanceState);
initData();
initView();
logic();
}
protected abstract void initBeforeCreate();
protected abstract int getLayoutId();
protected abstract void init(Bundle savedInstanceState);
protected abstract void initData();
protected abstract void initView();
protected abstract void logic();
protected void showToast (String toastStr) {
Toast.makeText(this, toastStr, Toast.LENGTH_SHORT).show();
};
}
复制代码
这是在没啥好说,这里释放了更BaseActivity有关的,而不是跟Mvp有关的。至于你了解的BaseActivity中要处理presenter绑定,解绑这样的操作我会另外建一个BaseMvpActivity来做专门针对Mvp的处理的。保持这样结构的整洁还是感觉神清气爽的;当然BaseMvpActivity还是要继承BaseActivity的;万一哪天天收的说这个模块我要用MVC之类的时候也好处理一点。 接下来就是 BasePresenter
public class BasePresenter {
private WeakReference mViewRef;
public V mView;
public void attachView(V view) {
mViewRef = new WeakReference(view);
mView = mViewRef.get();
}
public void detachView() {
mViewRef.clear();
mView = null;
}
}
复制代码
这里定义了attachView()以及detachView()两个接口;这是为啥?还有为什么要有mViewRef? 首先说为什么要attachView()?这个其实只要你敲过一些简单的mvp的代码就会知道,每一次都要写这样的代码:
MainActivity{
// 这一句代码做了两个事情,①View层创建自己适合的Presenter,②然后把自己传给Presenter完成两者的绑定
Presenter presenter = new Presenter(MainActivity.this);
}
复制代码
那么attachView()就是完成②的事情,至于①是留给View层自己去实现的,后面会说到。 所以attachView()诞生的原因就了解了,那么detachView()设计的原因呢? detachView()还有mViewRef的出现都是为了解决内存泄漏而存在的。那么是怎么解决内存泄漏的存在呢? 当一个Activity在显示的时候退出了,GC在感觉内存紧张的时候会想把这个Activity给回收掉,但是此时presenter对象是持有Activity对象的,所以GC就没办法回收了,这样就存在泄漏的隐患了。这种持有activity对象而引起内存泄漏是非常常见的原因。 所以我们使用deacttach()以及mViewRef(弱引用)来解除两者的绑定,让GC随心所欲。那什么时候解绑呢?在Activity的onDestroy()生命周期的时候合适。 那么接下来就是: BaseMvpActivity
public abstract class BaseMvpActivity extends BaseActivity implements IBaseView {
protected T mPresenter;
@Override
protected void init(Bundle savedInstanceState) {
mPresenter = bindPresenter();
mPresenter.attachView(this);
}
protected abstract T bindPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
}
复制代码
这个BaseMvpActivity里面做了针对Mvp的事情,包括定义bindPresenter()创建自己合适的presenter,然后执行presenter.attachView(this),将两者进行绑定;最后在onDestroy()方法中接触两者的绑定。
--------------------------------------人工分割线---------------------------------------------------------------------- 到这里,关于Mvp的基类设计好像就差不多了。 然后来模拟看看实际上要进行的业务: 登录页面要请求登录 第一步:在constract(合约层,维护P层与V层的关系) 建立一个LoginConstract
public interface LoginContract {
abstract class LoginPresenter extends BasePresenter{
public abstract void login(String name, String password);
}
interface LoginView extends IBaseView {
void showTips(String str);
}
}
复制代码
在合约里面定义LoginPresenter以及LoginView的接口;实现交给其他地方。 在这里开发者就要明白登录的逻辑,例如:首先View利用presetner发起登录(login)请求,请求完成之后View要给用户显示结果(showTips);所以在上面定义的接口也是这么来的。 实现: LoginPresenter:
public class LoginPresenter extends LoginContract.LoginPresenter {
ILoginModel loginModel;
@Override
public void login(String name, String password) {
loginModel = new LoginModel();
loginModel.login(name, password, new LoginCallBack());
}
private class LoginCallBack implements ICallBack {
@Override
public void onSuccess(LoginDomain result) {
mView.showTips("登录成功");
}
@Override
public void onFail(Exception error) {
mView.showTips("登录失败");
}
}
}
复制代码
简简单单,login()方法需要用到model层去帮忙获取数据。所以实例化合适的model,然后调用它取数据的接口。 我一般会把model层分为interface以及impl两层,一个定义接口,一个实现。 不过关于Presenter与Model层的交互问题需要说明一下:因为Model层取数据大多未异步操作,所以通常使用接口回调的方式。所以在调用Model的login()取数据的时候传递一个回调对象来实现异步。 LoginModel:
public class LoginModel implements ILoginModel {
@Override
public void login(String username, String password, ICallBack callBack) {
// 模拟一部网络获取
try {
Thread.sleep(2000);
callBack.onSuccess(new LoginDomain("周润发","123"));
} catch (InterruptedException e) {
e.printStackTrace();
callBack.onFail(new Exception());
}
}
}
复制代码
到了这里其实只有一些细节的问题了,关于presenter与model层的ICallBack回调如何统一规范等等都是小事。 最重要的是presenter如何复用?敬请期待我之后的博文。
最后是我的项目的目录结构: