美文网首页Android架构Android开发干货集中营1-Android开发知识
Android架构设计---MVP模式第(二)篇,如何减少类爆炸

Android架构设计---MVP模式第(二)篇,如何减少类爆炸

作者: LooperJing | 来源:发表于2017-03-16 21:29 被阅读5834次

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

    今天是2017年3月16日,差不多一年前,写过一篇MVP基础类型的文章Android架构设计---MVP模式第(一)篇,梳理了一下MVP怎么使用。OK,先回忆一下。

    一、基础知识

    1.1、MVP分层

    总共分成三层

    • a 、View: 视图层,对应xml文件与Activity/Fragment;
    • b 、Presenter: 逻辑控制层,同时持有View和Model对象;
    • c 、Model: 实体层,负责获取实体数据。

    1.2、实现方式

    总共四步

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

    1.3、原理图

    1.4、暴露的问题

    可见采用MVP模式之后,虽然类数量变大,但是逻辑更加清晰,遵守了高内聚,低耦合的设计原则,完全避免了几千行代码的Activity,这势必可以提交整个团队的开发效率。但是仍然美中不足,因为每个功能块的代码都是类似的,只是细节上会有所不同,正如上篇博客有些人评论就有问到这个问题。

    所以我们要将MVP进行优化,所以这篇博客的目标就是减少"类爆炸!",用的更爽。

    二、MVP设计优化

    关于MVP减少类数量,已经有很多人研究过,我将站在他们的肩膀上进行学习,个人觉得模式是一种思想,不是“法律”,所以MVP没有固定的写法,结合需求,理清架构,应该都可以有自己的独特MVP写法。本文不揉杂Rxjava,DataBinding以及一些注解框架,降低理解MVP的难度,我参考一个开源项目,优化了一下,相比之前,简洁不少。

    2.1、优化对比

    先看一下优化的效果对比

    优化前 优化后

    优化之前,实现一个网络请求+UI需要八个类(去除adapter,实体类等),优化之后,只需要四个类就搞定了(看joke包中的4个类),现在介绍一下优化步骤。

    2.2、Base类抽取

    我们类臃肿的原因是,M层,V层,P层没有抽取,导致每一个功能模块都对应着一个M,V,P三个类。现在讲三个类抽取一下:

    public interface BaseView {
    
        void showLoading();
    
        void hideLoading();
    
        void showError();
    
    }
    
    

    不多解释,定义了几个与UI显示相关的方法

    public interface BaseModel {
        
    }
    

    暂时还是空的

    public abstract class BasePresenter<M, V> {
    
    
        public M mModel;
    
        private V mView;
    
        public WeakReference<V> mViewRef;
    
    
        public void attachModelView(M pModel, V pView) {
    
            mViewRef = new WeakReference<V>(pView);
    
            this.mModel = pModel;
        }
    
    
        public V getView() {
            if (isAttach()) {
                return mViewRef.get();
            } else {
                return null;
            }
        }
    
        public boolean isAttach() {
            return null != mViewRef && null != mViewRef.get();
        }
    
    
        public void onDettach() {
            if (null != mViewRef) {
                mViewRef.clear();
                mViewRef = null;
            }
        }
    }
    

    Presenter层仍然要持有M,V的强引用,在attachModelView这个方法中,对两个对象进行赋值,这里可以看到我简单是采用了弱引用的方式去保存这个View的对象引用,减少内存泄露的可能性。OK,这就是MVP模式中的三个base类。

    2.3、合约分包模式

    Contract模式就是将三个接口合并为一个,如下:

    public interface JokeContract {
    
        interface JokeView extends BaseView {
            void setJoke(Joke pJoke);
        }
    
        interface JokeModel extends BaseModel {
             void requestJoke(String pNum, String pSize, MVPListener pMVPListener);
        }
    
        abstract class JokePresenter extends BasePresenter<JokeModel, JokeView> {
    
            public abstract  void requestJoke(String pNum, String pSize);
        }
    }
    
    

    以后我们不需要单独写View层和Presenter层了,将相同模块的M,V,P三层定一个合约,互相搞基,放在一块,统一管理。我们还需要看一个基类

    public abstract class BaseActivity<T extends BasePresenter, M extends BaseModel> extends Activity {
    
    
        public T mPresenter;
    
        public M mModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutResId());
            //内部获取第一个类型参数的真实类型  ,反射new出对象
            mPresenter = CreateUtil.getT(this, 0);
            //内部获取第二个类型参数的真实类型  ,反射new出对象
            mModel = CreateUtil.getT(this, 1);
            //使得P层绑定M层和V层,持有M和V的引用
            mPresenter.attachModelView(mModel, this);
            initView();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mPresenter.onDettach();
        }
    
        //子类Activity实现
        public abstract void  initView();
        //子类Activity实现
        public abstract int getLayoutResId() ;
    }
    

    顺便看一下CreateUtil,对于这种方式不明白的,移步Java中的getGenericSuperclass方法的基本用法

    public class CreateUtil {
    
        static <T> T getT(Object o, int i) {
            try {
    
                return ((Class<T>) ((ParameterizedType) (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    内部获取第i个类型参数的真实类型 ,反射new出对象.

    2.4、Presenter 的实现

    这部分没办法,每一个业务需求不一样,都需要重新编写的,可以适当变化,建议一个模块编写一个。

    public class JokePresenter extends JokeContract.JokePresenter {
    
        @Override
        public void requestJoke(String pNum, String pSize) {
    
            final JokeContract.JokeView mView = getView();
    
            if(mView==null){
                return;
            }
    
            mView.showLoading();
    
            mModel.requestJoke(pNum, pSize, new MVPListener<Joke>() {
    
                @Override
                public void onSuccess(Joke pJoke) {
                    mView.hideLoading();
                    mView.setJoke(pJoke);
                }
                @Override
                public void onError() {
                    mView.hideLoading();
                    mView.showError();
                }
            });
        }
    }
    
    

    现获取父类已经绑定的mView,把loading显示出来,然后用父类已经绑定的Model交给去请求数据,等数据请求完毕后,在通知mView去更新UI。这个跟以前都差不多。其中的MVPListener作为Model的回调接口,作用是把数据传递给P层。

    
    public interface MVPListener<E> {
    
        /**
         * 成功的时候回调
    
         */
        void  onSuccess(E pJoke);
    
        /**
         * 失败的时候回调
         */
        void  onError();
    }
    
    

    2.5、Moldel 的实现

    
    public class JokeModel implements JokeContract.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 requestJoke(String pNum, String pSize, final MVPListener pMVPListener) {
    
            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) {
                                pMVPListener.onSuccess(pJoke);
                            } else {
                                pMVPListener.onError();
                            }
                        }
                    }, new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            pMVPListener.onError();
                        }
                    });
        }
    }
    
    

    这一部分跟以前相比基本没有变化,不多解释,可以看看上一篇博客。

    2.6、Activity 的实现

    public class JokeActivity extends BaseActivity<JokePresenter, JokeModel> implements JokeContract.JokeView {
    
    
        public static final String PAGE_NUM = "1";
    
        public static final String PAGE_SIZE = "20";
    
        private ListView mListView;
    
        private ProgressBar mLoadingBar;
    
        private ArrayList<JokeInfo> mJokeInfoArrayList = new ArrayList<>();
    
        private JokeAdapter mJokeAdapter;
    
        @Override
        public int getLayoutResId() {
            return R.layout.activity_joke;
        }
    
        @Override
        public void initView() {
            mLoadingBar= (ProgressBar) findViewById(R.id.pressbar);
            mListView = (ListView) findViewById(R.id.main_page_joke_lv);
            mJokeAdapter = new JokeAdapter(this, mJokeInfoArrayList);
            mListView.setAdapter(mJokeAdapter);
            mPresenter.requestJoke(PAGE_NUM, PAGE_SIZE);
        }
    
        @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 showLoading() {
            mLoadingBar.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void hideLoading() {
            mLoadingBar.setVisibility(View.INVISIBLE);
        }
    
        @Override
        public void showError() {
            TextView errorView = new TextView(this);
            errorView.setTextSize(20);
            errorView.setText("请求失败了");
            mListView.setEmptyView(errorView);
        }
    }
    

    短短六七十行代码就OK了,效果如下。


    效果.gif

    最后有一个MVPHepler插件,可以自动实现MVP部分代码,哈哈,对于这种插件,公司完全可以写一个,以后开发就不用写了。

    20161021095635792.gif20161021095635792.gif

    终于写完了,下班回家,Please accept mybest wishes for your happiness and success !

    参考:
    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0513/4260.html
    http://blog.csdn.net/dantestones/article/details/51445208
    http://www.cnblogs.com/liemng/p/5955169.html
    http://blog.csdn.net/z2wenfa/article/details/52873009
    http://blog.csdn.net/dantestones/article/details/51445208
    https://github.com/googlesamples/android-architecture/tree/todo-mvp/
    http://android.jobbole.com/82051/

    相关文章

      网友评论

      • 澳门记者:View和Presenter是一一对应的,View是一个抽象的视图,Presenter是视图对应的逻辑,但是Model并不应该在合约里面,Model应该是可以复用的。View是抽象的,不一定是指某个Activity,复杂情况下也可能一个Activity实现多个View接口,持有相应的多个Presenter。

        一些粗浅的拙见。
      • 小欧lq:能发一份源码吗 1012014758@qq.com,给个github地址也可以,谢谢model层跟view层接触是不是违背了mvp的设计原理,model层跟p层接触,m层不会接触到V层,
      • 谁的春春不迷茫:作者,还有一个问题就是,你在View层是直接的引用到了P层的一个具体实现类,而不是他的一个抽象或者接口,有点违背依赖倒置原则,长远角度看扩展性不是很好,你感觉呢。
      • 谁的春春不迷茫:作者,你这样写,我感觉有个缺点,就是你在View层中初始化了Model层,那么我在View层其实是可以直接拿到Model层去操作数据的,感觉违背了MVP解耦的思想,将控制权应该只交给P层,你感觉呢。
      • 我一定会学会:一个页面多个请求,怎么搞!Model怎么写啊,大佬!
      • 獨孤不敗:在presenter中既持有View的引用,又持有Model的引用,这样貌似就变成MVC了吧?
        下一刻_ebae:和MVC相差很大
      • AWeiLoveAndroid:如果是一对多的情况下 你这个就不实用了 一个v 对应多个p 或者一个p对应多个m层 处理起来就比较复杂
      • 花花笑脸人888:大神 求源码 1007812935@qq.com 谢谢
      • Dora_Liang:大佬,有源码,发一份,谢谢哦
      • 梦华芳秋:这样的文章,确实很好!值得细读和实践!
        LooperJing:@梦华芳秋 :relieved:
      • 小丸子_unique:Looper大神,请问这个有项目地址吗?
        LooperJing: @小丸子_unique 是的
        小丸子_unique:@LooperJing 我加一下您的qq 是这个吧3556721668
        LooperJing: @小丸子_unique 我可以明天发给你
      • leach_chen: 你这个就是把 M V P 三层接口合到JokeContract里面了吧?只是类文件少了,类个数没少吧。而且不同业务,都要定义不同JokeContract吧?
      • 请叫我田胖子:我仔仔细细反反复复想了很久,我有问题啊楼主 。在mvp中这个activity只需要使用一个类型的Presenter吗?比如我这个页面如果要显示数据类型A,也需要显示数据类型B。按照现在这个mvp架设,我在model的实现类里去做对数据类A,B操作的实现 最后我用Presenter就完成。好到这里 如果有另外一个Activity 它显示数据类型B,数据类型C。那么利用mvp我就要定一个model的接口 对数据类型B,C进行操作,但其实对数据B的处理和前面一个页面是一模一样的。我现在的问题是我要如何复用对B数据处理这部分操作?
        叨叨宅:@LooperJing 有些疑问啊 楼主,当一个Activity中用到了多个Presenter(为了Presenter复用)这样处理mvp就行不通了
        LooperJing: @请叫我田胖子完全可以
      • X_d420:终于理清MVP了 谢谢 !
        LooperJing:@X_d420 不客气
      • 2e6e8edf2957:createUtils通过反射创建presenter等类,如果我的子类中不需要presenter对象呢?建议中间可以创建一个不需要presenter的基类activity
        LooperJing: @laulee 怎会是空呢
        2e6e8edf2957:@LooperJing 又要创建空的类文件,岂不是很浪费?
        LooperJing:@laulee 可以有,你不用就是了,退出Activity时候销毁掉
      • 3cf4933d1229:attachModelView引用报错空指针异常,,什么原因
        3cf4933d1229:@LooperJing CreateUtil里面的静态方法不能被调用,会报错,我看是没有家访问修饰符
        3cf4933d1229: @LooperJing CreateUtil这个是安照你的写的,报空指针异常
        LooperJing:@开着拖拉机迎接春天 你检查一下什么错误吧,我项目中是OK的
      • 李简书:我觉得不应该在activity中含有model的引用,而且在BasePresenter中你拿到了model并没有什么用处
        请叫我田胖子:子类是只看见了 Presenter,但其实子类也持有对model的引用的,这个方案我觉得最大的不好就是让model和view在activity里面碰头了
        请叫我田胖子:我也觉得 activity中不应该持有对model的引用,如果这样做其实没必要用你的Presenter了
        LooperJing:BaseActivity中有Model的引用,目的是让P层绑定M层和V层,在子类Activity只使用到了Presenter
      • luokanghui:你的弱引用用法错了
        LooperJing:@咖啡_e334 最先用的强引用,忘记删除,已经更正,谢谢指出
        luokanghui: @LooperJing attach那里你用mView强引用了,弱引用就没用了
        LooperJing:那该怎么用呢?
      • 小编:思路不错(*๓´╰╯`๓)♡
        LooperJing:@小编 :smile: smile: smile:
      • Android之路:nice,真棒
        LooperJing:谢谢
      • 1ff85f59a87a:mark
        LooperJing: @昵称怎么取啊 谢谢支持
      • 78dc7eb19e99:很喜欢看你的文章,看了好多了
        LooperJing: @78dc7eb19e99 谢谢支持
      • Hidetag:还有更简洁的,把Presenter作为注解
        LooperJing: @S_H_I_E_L_D 应该和这种效果一样
        Hidetag:@LooperJing 就是把presenter作为注解引用,使用时不需要反复创建N多接口,一个p 一个v就够了,两个类。如果有m,再新建m的类
        LooperJing: @S_H_I_E_L_D 麻烦给个思路或者博客参考一下

      本文标题:Android架构设计---MVP模式第(二)篇,如何减少类爆炸

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