android架构

一步步构建MVP框架

2018-01-19  本文已影响27人  Shmily鱼

MVP模式已经是老生常谈了,很多程序员用过这个架构进行开发,但是从无到有搭建MVP架构的过程,或许没有体验过,在此我们来一步步搭建MVP环境。
先说说概念,MVP: Model View Presenter

Model:获取数据业务逻辑
View: UI展示
Presenter: 实现者 Model与View相互独立,Presenter负责将Model与View绑定,是一个中间者的角色。

image.png

如图:MVC模式中,Model与View相互持有对方的引用,视图与数据逻辑交互。
在实际开发中,业务逻辑基本都是在Activity / ViewController中实现的,导致Controller的臃肿和难以维护。
而在MVP模式中:Model与View完全被Presenter隔离,而Presenter作为一个中间者,负责将Model与View绑定。
假设我们的有一组数据模型叫StudentBean

public class StudentBean {
    private String name;
    private String sex;
    private String ege;
    private String phone;
    //各种set get属性的方法...

首先我们创建出Model,获取StudentBean的数据;同时我们也希望在数据获取成功时有所反馈,于是我们在数据获取结束时,来个回调,实现如下:

/**
 * Created by Shmily on 2018/1/18.
 * Model接口
 */
public interface IStudentModel {
    /**
     * 加载数据
     * @param listener
     */
    void fetchData(OnLoadListener listener);
    /**
     * 加载完成的回调
     */
    interface OnLoadListener {
        void onLoadSuccess(List<StudentBean> list);
        void onLoadFailed();
    }
}

由代码我们可以看出Model提供了获取数据的方式,并在数据获取结束时给予回调方法。
现在我们实现这个Model接口

public class StudentModelImpl implements IStudentModel{
    /**
     * 获取数据,并在结束时实现回调
     * @param listener
     */
    @Override
    public void fetchData(OnLoadListener listener) {
        if (listener == null) {
            return;
        }
        ArrayList<StudentBean> list = loadDataFromLocal();
        if (list == null || list.isEmpty()){
            listener.onLoadFailed();
        }
        else {
            listener.onLoadSuccess(list);
        }
    }

    /**
     * 模拟加载本地数据
     * @return
     */
    private ArrayList<StudentBean> loadDataFromLocal(){
        ArrayList<StudentBean> list = new ArrayList<>();
        StudentBean student1 = new StudentBean();
        student1.setName("jason");
        student1.setSex("女");
        student1.setEge("10");
        student1.setPhone("010-1234567");
        list.add(student1);
        ...
        return list;
    }
}

在StudentModelImp中,模拟实现了加载数据的方法,并在加载结束时,根据结果进行对应成功失败的回调。
有了数据获取,我们来实现数据展示View模块
View更简单,拿到数据就展示,没有数据,就展示其他。

public interface IStudentView {
    /**
     * 显示进度条
     */
    void showLoading();

    /**
     * 展示失败结果
      */
    void showFailed();
    /**
     * 显示获取数据的结果,即数据
     * @param list
     */
    void showStudentList(List<StudentBean> list);
}

在View模块中,实现加载进度条的方法和展示数据。
View模块与Model模块都有了,就差Presenter了,看代码之前,我们来分析下Presenter:
Presenter虽然隔离了Model与View,但是也将二者关联起来,也就是说要将数据获取与数据展示关联起来,所以,聪明的你,应该想到了吧?

Presenter持有Model与View的引用,通过对象组合,借助Model获取数据,借助View展示数据,利用回调的方式,将这个过程串起来。
public class StudentPresenter {
    private IStudentView mStudentView;
    private IStudentModel mModel = new StudentModelImpl();
    public StudentPresenter(IStudentView studentView) {
        this.mStudentView = studentView;
    }

    public void fetch(){
        //借助view show loading
        mStudentView.showLoading();
        // 借助model 获取数据
        mModel.fetchData(new IStudentModel.OnLoadListener() {
            @Override
            public void onLoadSuccess(List<StudentBean> list) {
                // model获取数据成功后,由view来展示
                mStudentView.showStudentList(list);
            }

            @Override
            public void onLoadFailed() {
                mStudentView.showFailed();
            }
        });
    }
}

还差最后一步,View的实现,与MVC一样,我们用Activity作为View,实现IStudentView接口,
并借助Presenter调用数据获取的方法。

Presenter本身不能获取数据,但是它持有View与Model的引用,通过Model获取数据,再通过回调的方式,让View展示数据

    private void fetchData(){
        // 调用presenter加载数据
        new StudentPresenter(this).fetch();
    }

    /**
     * 展示进度条
     */
    @Override
    public void showLoading() {
        Toast.makeText(this,getString(R.string.loading),Toast.LENGTH_SHORT).show();
    }

    /**
     * 展示数据
     */
    @Override
    public void showStudentList(List<StudentBean> list) {
        myAdapter = new MyAdapter(this,list);
        listview.setAdapter(myAdapter);
    }

    /**
     * 展示加载失败
     */
    @Override
    public void showFailed() {
        Toast.makeText(this,getString(R.string.loadfailed),Toast.LENGTH_SHORT).show();
    }

在Activity中,完全看不到Model,但却借助Presenter调用Model间接获取了数据,再通过View本身展示。

Model与View二者完全隔离,却被Presenter利用对象组合回调的方式相关联。

代码的耦合度降低,在业务需求发生变化时,eg:需要从网络获取数据。 基于目前的代码,我们不需要修改什么,只要再实现一个StudentImlInternet即可。可见实现了开闭原则Open Closed Principle
忘了什么是开闭原则?贴下概念:

定义: Softeware entities like classes,modules and functions should be open for extension but closed for modifications。
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
即通过扩展来实现变化,而不是通过修改已有代码来实现变化。

以上就是MVP模式。有没有一种so easy! 的感觉。那么上面的写法会不会有问题呢?

某一个Activity(View)在内存不足时,已经销毁, 但是Model获取数据(子线程耗时操作)
那么Presenter还持有View的引用。但Model完成后,Presenter去通知View更新,则出现内存泄漏(生命周期不#####同步,上下文丢失等问题)

举例:开启一个DownActivity 点击页面下载功能 那么model就开启子线程获取数据
然后点击DownActivity的某按钮去其他页面 AnimationActivity 播放动画或视频,DownActivity是如果此时内存不足>DownActivity被销毁,那么当model完成数据获取,由于P还持有V的引用,则Presenter就会通过DownActivity去更>新UI,此时会发生内存泄漏。

image.png

解决方案:View被销毁时,解除与Presenter的关联。

So 我们需要两个方法:attachView(view);  detachView(); (像Fragment的生命周期的用法)
在View创建onCreate与销毁onDestory的时候调用。

但是如果每个实现View的Activity都在onCreate与onDestory的时候调用绑定与解绑的方法,这未免太傻了吧。于是你想到了在BaseActivity中实现,于是我们要把代码优化为扩展的方式: 配合泛型提取父类

/**
 * Created by Shmily on 2017/6/2.
 */
public abstract class BaseActivity<V,T extends  BasePresenter<V>> extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 创建Presenter 将在view中创建presenter提取到Base类
        mPresenter = createPresenter();
        // p与v的关联
        mPresenter.attachView((V)this);
    }

    public T mPresenter;

    /**
     * create presenter
     * @return 模版方法,具体实现到子类
     */
    public abstract T createPresenter();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // p与v解除关联 解决内存泄漏的问题
        mPresenter.detachView();
    }
}

解释下泛型这里:

由于业务未来的扩展,Presenter实现可能有多种类型,可以提取到父类BaseActivity中创建:采用模版
方法public T mPresenter; public abstract T createPresenter();
而T要在BaseActivity中声明,So BaseActivity<T>
Presenter的父类 BasePresenter(持有View的引用)
使用泛型V 在BasePresenter声明 BasePresenter<V>
public abstract class BasePresenter<T>
而BaseActivity持有BasePresenter的子类的引用(要在这里创建) So BaseActivity中的T是继承
BasePresenter的一个子类。那么 abstract class BaseActivity<V,T extends BasePresenter<V>>

基于上文内存泄漏的可能性,我们对Presenter的扩展时,让presenter对view的持有为弱引用
public abstract class BasePresenter<T> {
    //View接口类型弱引用
    public WeakReference<T>  mViewRefer;

    /**
     * bind view with presenter
     * @param view
     */
    protected void attachView(T view) {
        //建立关联
        mViewRefer = new WeakReference<T>(view);
    }

    //可以通过此方法,判断是否与View建立了关联
    protected boolean isViewAttached() {
        return mViewRefer != null && mViewRefer.get() != null;
    }

    /**
     * unbind view with presenter
     */
    protected void detachView(){
        if (mViewRefer != null) {
            mViewRefer.clear();
            mViewRefer = null;
        }
    }
    /**
     * base 类提供view的引用
     * @return
     */
    public T getView(){
        return mViewRefer.get();
    }
}

对于弱引用不了解的童鞋,一定要自己学习下。
最后在Activity调用

  private void fetchData(){
        mPresenter.requestData();
    }
    /**
     * 子类实现父类的方法
     * @return
     */
    @Override
    public StudentPresenterIml createPresenter() {
        return new StudentPresenterIml();
    }

在子类中创建自己想要的presenter对象,通过引用父类的mPresenter直接获取数据。
MVP是安卓研发编码时常用的代码框架,本篇先是以最精简的方式,搭建MVP框架。了解了它的核心与本质后,再用扩展的方式,再将Presenter的实现提到抽象层,具体实现延迟到子类,并解决可能存在的内存泄漏的问题。
代码中有充分的注释,在此献上代码
[精简版] https://github.com/Shmily701/SimpleMVP
[扩展版] https://github.com/Shmily701/ExtendedMVP
喜欢学习,乐于分享,不麻烦的话,给个❤鼓励下吧!

上一篇下一篇

猜你喜欢

热点阅读