MVP 笔记

作者: 英勇青铜5 | 来源:发表于2018-04-05 17:00 被阅读203次

    公司项目代码中使用MVP,之前并没有在项目中使用过,记录一下最近一段个人写代码时学到的习惯,代码中有哪些不合理的地方,希望可以留言指出

    1. Demo 代码

    项目中,是按照模块分的包,例如新闻相关的代码就放在同一个news包下

    写代码时一个重要的思路:

    V层只负责UI展示,任何数据源尽量不放V层

    P层负责逻辑处理,尽量不要有任何Android环境的代码,尤其是 Context,更符合MVP的规范,并且方便单元测试

    M层负责数据获取,主要负责从网络、数据库、本地文件中获取数据源

    写完代码,检查写的是否合理,简单的办法就是查看各个分层代码中类的引用


    1.1 Activity 代码

    UI

    整个Actiivty就一个Button,点击Button请求网络,然后将数据显示

    /**
     * 展示请求的新闻字符串
     */
    public class NewsActivity extends AppCompatActivity 
                                     implements NewsContract.View {
        private TextView mTvContent;
        private NewsContract.Presenter mPresenter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_news);
            mPresenter = new NewsPresenter(this);
            initView();
        }
        
        /**
         * 取消网络请求
         */
        @Override
        protected void onPause() {
            super.onPause();
            mPresenter.onPause();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mPresenter != null) {
                mPresenter.onDestroy();
                mPresenter = null;
            }
        }
    
        /**
         * 展示请求的内容
         *
         * @param content 数据
         */
        @Override
        public void showRequestContent(String content) {
            mTvContent.setText(content);
        }
    
        private void initView() {
            mTvContent = findViewById(R.id.activity_news_tv_content);
            Button btNews = findViewById(R.id.activity_news_bt_news);
            btNews.setOnClickListener(v -> mPresenter.onBtNewsClick());
        }
    }
    

    onCreate()中,正常的初始化,并初始化Presenter。有时实际项目中,需要考虑下初始化的合理顺序

    就一个Button,在点击监听中,mPresenter.onBtNewsClick(),调用P层逻辑代码

    onPause()中,取消未完成的网络请求

    onDestroy()方法中,做一些回收销毁工作

    void showRequestContent(String content)就是实现的V层接口回调的方法,在请求网络后,展示内容


    1.2 Base 接口

    BasePresenter

    public interface BasePresenter {
        /**
         * 在 Activity onDestroy()生命周期方法
         */
        void onDestroy();
    }
    

    声明每一个Presenter都要实现的销毁阶段方法


    BaseView

    public interface BaseView {
    }
    

    在这个Demo中,BaseView里并没有声明啥方法,实际项目代码中根据实际需要声明View层的共有方法


    BaseCallback

    public interface BaseCallback {
        /**
         * 成功回调
         *
         * @param content 请求内容
         */
        void onSuccess(String content);
    
        /**
         * 失败回调
         *
         * @param errorInfo 错误信息
         */
        void onError(String errorInfo);
    }
    

    要进行网络请求,肯定需要用到Callback,个人习惯定义一个Base型接口


    1.3 Contract 契约

    interface NewsContract {
        interface Model {
            /**
             * 从网络请求新闻内容
             *
             * @param callback 结果回调
             */
            void requestNewsFromNet(BaseCallback callback);
    
            /**
             * 取消网络请求
             */
            void cancel();
        }
    
        interface View extends BaseView {
            /**
             * 展示请求的内容
             *
             * @param content 数据
             */
            void showRequestContent(String content);
    
        }
    
        interface Presenter extends BasePresenter {
            /**
             * 点击按钮
             */
            void onBtNewsClick();
    
            /**
             * 在 onPause() 生命周期
             */
            void onPause();
        }
    }
    

    一般View层接口主要以show方法为主

    Presenter层方法个人习惯以onXX()形式,方便在查看代码,更容易通过名字知道方法负责做啥


    1.4 NewPresenter 代码

    public class NewsPresenter implements NewsContract.Presenter {
        private NewsContract.View mView;
        private NewsContract.Model mNewModel;
    
        NewsPresenter(NewsContract.View mView) {
            this.mView = mView;
            mNewModel = new NewsModel();
        }
    
        /**
         * 点击按钮,请求网络
         */
        @Override
        public void onBtNewsClick() {
            mNewModel.requestNewsFromNet(new NewsCallback(this));
        }
    
        /**
         * 取消网络请求
         */
        @Override
        public void onPause() {
            mNewModel.cancel();
        }
    
        /**
         * 销毁
         */
        @Override
        public void onDestroy() {
            if (mNewModel != null) {
                mNewModel = null;
            }
            if (mView != null) {
                mView = null;
            }
        }
    
        /**
         * 成功处理
         *
         * @param content 内容
         */
        private void onRequestSuccess(String content) {
            mView.showRequestContent(content);
        }
    
    
        /**
         * 失败处理
         *
         * @param errorInfo 错误信息
         */
        private void onRequestError(String errorInfo) {
            mView.showRequestContent(errorInfo);
        }
    
        /**
         * 结果回调
         */
        private static class NewsCallback implements BaseCallback {
    
            private WeakReference<NewsPresenter> mReference;
    
            private NewsCallback(NewsPresenter presenter) {
                mReference = new WeakReference<>(presenter);
            }
    
            @Override
            public void onSuccess(String content) {
                NewsPresenter newsPresenter = mReference.get();
                if (newsPresenter != null) {
                    newsPresenter.onRequestSuccess(content);
                }
            }
    
            @Override
            public void onError(String errorInfo) {
                NewsPresenter newsPresenter = mReference.get();
                if (newsPresenter != null) {
                    newsPresenter.onRequestError(errorInfo);
                }
            }
        }
    }
    

    在构造方法关联View层,并初始化Model层对象

    Model进行网络请求,需要一个接口,回调Presenter的代码来更新UI

    Model中的网络耗时任务持有Presenter对象,而Presenter又持有View对象,当Activity关闭时,Activity对象无法被回收造成内存泄漏

    NewCallback写成静态内部类,并且使用WeakReference软引用,使Presenter能够及时的回收,这样Activity也能及时回收


    1.5 NewsModel 代码

    public class NewsModel implements NewsContract.Model {
        private final static String NEWS_URL = "http://news-at.zhihu.com/api/4/news/latest";
    
        /**
         * 从网络获取新闻内容
         *
         * @param callback 结果回调
         */
        @Override
        public void requestNewsFromNet(BaseCallback callback) {
            OkGo.<String>get(NEWS_URL).execute(new InnerCallback(callback));
        }
    
        /**
         * 取消网络请求
         */
        @Override
        public void cancel() {
            OkGo.getInstance().cancelAll();
        }
    
        /**
         * 结果回调
         */
        private static class InnerCallback extends StringCallback {
            private WeakReference<BaseCallback> mReference;
    
            private InnerCallback(BaseCallback callback) {
                mReference = new WeakReference<>(callback);
            }
    
            /**
             * 网络
             */
            @Override
            public void onSuccess(Response<String> response) {
                String content = response.body();
                BaseCallback callback = mReference.get();
                if (callback != null) {
                    callback.onSuccess(content);
                }
            }
    
            @Override
            public void onError(Response<String> response) {
                super.onError(response);
                String content = response.body();
                BaseCallback callback = mReference.get();
                if (callback != null) {
                    callback.onError(content);
                }
            }
    
            /**
             * 缓存
             */
            @Override
            public void onCacheSuccess(Response<String> response) {
                super.onCacheSuccess(response);
                String content = response.body();
                BaseCallback callback = mReference.get();
                if (callback != null) {
                    callback.onSuccess(content);
                }
            }
        }
    }
    

    Demo网络请求使用的OkGo网络框架,很强大蛮好用的网络框架

    内部也使用一个静态内部类,使用WeakReference


    2. 单元测试 TODO

    单元测试主要针对的Presenter,主要的逻辑都在P

    View层主要是UIModel主要是拉取数据,时间紧,就先不做,不赶时再做

    P层单元测试通过后,很大程度就说明逻辑通过

    由于P层不包含Android环境代码,可以使用JUint + Mockit来进行单元测试。这也是为啥说Presenter中尽量不要有Android SDK代码的原因

    单元测试正在学,等实际做一段后再来补充

    相关文章

      网友评论

      • 明月依希:描述不够准确。Activity就是Context来的,怎么能说“P层负责逻辑处理,尽量不要有任何Android环境的代码,尤其是 Context,更符合MVP的规范,并且方便单元测试”
        英勇青铜5:@明月依希 多谢指出问题。无论mvc还是mvp,现在理解都不深,现在还有些照葫芦画瓢。我这里是意思是,在我们项目中,为了方便单元测试,p层不直接包含任何安卓sdk的环境对象。
      • 等风来_Android:大佬,在哪里上班?

      本文标题:MVP 笔记

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