美文网首页android架构
一步步构建MVP框架

一步步构建MVP框架

作者: Shmily鱼 | 来源:发表于2018-01-19 15:26 被阅读27次

    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
    喜欢学习,乐于分享,不麻烦的话,给个❤鼓励下吧!

    相关文章

      网友评论

        本文标题:一步步构建MVP框架

        本文链接:https://www.haomeiwen.com/subject/dxjuoxtx.html