这几天都在研究如何搭建一个实用稳固的 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<V extends IBaseView>  {
private WeakReference&lt;V&gt; mViewRef;
public V mView;

public void attachView(V view) {
    mViewRef = new WeakReference&lt;V&gt;(view);
    mView = mViewRef.get();
}

public void <span class="hljs-function"><span class="hljs-title">detachView</span></span>() {
    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<T extends BasePresenter> extends BaseActivity implements IBaseView {
protected T mPresenter;

@Override
protected void init(Bundle savedInstanceState) {
    mPresenter = <span class="hljs-built_in">bind</span>Presenter();
    mPresenter.attachView(this);
}

protected abstract T <span class="hljs-built_in">bind</span>Presenter();

@Override
protected void <span class="hljs-function"><span class="hljs-title">onDestroy</span></span>() {
    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<LoginView>{
        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&lt;LoginDomain, Exception&gt; {

    @Override
    public void onSuccess(LoginDomain result) {
        mView.showTips(<span class="hljs-string">"登录成功"</span>);
    }

    @Override
    public void onFail(Exception error) {
        mView.showTips(<span class="hljs-string">"登录失败"</span>);
    }
}

}

复制代码

简简单单,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&lt;LoginDomain, Exception&gt; callBack) {
    // 模拟一部网络获取
    try {
        Thread.sleep(2000);
        callBack.onSuccess(new LoginDomain(<span class="hljs-string">"周润发"</span>,<span class="hljs-string">"123"</span>));
    } catch (InterruptedException e) {
        e.printStackTrace();
        callBack.onFail(new Exception());
    }
}

}

复制代码

到了这里其实只有一些细节的问题了,关于 presenter 与 model 层的 ICallBack 回调如何统一规范等等都是小事。 最重要的是presenter 如何复用?敬请期待我之后的博文。

最后是我的项目的目录结构:

在这里插入图片描述

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...