美文网首页学学人家的框架Androidk开发合集Android
Android架构设计---MVP模式第(一)篇之基本认实

Android架构设计---MVP模式第(一)篇之基本认实

作者: LooperJing | 来源:发表于2016-05-23 19:23 被阅读6077次

    版权声明:本文为LooperJing原创文章,转载请注明出处!

    MVP 这种模式出现已经很久了,在网上有些关于 MVP 开源代码2014年就有了,近期有关注项目架构方面的内容,于是乎,作为一个还不懂什么是 MVP 的人,那么就一定要了解一下的。网上关于 MVP 的资料其实也不少,通常都要把 MVP 和 MVC 做一下比较,我喜欢直接了当,相信有耐心看MVP的人是一定懂 MVC 的,MVC 的略过。本文的项目地址是:https://github.com/herojing/JokeMVP,下面结合项目谈谈MVP是个什么东西,以下就当作自己的学习总结笔记吧。

    一、什么是MVP?

    MVP 是 Model、Presenter、View 的缩写,三个部分的关系如下图所示。


    效果图

    在 Android 项目中,负责界面展示的模块(所有的 Activitiy 、Fragment以及 View 的子类)都可以划分到 View 这个层次,所有的业务逻辑处理(请求网络数据、数据库读取等)可以划分到 Model 这个层次,为了使得 View 和 Model 之间松耦合,用 Presenter 帮助解耦。所以可以猜测,在具体实现中 Presenter 类肯定要持有 View 和 Model 的引用。现在来说一下,上图中三个箭头的意思。流程是这样子的,从左到右看,比如我们刚进入一个 Activity,那么这个 Activity 做为 View 层,肯定需要通知 Presenter 加载数据,而Presenter会继续调用Model层加载数据,等Model加载完毕后,回调给 Presenter,Presenter 持有View引用,再通知View更新界面。

    二、MVP的效果

    采用MVP的目的就是使得层次更加清晰,业务逻辑与 UI 分离,那么采用 MVP 以后的效果如何呢?DEMO 实现的是一个列表,效果如图下图所示,列表的内容是一些笑话信息。



    如果上面的页面采用 MVP 的模式进行设计的话,那么Activity中的代码将非常清洁!请看下面。

    public class MainActivity extends BaseActivity implements JokeView {
    
        // 不做分页加载的操作,所以这两个参数写死
        public static final String  PAGE_NUM           = "1";
    
        public static final String  PAGE_SIZE          = "20";
    
        private ListView            mListView;
    
        private JokePresenter       mJokePresenter     = null;
    
        private ArrayList<JokeInfo> mJokeInfoArrayList = null;
    
        private JokeAdapter         mJokeAdapter;
    
        @Override
        public void initVariables() {
            mJokeInfoArrayList = new ArrayList<>();
            mJokePresenter = new JokePresenterImpl(this);
        }
    
        @Override
        public void initView() {
            setContentView(R.layout.activity_main);
            mListView = (ListView) findViewById(R.id.main_page_joke_lv);
        }
    
        @Override
        public void loaderData() {
            mJokeAdapter = new JokeAdapter(this, mJokeInfoArrayList);
            mListView.setAdapter(mJokeAdapter);
            //通知 Presenter 加载数据
            mJokePresenter.getJoke(PAGE_NUM, PAGE_SIZE);
        }
    
        @Override
        public void showLoading() {
            // TODO 显示进度条
        }
    
        @Override
        public void hideLoading() {
            // TODO 隐藏进度条
        }
    
        @Override
        public void setJoke(Joke pJoke) {
            if (pJoke != null) {
                Joke.Result result = pJoke.getResult();
                if (result != null) {
                    ArrayList<JokeInfo> jokeInfoArrayList = result.getJokeInfoArrayList();
                    mJokeInfoArrayList.addAll(jokeInfoArrayList);
                    mJokeAdapter.notifyDataSetChanged();
                }
            }
        }
    
        @Override
        public void showError() {
            TextView errorView = new TextView(this);
            errorView.setTextSize(20);
            errorView.setText("请求失败了");
            mListView.setEmptyView(errorView);
        }
    }
    

    我重新定义了一下 Activity的“生命周期”,这个 MainActivity 继承了 BaseActivity ,BaseActivity 的实现如下:

    public abstract class BaseActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initVariables();
            initView();
            loaderData();
        }
    
        /**
         * 做初始化方面的工作,比如接收上一个界面的Intent
         */
        public abstract void initVariables();
    
        /**
         * 初始化控件
         */
        public abstract void initView();
    
        /**
         * 加载数据
         */
        public abstract void loaderData();
    
    }
    

    如果你觉的还不错,那么可以继续看下面了,下面将具体阐述 MVP 三个部分是如何协同操作的。

    三、View层实现

    在讲述View层实现之前,首先看一下,项目的整体结构划分,有个大致的感觉。如下图所示。感觉内容还是比较多的,但是不难,一步一步的看吧!

    项目结构划分

    如果要实现上面的效果,首先做一下需求分析,每一条的笑话实体类包括的属性有笑话内容、时间;所以建立一个 Joke 实体类是很简单的。View层承担着界面的更新,MVP 中一般将界面更新的职责都交给一个 XXView ,我们的项目姑且叫做 JokeView 。当 Model 层请求到数据的时候,通知 Presenter 层后,Presenter 层就会调用 JokeView 进行界面的更新,所以需要一个设置笑话的方法;请求会有加载时间,所以界面要显示 Loading ,请求结束后需要隐藏 Loading ;当断网等异常情况发生的时候,还需要提醒用户请求发生了错误,所以还需要显示错误界面的方法。综上,定义的 JokeView 接口如下;定义好 JokeView 后,就可以让 Activity 实现 JokeView 接口,重写里面的方法进行更新了。所以我觉得在 MVP 模式开发的过程中,最先确定的就是写一个 XXView。

    public interface JokeView {
    
        void showLoading();
    
        void hideLoading();
    
        void setJoke(Joke pJoke);
    
        void showError();
    }
    

    四、Model 层实现

    在 Model 层中做的主要的工作就是请求网络数据了。请求逻辑我用了 Volley ,具体可以看项目中是如何实现的,也是参考了网上一个开源代码,具体的地址记不清了 。

    public class JokeModelImpl implements JokeModel {
    
    
        public static final String REQUEST_SERVER_URL="http://api.jisuapi.com/xiaohua/text?";
    
        public static final String APPKEY="&appkey=9814b57c706d0a23";
    
        //http://api.jisuapi.com/xiaohua/text?pagenum=10&pagesize=3&appkey=9814b57c706d0a23
        @Override
        public void getJoke(String pNum, String pSize, final OnJokeListener pOnJokeListener) {
    
            VolleyRequest.newInstance().newGsonRequest(REQUEST_SERVER_URL+"pagenum="+pNum+"&"+"pagesize="+pSize+"&sort=addtime"+APPKEY,
                    Joke.class, new Response.Listener<Joke>() {
                        @Override
                        public void onResponse(Joke pJoke) {
                            if (pJoke != null) {
                                pOnJokeListener.onSuccess(pJoke);
                            } else {
                                pOnJokeListener.onError();
                            }
                        }
                    }, new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            pOnJokeListener.onError();
                        }
                    });
        }
    }
    

    其中 OnJokeListener 是 Presenter 层中定义的接口,用与通知 Presenter 层要调用 View 层更新数据。

    public interface OnJokeListener {
    
        /**
         * 成功的时候回调
         * @param pJoke joke
         */
        void  onSuccess(Joke pJoke);
    
        /**
         * 失败的时候回调
         */
        void  onError();
    }
    

    五、Presenter 层实现

    在 Model 层和 View 层都定义好了之后,就可以写 Presenter 层了,之前已经多次说过 Presenter 层作为 Model 和 View 的桥梁,需要持有 Model 和 View 的引用。Presenter 需要实现 OnJokeListener 接口,具体的实现如下:

    public class JokePresenterImpl implements JokePresenter, OnJokeListener {
    
        // P层作为M层和V层的衔接者,需要持有JokeView和JokeModel的引用
    
        private JokeModel mJokeModel = new JokeModelImpl();
    
        private JokeView  mJokeView;
    
        public JokePresenterImpl(JokeView jokeView) {
            mJokeView = jokeView;
        }
    
        /**
         * 调用M层取数据,getJoke由所展示的界面(Activity)调用
         * 
         * @param pNum
         * @param pSize
         */
        @Override
        public void getJoke(String pNum, String pSize) {
            mJokeView.showLoading();
            mJokeModel.getJoke(pNum, pSize, this);
    
        }
    
     /**
         * 接收M层的回调,调用View 层进行界面的刷新
         *
         */
        @Override
        public void onSuccess(Joke pJoke) {
            mJokeView.setJoke(pJoke);
        }
    
        @Override
        public void onError() {
            mJokeView.showError();
        }
    }
    

    六、总结

    最后重新梳理一下 MVP 的编写方式。
    1、 根据项目需求,写一个 XXView 接口。然后让对应的 Activity/Fragment 实现这个接口。View 层基本搞定!
    2、编写 Model 层,主要就是网络数据请求了或者其他什么耗时操作,实现方式尽情发挥你的想象,但是最后一定需要用 Presenter 层定义的接口,回调给 Presenter 通知 View 层 更新数据。
    3、编写 Presenter 层,Presenter 层需要持有 View 层和 Model层的引用,并且实现 Presenter 层定义的回调接口。在回调接口中调用 View 层的代码 进行界面更新,最重要的是,有一个调用通过Model层的方法,在此方法中,调用 Model 层请求数据。
    4、回到View 层的Activity ,调用 Presenter 层获取数据。到此完成。

    备注:为了遵守面向接口编程的原则,做了一下接口的抽取。如Presenter 中 实现了 JokePresenter 接口,Model 层中实现了 JokeModel 接口。好了,如果在阅读中,发现了有错误的地方,还望指正。

    相关文章

      网友评论

      • 真的放飞自我:网络请求如果长时间未返回,会造成内存泄露。
      • Boyko:我们项目比较复杂一直想换成MVP,尝试了一次还是有点乱,
        请问楼主, 当网络请求回来解析完数据,传回P层,要根据数据 add view ,add过程中会调用很多关于数据处理的方法,这些方法写在V层还是P层?
        LooperJing:@Boyko 对于数据处理,建议在M层中做,P层不做逻辑处理,只做数据结果分发与数据请求分发,MVP并不是三个都要有,可以一个P,多个M,或者多个P,一个M,灵活变通,不能“照搬”模式,这是我的建议
      • 小强闯江湖:花了一个晚上,大概有点明白怎么做了,明天实战去,谢谢楼主!
        LooperJing: @鱼_d667 不客气
      • 醉卧风中:讲的很清晰,通俗易懂,赞一个
        LooperJing: @浪迹天涯来了 gradle的版本对应不上,请翻墙
        170a1a37706d:楼主,我用Android Studio打开项目,一直停在“Building JokeMVP gradle project info”。
        没写过Android代码,是java程序猿
      • 叔叔有糖吃:有个困扰我的问题,
        public void onSuccess(Joke pJoke) {
        mJokeView.setJoke(pJoke);
        }
        这个是网络请求回调回来的处理,万一网络延迟,此时界面已经销毁,此时就有可能mJokeView为null,我目前的项目做法是在所有回调处理中加入了非空判断,但是感觉不是很友好,不知道有没有更好的思路~
        真的放飞自我: @叔叔有糖吃 在Activity的onDestroy取消所有网络请求
        LooperJing:界面已经销毁了,就无需设置了,只能非空判断了
      • 3481319ea87f:一直用MVP开发,可以再结合databinding和dagger做依赖注入一起玩,就爽了,😂
      • Passon_Fang:冒昧的问一句:BaseActivity中的抽象方法有没有必要定义为public??
        LooperJing:@DoubleFang 最好定义成public,因为项目中Activity不一定都放在一个包中
      • Waikey:不错
        Waikey:@醉风景 其实我的BaseFragment就是你这种模式,重新定义了生命周期
        LooperJing: @Waikey 😁
      • f49cef042ed7:才看完书,还没进行过开发:disappointed_relieved:,直接看架构,框架等等,希望楼主能给个实际项目让我膜拜膜拜:+1:
      • 带心情去旅行:总结得挺好的,受教了。不过看不到JokeModel的代码,总觉得少了什么
        LooperJing: @带心情去旅行 具体可以看github上的代码的
      • zml_smile:学习了…
        LooperJing: @zml_smile 共同进步
        LooperJing:@zml_smile 共同学习
      • 齐刘海姑娘:哈喽~您的文章已收录专题【我不是程序猿,请叫我攻城狮】http://www.jianshu.com/collection/db91065b98c6,欢迎关注投稿哦~ :kissing_heart: :heart: :heart: :smile:
        LooperJing:@齐刘海姑娘在DevStore 谢谢
      • 王元_Trump:还是不习惯 没用过
      • 王元_Trump:感觉要是这样写一个项目的话得多累啊
        3481319ea87f: @王元_Trump 还好吧,一直用MVP,但是有稍微改进,对解耦很有用,特别在做假数据和单元测试,业务复用的时候,很有用,就是前期代码写的有点多
        王元_Trump:@王元_Trump 有理有理
        LooperJing:@王元_Trump 不同的项目肯定有不同的架构,我觉得mvp这种模式优点已经跟明确了,缺点是对于简单的项目,由于层次的划分,显得有点过度封装,但是对于一个稍微复杂类型的项目,这种模式的优点就出来了,我看过一个项目的主activity在继承三层之后,仍然有4000多行的代码,如果能采用mvp的思想,就不会那么臃肿了
      • dsx:很有用
        LooperJing:@dsx 谢谢,共同进步
      • 0c28ba0c531d:谢谢分享
        LooperJing:@MPhone 嗯嗯,有错误还望指出来,共同进步
      • Bigmercu:可以参考一下Google官方的todo-MVP 可能你会有些收获
        LooperJing: @Bigmercu 谢谢,共同学习!
      • Jenchar:你的目录结构名起得,我给100分。。
        LooperJing: @Jenchar 哈哈,还好吧

      本文标题:Android架构设计---MVP模式第(一)篇之基本认实

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