美文网首页Android架构android技术收藏Android开发精选
Google官方架构MVP解析与实战【从零开始搭建android

Google官方架构MVP解析与实战【从零开始搭建android

作者: 程序员Anthony | 来源:发表于2016-05-07 14:38 被阅读38540次

    由于对之前的项目做出了更改,所以下面内容在2016.8.10做出了更新

    本文章原地址:Anthony的博客

    1 前言

    当然对于MVP的解说也是使用也是层出不穷,我也网络上也能看到各种版本的解说,之前博客也有文章的更新,里面有MVP的详细说明和项目代码--->Android中的MVP模式,带实例

    本篇文章将参考 google官方android MVP架构项目的实现,来实现自己的项目。或许看了这篇文章之后,你再去梳理一下google官方架构项目,会让你收获更多。官方的实例肯定具有更好的权威性

    推荐关注安卓各种架构相关文章合集github地址:AndroidArchitectureCollection

    2 google官方MVP架构解析

    1 项目目录

    打开github,展开项目目录,会发现项目结构的组织方式是按照功能进行分模块的,当然根据个人情况,也可以按照ui,model,view,presenter这种情况进行划分组织目录。

    google官方MVP架构目录视图
    2 具体实现流程

    我们将关注度放到具体的一个taskdetail模块当中来解析实现MVP的流程。

    taskdetail模块
    2.1 TaskDetailContract
    可以看到这里是通过一个协议类XXXContract来对View和Presenter的接口进行继承。这样做的好处也就是,我们可以将基础的View层的操作放在BaseView里面,对基础的Presenter层的操作放在BasePresenter里面。减少后续代码的重复。一个协议类也将View和Presenter管理起来,方便操作。

    2.2 BaseView
    那么来看看BaseView,主要是有一个setPresenter的操作,MVP中Presenter和View层是需要交互的,这里通过setPresenter操作,我们也就可以获得相应的Presenter的实例在View层直接mPresenter.xxx()进行交互了。我们可以在下面的代码中看到官方示例代码是通过在TaskDetailPresenter的构造函数中调用mTaskDetailView.setPresenter(this)完成这一步操作的。

    所以在我们自己的代码中,我们也可以将加载的loading,以及加载错误页面,加载失败页面等操作放在BaseView里面,这是每个View都会有的:


    2.3 BasePresenter
    BasePresenter中只有一个start方法,表示“开始”,我们可以在这里进行数据加载初始化等。


    2.3 TaskDetailActivity
    可以看到这里这里一个初始化了fragment的activity,主要操作当让是new了一个XXXPresenter。activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来,

    2.4 TaskDetailFragment
    Fragment是MVP中View的实现类,它不与Model 层进行交互,只和presenter的实例进行交互。


    2.5 TaskDetailPresenter
    Presenter的真正实现类,在这里进行model层和view层的交互。

    通过上面的分析,在来梳理一下整个步骤:
    1 官方MVP实例,通过协议类XXXContract来对View和Presenter的接口进行内部继承。是对BaseView和BasePresenter的进一步封装,所以我们实现的View和Presenter也只需要继承XXXContract中的对应内部接口就行。

    2 activity的作用主要是创建View(这里是相应的fragment),以及创建presenter,并把view传递给presenter(完成presenter对view实例关联操作)

    3 在presenter的实现类的构造函数中,通过view的setPresenter,让view获得了presenter实例。这样view中就可以对Presenter中的方法进行操作了。(完成view对presenter实例关联操作)

    4 在presenter的实现类中,可以对Model数据进行操作。实例中,数据的获取、存储、数据状态变化都是model层的任务,presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。

    3 实战应用:

    说了这么多,通过一个一个最为简单的spalsh页面的搭建,来完整的使用MVP吧。

    3.1 BaseView
    我在这里没有添加setPresenter方法,而是将loading,以及加载错误,网络加载错误等页面都放在了这里面。

    /**
     * Created by Anthony on 2016/5/3.
     * Class Note:
     * interface for MVP View in all of the project
     */
    public interface BaseView {
    
        void showMessage(String msg);
    
        void close();
    
        void showProgress(String msg);
    
        void showProgress(String msg, int progress);
    
        void hideProgress();
    
        void showErrorMessage(String msg,String content);
    }
    
    

    3.2 BasePresenter
    在我的项目中,我采用的是在BasePresenter中获取View的实例,也就是通过下面的attachView方法完成了View和Presenter的关联性。

    public interface BasePresenter<T extends BaseView> {
        void attachView(T view );
        void detachView();
    }
    

    这个方法会在相应的Presenter的实现类里面得到实现。而我们需要通过在相应的View类里面通过mPresenter.attachView(this)进行初始化关联,这样就可以在Presenter的实例中使用View的实例。那么上面这个mPresenter实例我是怎么在View的实现类中得到的呢? 答案是使用Dagger2的Inject获取。可以参考我的文章Google官方MVP+Dagger2架构详解 进行Dagger2的学习。

    @Inject
    SplashPresenter mPresenter;
    

    3.3 SplashContract
    同样的我们也将采用契约类来完成Presenter和View接口的展现。这里Presenter主要是完成加载数据(在splash页面中加载数据是应用普遍应当考虑的一种方式。)

    /**
     * Created by Anthony on 2016/5/31.
     * Class Note:
     * contract class for splash view & presenter
     */
    public interface SplashContract {
        interface Presenter extends BasePresenter<View> {
            void initData();
        }
    
        interface View extends BaseView {
            void toMainActivity();
        }
    }
    
    

    3.4 SplashContract.View的实现SplashActivity
    官方示例代码采用的方式是fragment作为View的实现,这里灵活变通,采用Activity作为MVP中View的实现。这里继承自AbsBaseActivity其中完成了一些初始化操作,将会在另外的文章中进行讲解。这里我们就采用Dagger2进行了SplashPresenter的实例的获取。也实现了toMainActivity方法,用于跳转到主页面。

    /**
     * Created by Anthony on 2016/5/31.
     * Class Note:
     * this class is simple but indicates how to use MVP in your project
     *
     * implements of splash view
     */
    public class SplashActivity extends AbsBaseActivity implements SplashContract.View {
    
        @Inject
        SplashPresenter mPresenter;
    
    
        @Override
        public void toMainActivity() {
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);
        }
    
    
        @Override
        protected void initViewsAndEvents() {
            mPresenter.attachView(this);
            mPresenter.initData();
        }
    
        @Override
        protected int getContentViewID() {
            return R.layout.activity_splash;
        }
    
        @Override
        protected void injectDagger(ActivityComponent activityComponent) {
            activityComponent.inject(this);
        }
    
    }
    

    3.5 SplashContract.Presenter 的实现SplashPresenter
    这里对Presenter的实现类是SplashPresenter,我们需要实现的方法自然有三个attachView,detachView,以及initData三个方法。

    /**
     * Created by Anthony on 2016/5/31.
     * Class Note:
     * presenter for splash view
     */
    public class SplashPresenter implements SplashContract.Presenter {
    
        private static final short SPLASH_SHOW_SECONDS = 1;
        private long mShowMainTime;
    
        private SplashContract.View mView;
        private Context mContext;
        private Subscription mSubscription;
    
        @Inject
        ToastUtils mToastUtil;
    
        @Inject
        DataManager mDataManager;
    
    
        private MyApplication mApplication;
    
        @Inject
        public SplashPresenter(@ActivityContext Context context, MyApplication application) {
            mContext = context;
            this.mApplication = application;
        }
    
    
        @Override
        public void initData() {
            mShowMainTime = System.currentTimeMillis() + SPLASH_SHOW_SECONDS * 2000;
    
    
            //load channel list data ,then save to database
            mSubscription = mDataManager.loadChannelList(Constants.FIRST_MENU_URL)
                    .doOnNext(mDataManager.saveChannelListToDb)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new HttpSubscriber<List<Channel>>() {
                        @Override
                        public void onNext(List<Channel> channels) {
    //                        mApplication.channels = channels;//load to global instance
                            showView();
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            super.onError(e);
                        }
                    });
        }
    
        /**
         * menu url to load channels
         *
         * @return
         */
    //    private String getFirstMenuUrl() {
    //        return "raw://news_menu";  //local data fot testing
    //    }
    
        private void showView() {
            AsyncTask<String, String, String> showMainTask = new AsyncTask<String, String, String>() {
                @Override
                protected String doInBackground(String[] params) {
                    if (System.currentTimeMillis() < mShowMainTime) {
                        try {
                            long sleepTime = mShowMainTime - System.currentTimeMillis();
                            if (sleepTime > 0) {
                                Thread.sleep(mShowMainTime - System.currentTimeMillis());
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    return null;
                }
    
                @Override
                protected void onPostExecute(String o) {
                    mView.toMainActivity();
                    mView.close();
                }
            };
    
            showMainTask.execute();
        }
    
    
        @Override
        public void attachView(SplashContract.View view) {
            mView = view;
        }
    
        @Override
        public void detachView() {
            mView = null;
            if (mSubscription != null && !mSubscription.isUnsubscribed()) {
                mSubscription.unsubscribe();
            }
        }
    
    
    }
    
    

    这里需要关注的点:我们在attackView中完成了view的实例获取mView,以及在需要解除绑定的时候使用detachView方法。这样我们就可以在当前Presenter的全局中使用View的实例了。

        @Override
        public void attachView(SplashContract.View view) {
            mView = view;
        }
    
        @Override
        public void detachView() {
            mView = null;
            if (mSubscription != null && !mSubscription.isUnsubscribed()) {
                mSubscription.unsubscribe();
            }
        }
    

    注意点:
    1 细心的你可能会发现,那么上面的所有BaseView的方法在哪里实现呢?
    答案是所有Activity都需要继承的AbsBaseActivity中


    这样我们就可以让所有activity继承AbsBaseActivity,他们都是实现了BaseView的所有方法的状态。这样就提供了统一的界面化处理而且减去了许多重复的代码。
    2 上面无论是官方的代码还是我们自己的代码,由于使用MVP模式,都会降低模块之间的耦合性。所以这时候能够正确的关联View,Presenter和Model层之间的数据显得尤为的重要。
    3 对于Model层的数据,我们可以看到我是在initData方法中调用DataManger的loadChannelList进行数据的加载的。关于Model层这方面的内容,已经在文章浅析MVP中model层设计 进行了分析,不再赘述。

    4 参考资料:

    Android官方MVP架构示例项目解析
    AndroidArchitectureCollection

    相关文章

      网友评论

      • 凸图土吐:你好
        根据你的实战 我尝试把公司曾经的小项目 借助Dagger2 转成MVP模式
        目前遇到的问题:
        View只是负责展示,但是我们原本项目 界面之前的数据传输 又依赖于Android的组件,例Activity,所以我想了1种方案,用第三方的库进行数据传输 例eventbus,以前挺反感用这种方式进行数据传输 抛弃了Android自带组件的数据传输方式 不知道大神有什么可推荐的方式吗?
        凸图土吐:@安东尼_Anthony :flushed: 学习了 一直在用Rxjava 没想到Rxjava的妙用...
        程序员Anthony:@凸图土兔_pa :joy: 我现在只会用Rxbus了。经过Rxjava结合EventBus的RxBus真的很好用,解耦的同时代码也是异常的简洁。
      • 76f76c02a29b:例子很详细,讲解的也很好,学习了,谢谢
      • 暴走的兜兜:我想问下里面的泛型是怎么回事,看了好几篇文章都没看懂,为什么引入了泛型?什么时候用泛型?:smiley:
      • 从01开始:学习了,实战代码的链接在哪里呢
      • 哎呦哥哥QAQ:不是说数据的获取是model层的任务吗?为什么文中写在了SplashPresenter 里啊。。。而且没看到model的实现类啊。。。看好几篇关于mvp的文章都是代码写起来就跟说好的根本不一样啊。。。搞的很懵逼。。。:disappointed_relieved:
      • 陈有余:您好,实战应用的源码地址有吗?
      • openGL小白:想请教楼主,有些逻辑,会在view层的界面显示错误信息后,弹出对话框,对话框里的确定会再请求业务逻辑并触发其结果回调,这对话框的代码,是放到view层触发?还是放到Presenter层中?放到Presenter层中是不是不符合mvp的规范?
      • 梦华芳秋:值得学习!
      • 蘑菇君的小小世界:你好,很喜欢你写的文章!这里有点疑问想请教一下。我在使用MVP模式的时候,对于V层的接口里的应该有方法有些疑问。比如一个加载列表的界面,那么V层的接口是否要定义如下方法:showLoading();hideLoading();showList();showErrorView();hideErrorView();showEmptyView();hideEmptyView();.....然后所有的控制细节都交给Presenter去控制。还是应该只在V层里定义showError()和showList()方法,在View的实现类里去进行控件的显示和隐藏操作更好呢?
      • MarvinDuan:非常棒!很具有参考意义!
      • 4b5a49c9c63c: @Override
        public void onDrawerIconClicked() {
        //已经登录,跳到个人详情页
        ToastUtils.getInstance().showToast("icon clicked");
        //没有登录 ,则跳到登录页面。。。
        }
        在登录这里的网络请求,是直接就写在model里面吗?
      • Melonpi:Service和广播怎么处理的呢?
      • North_2016:最近基于Google的MVP框架做了泛型解耦的封装,代码精简了大半,感兴趣的可以看一下 https://github.com/north2014/T-MVP
        程序员Anthony:@North_2016 写的很不错。nice
      • PossibleZero:代码看了下,平心而论,贯穿全文的应该是mvp架构方面的以及设计方面的,不过穿插的一些三方的包太多了,给人的感觉有点....感觉可以把核心部分提取出来,有些部分用些假数据或者一些接口就行了,能烘托出主要的整体脉络就好,不然有点乱,感觉这也讲了,那也讲了,给一些初学者增加了很多额外的成本,不过楼主真心的很赞,良心之作,非常的棒;近段时间也在做个新项目,运用mvp+rx+retrofit等一些技术,有时间可以交流学习下。
        程序员Anthony:@天天贼拽 谢谢你的回复。主要是写这个系列文章,而且不断的总结提炼一个更好的表达方式。代码方面,确实有点逻辑不清楚。项目会不断的提交更新,很多代码也会不断的提炼。确实有很多问题需要解决。 :smile:
      • 陈明浩:我感觉这种一个activity带多个fragment更好,为什么googlesample每一个页面都用一个单独的activity,这样做有什么好处
        程序员Anthony:@陈明浩 个人只是觉得这只是分模块展示,减少模块耦合。方便大家阅读,至于优点,还真没发现
        陈明浩:@CameloeAnthony我是想说这种方式相比知乎那种一个activity对多个fragment有什么优点吗?毕竟activity的开销更大
        程序员Anthony:@陈明浩 google例子中,没有把activity当做MVP中的View,而是进行初始化fragment(fragment也就是MVP中的view),以及初始化Presenter。 起到一个管理类的作用。 当然这只是一个例子。我这也是参考了那个例子,实际上activity和fragment都可以作为View。google的例子有很好的参考意义。这里就是做了个延伸 。
      • 娃娃要从孩子抓起:实战这部分内容,先给出需求和最后的实现效果再贴代码讲解会不会更好一点?
        洛埋名:@CameloeAnthony 确实,自己懂其实不难,要写出来的东西让大家懂,这个比较难。加油~
        程序员Anthony:@娃娃要从孩子抓起 好的 。谢谢你。很不错的建议 。关于怎样写一篇通俗易懂的博客,正在摸索过程中。
      • Bigmercu:很好,学习了,和我之前理解的MVP有的部分不太一样,这种实现更合理。

      本文标题:Google官方架构MVP解析与实战【从零开始搭建android

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