美文网首页
Google官方Mvp架构详解(基于仿今日头条News项目)

Google官方Mvp架构详解(基于仿今日头条News项目)

作者: Funny灬 | 来源:发表于2018-09-28 12:26 被阅读0次

    先来一发Google官方Mvp架构地址:
    https://github.com/googlesamples/android-architecture/tree/todo-mvp/

    基类介绍

    BaseFragment

    这个类是Fragment的一级父类。主要完成以下功能:

    1. 在类的声明上添加了一个IBasePresenter的泛型,并且实现了IBaseView接口。

    2. 统一封装了EventBus的使用,在基类中注册和反注册EventBus。子类如果需要使用EventBus,则只需要添加相应的注解即可。(关于通过注解的方式在基类中封装EventBus,可以参考https://blog.csdn.net/xieluoxixi/article/details/78262765

    3. 在Fragment挂载在Activity的时候,保存了一个上下文Context对象。

    4. 在onCreate()方法中,调用了setPresenter()方法,来给Fragment设置它对应的Presenter,并且先于onCreateView()方法执行。(因为可能会在initData()、initView()、initEvent()方法中用到Presenter对象,避免空指针异常)

    package com.example.think.base;
    
    import android.content.Context;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    import com.example.think.utils.BindEventBus;
    
    import org.greenrobot.eventbus.EventBus;
    
    import butterknife.ButterKnife;
    
    /**
     * Author: Funny
     * Description: This is 一级Fragment
     */
    public abstract class BaseFragment<P extends IBasePresenter> extends Fragment implements IBaseView<P> {
    
        protected P mPresenter;
    
        protected Context mContext;
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            this.mContext = context;
        }
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //设置Presenter对象
            setPresenter(mPresenter);
        }
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = LayoutInflater.from(getContext()).inflate(getLayoutId(), null);
            ButterKnife.bind(this, view);
    
            registEventBus();
            initData();
            initView(view);
            initEvent();
            return view;
        }
    
        private void registEventBus() {
            if (this.getClass().isAnnotationPresent(BindEventBus.class)) {
                EventBus.getDefault().register(this);
            }
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            if (this.getClass().isAnnotationPresent(BindEventBus.class)) {
                EventBus.getDefault().unregister(this);
            }
        }
    
        protected abstract int getLayoutId();
    
        protected void initData() {
        }
    
        protected void initView(View view) {
        }
    
        protected void initEvent() {
        }
    
    }
    
    

    IBasePresenter和IBaseView

    接下来再来看看IBasePresenter和IBaseView里面的具体实现。这两个接口是MVP模式中Presenter和View的一级接口。其他的Presenter和View必须直接或者间接实现它。

    这里有一个方法命名的小技巧,Presenter里面的方法全是do开头,而View里面的方法是on开头(想想也挺符合逻辑的,Presenter是干事的人,使劲的do,而View是被干的人,所以要on.......)。这样通过名字就可以知道是谁的方法,因为后面Activity或者Fragment里面的overrider方法太多,容易搞懵。

    • IBasePresenter

    IBasePresenter里面有两个抽象方法,用于刷新数据和显示网络错误。这个功能方法和具体的业务逻辑有关,因为所有的Presenter都有这两个功能,其他更具体的功能,后面在它的子类中会说到。

    package com.example.think.base;
    
    /**
     * Author: Funny
     * Description: This is 一级Presenter接口
     */
    public interface IBasePresenter {
    
        /**
         * 刷新数据
         */
        void doRefresh();
    
        /**
         * 显示网络错误
         */
        void doShowNetError();
    
    }
    
    
    • IBaseView

    IBaseView里面的方法和具体的业务逻辑有关,注释已经写得很清楚了

    package com.example.think.base;
    
    /**
     * Author: Funny
     * Description: This is 一级View接口
     */
    public interface IBaseView<P> {
        /**
         * 显示加载动画
         */
        void onShowLoading();
    
        /**
         * 隐藏加载
         */
        void onHideLoading();
    
        /**
         * 显示网络错误
         */
        void onShowNetError();
    
        /**
         * 设置 presenter
         */
        void setPresenter(P presenter);
    
        /**
         * 绑定生命周期
         */
        //<X> AutoDisposeConverter<X> bindAutoDispose();
    }
    
    

    BaseListFragment

    BaseListFragment是BaseFragment的子类。所有列表页Fragment都继承它。

    BaseListFragment所继承的LazyLoadFragment是处理懒加载的,LazyLoadFragment同样继承自BaseFragment。只需要注意一个地方,在它的生命周期方法onActivityCreated()中,执行了一个抽象方法fetchData(),这个抽象方法就是界面加载数据的开始。

    package com.example.think.base;
    
    import android.support.v4.widget.SwipeRefreshLayout;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.view.View;
    import android.widget.Toast;
    
    import com.example.think.R;
    
    import me.drakeet.multitype.Items;
    import me.drakeet.multitype.MultiTypeAdapter;
    
    /**
     * Author: Funny
     * Description: This is BaseListFragment
     */
    public abstract class BaseListFragment<P extends IBasePresenter> extends LazyLoadFragment<P> implements IBaseListView<P>,SwipeRefreshLayout.OnRefreshListener{
    
        protected RecyclerView mRecyclerView;
        protected SwipeRefreshLayout mRefreshLayout;
        protected MultiTypeAdapter mAdapter;
    
        protected boolean canLoadMore = false;
    
        @Override
        protected int getLayoutId() {
            return R.layout.fragment_list;
        }
    
        @Override
        protected void initView(View view) {
            mRecyclerView = view.findViewById(R.id.recycler_view);
            mRefreshLayout = view.findViewById(R.id.refresh_layout);
            mRecyclerView.setHasFixedSize(true);
            LinearLayoutManager manager = new LinearLayoutManager(getContext());
            mRecyclerView.setLayoutManager(manager);
            //下拉刷新
            mRefreshLayout.setOnRefreshListener(this);
        }
    
    
        @Override
        public void onShowLoading() {
            /**
             * 列表Fragment,显示加载视图,设置mRefreshLayout的刷新状态为true
             */
            mRefreshLayout.post(() -> {
                mRefreshLayout.setRefreshing(true);
            });
        }
    
        @Override
        public void onHideLoading() {
            /**
             * 列表Fragment,隐藏加载视图,设置mRefreshLayout刷新状态为false
             */
            mRefreshLayout.post(() -> {
                mRefreshLayout.setRefreshing(false);
            });
        }
    
        @Override
        public void onShowNetError() {
            /**
             * 列表Fragment,加载时显示网络错误
             */
            Toast.makeText(getContext(), "网络不给力", Toast.LENGTH_SHORT).show();
            mAdapter.setItems(new Items());
            mAdapter.notifyDataSetChanged();
            canLoadMore = false;
        }
    
        @Override
        public void onShowNoMore() {
            /**
             * 列表Fragment,加载完毕,无更多数据
             */
            // TODO: 2018/9/6 无更多数据实现
            canLoadMore = false;
        }
    
        @Override
        public void onRefresh() {
            LinearLayoutManager manager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
            int firstVisibleItemPosition = manager.findFirstVisibleItemPosition();
            if (firstVisibleItemPosition == 0) {
                mPresenter.doRefresh();
                return;
            }
            mRecyclerView.scrollToPosition(5);
            mRecyclerView.smoothScrollToPosition(0);
        }
    }
    
    

    这里主要关注一下它所实现的IBaseListView接口,MVP模式有一个特点,一个View所需要完成的功能方法,往往都定义在它实现的View接口中。换句话说,就是IBaseListView接口规定了BaseListFragment需要实现哪些主要的功能(爸爸要你干啥,你就得乖乖干啥)。

    注释已经写得很清楚了,前四个方法在BaseListFragment中处理,因为所有的加载动作都是一样的。后两个方法交给BaseListFragment的子类去实现,因为每个子类设置的presenter和adapter是不同的。

    package com.example.think.base;
    
    import java.util.List;
    
    /**
     * Author: Funny
     * Time: 2018/8/27
     * Description: This is IBaseListView,列表Fragment中该有的行为
     */
    public interface IBaseListView<T> extends IBaseView<T> {
    
        ///////////////////////////////////////////////////////////////////////////
        // 这四个方法在BaseListFragment中处理,因为所有的加载动作都是一样的
        ///////////////////////////////////////////////////////////////////////////
        /**
         * 显示加载动画
         */
        void onShowLoading();
    
        /**
         * 隐藏加载动画
         */
        void onHideLoading();
    
        /**
         * 显示网络错误
         */
        void onShowNetError();
    
        /**
         * 加载完毕
         */
        void onShowNoMore();
    
    
        ///////////////////////////////////////////////////////////////////////////
        // 这两个方法交给BaseListFragment的子类去实现,因为每个子类设置的presenter和adapter是不同的
        ///////////////////////////////////////////////////////////////////////////
        /**
         * 设置 presenter
         */
        void setPresenter(T presenter);
    
        /**
         * 设置适配器
         */
        void onSetAdapter(List<?> list);
    
    }
    
    

    MVP模式最主要的几个基类就是这些了。基类的好坏与否,直接决定项目结构的优劣。但是抽取基类往往是最难的部分,因为一开始你并不会对之后具体的业务逻辑那么了如指掌,所以你不能清楚的知道在基类的接口中写什么功能方法,哪些方法可以统一处理,哪些方法需要交给具体的子类单独处理......

    我们能做的是,对照UI图,多看看接口文档,脑子里有一个成型的界面和界面的功能逻辑。尽量在写代码前,理清楚整个功能的思路,这非常重要。

    或者有时候,当你写了一部分代码时,回过头来看,突然发现某一块代码可以抽取和优化,某一个功能可以抽取一个公共的接口方法放到基类里......

    具体功能和界面

    基类已经简单介绍过了,接着该实现具体的功能页面。

    News News

    MD讲了这么多繁琐的基类,写了那么多代码,在手机上跑起来还是一片空白,真TM不爽。终于可以开始撸界面和功能了,不管那么多了,先撸一个RecyclerView列表把场面撑起来再说......

    等等,在开始写功能代码前,我们还是需要再理一理这个界面和功能的具体实现思路,抽象成一个接口,把功能方法写在里面。先有优秀的爸爸,才能有更优秀的儿子。

    IVideoContract契约类

    这里使用的Google官方MVP的结构思想,先创建一个契约类IVideoContract,这个就是MVP的接口类,感觉就像是Model、View、Presenter三者签订某种契约,规定各自需要做的事情。里面写的都是抽象的功能方法,需要子类去具体实现。

    契约类规定说:

    Model长得太苦逼,性格又内向,网络请求和数据处理的重任就交给你了。

    Presenter巧舌如簧,为人处世机灵圆滑,负责沟通和交互的工作。

    View天生丽质,颜值高,你就是我们的形象代言人了。

    package com.example.think.ui.video;
    
    import com.example.think.base.IBaseListView;
    import com.example.think.base.IBasePresenter;
    import com.example.think.bean.news.MultiNewsArticleDataBean;
    import com.example.think.net.NetCallBack;
    
    import java.util.List;
    
    /**
     * Author: Funny
     * Description: This is MVP契约类
     */
    public interface IVideoContract {
    
        interface View extends IBaseListView<Presenter> {
    
            /**
             * 请求数据
             */
            void onLoadData();
    
        }
    
        interface Presenter extends IBasePresenter {
    
            /**
             * 请求数据
             */
            void doLoadData(String categoryId);
    
            /**
             * 设置适配器
             */
            void doSetAdapter(List<MultiNewsArticleDataBean> datas);
    
            /**
             * 加载更多
             */
            void doLoadMoreData();
    
            /**
             * 加载完成
             */
            void doShowNoMore();
    
        }
    
        interface Model {
    
            /**
             * 网络请求
             */
            void loadNetData(String category, String time, NetCallBack<MultiNewsArticleDataBean> netCallBack);
    
        }
    
    }
    
    

    VideoArticleModel

    Model的功能比较单一而繁重,负责做网络请求和数据处理。Model说谁叫自己长得丑,这种脏活累活体力活只能自己去做了。甚至于Model都要开启子线程来工作,不能挡了主线程的路(突然想到身为程序猿的自己不正是扮演着这么苦逼的角色么......)。

    package com.example.think.ui.video;
    
    import android.annotation.SuppressLint;
    
    import com.example.think.bean.news.MultiNewsArticleBean;
    import com.example.think.bean.news.MultiNewsArticleDataBean;
    import com.example.think.net.IMobileVideoApi;
    import com.example.think.net.NetCallBack;
    import com.example.think.net.RetrofitFactory;
    import com.google.gson.Gson;
    import com.trello.rxlifecycle2.LifecycleProvider;
    import com.trello.rxlifecycle2.android.ActivityEvent;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import io.reactivex.Observable;
    import io.reactivex.android.schedulers.AndroidSchedulers;
    import io.reactivex.functions.Consumer;
    import io.reactivex.functions.Function;
    import io.reactivex.schedulers.Schedulers;
    
    /**
     * Author: Funny
     * Description: This is VideoArticleModel网络请求
     */
    public class VideoArticleModel implements IVideoContract.Model {
    
        @SuppressLint("CheckResult")
        @Override
        public void loadNetData(LifecycleProvider<ActivityEvent> provider, String category, String time, NetCallBack<MultiNewsArticleDataBean> netCallBack) {
            Observable<MultiNewsArticleBean> observable = RetrofitFactory.getInstance().create(IMobileVideoApi.class).getVideoArticle(category, time);
            observable.subscribeOn(Schedulers.io())
                    .map(new Function<MultiNewsArticleBean, List<MultiNewsArticleDataBean>>() {
                        @Override
                        public List<MultiNewsArticleDataBean> apply(MultiNewsArticleBean multiNewsArticleBean) throws Exception {
                            List<MultiNewsArticleDataBean> dataList = new ArrayList<>();
                            List<MultiNewsArticleBean.DataBean> data = multiNewsArticleBean.getData();
                            for (MultiNewsArticleBean.DataBean datum : data) {
                                String content = datum.getContent();
                                dataList.add(new Gson().fromJson(content, MultiNewsArticleDataBean.class));
                            }
                            return dataList;
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .compose(provider.bindToLifecycle())
                    .subscribe(new Consumer<List<MultiNewsArticleDataBean>>() {
                        @Override
                        public void accept(List<MultiNewsArticleDataBean> multiNewsArticleDataBeans) throws Exception {
                            netCallBack.success(multiNewsArticleDataBeans);
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                            netCallBack.fail(throwable);
                        }
                    });
    
    
        }
    }
    
    

    VideoArticleFragment

    VideoArticleFragment对应MVP的View层,重点来关注一下重写的方法,来理一下它的执行顺序。顺序一定要对,不然可能会报空指针异常或者崩溃。

    1. onSetPresenter()方法是最先执行的,在BaseFragment中的onCreate()方法中执行。

    2. initData()、initView()、包括initEvent()方法是在onCreateView()方法中执行。

    3. fetchData()在onActivityCreated()方法中执行。

    这几个方法是在生命周期方法里执行的,不需要我们手动调用。而在契约类IVideoContract中的onLoadData()方法,是需要手动调用,才会执行,所以需要搞清楚。(前面提到的,我们通过方法名前面一个on,就可以知道他是契约类IVideoContract中View层的方法。)

    package com.example.think.ui.video;
    
    import android.os.Bundle;
    import android.view.View;
    
    import com.example.think.base.BaseListFragment;
    import com.example.think.bean.news.MultiNewsArticleDataBean;
    import com.example.think.viewHolder.news.NewsArticleVideoViewBinder;
    
    import java.util.List;
    
    import me.drakeet.multitype.Items;
    import me.drakeet.multitype.MultiTypeAdapter;
    
    /**
     * Author: Funny
     * Description: This is VideoArticleFragment
     */
    public class VideoArticleFragment extends BaseListFragment<IVideoContract.Presenter> implements IVideoContract.View {
    
        private String mCategoryId;
        private Items mDatas = new Items();
        private MultiTypeAdapter mAdapter;
    
    
        public static VideoArticleFragment newInstance(String categoryId) {
    
            Bundle args = new Bundle();
            args.putString("categoryId", categoryId);
            VideoArticleFragment fragment = new VideoArticleFragment();
            fragment.setArguments(args);
            return fragment;
        }
    
        /**
         * 在BaseFragment中的onCreate方法中执行
         *
         * @param presenter
         */
        @Override
        public void onSetPresenter(IVideoContract.Presenter presenter) {
            if (mPresenter == null) {
                mPresenter = new VideoArticlePresenter(this);
            }
        }
    
        /**
         * 在onCreateView方法中执行
         */
        @Override
        protected void initData() {
            mCategoryId = getArguments().getString("categoryId");
        }
    
        @Override
        protected void initView(View view) {
            super.initView(view);
            mAdapter = new MultiTypeAdapter(mDatas);
            mAdapter.register(MultiNewsArticleDataBean.class, new NewsArticleVideoViewBinder());
            mRecyclerView.setAdapter(mAdapter);
        }
    
        /**
         * 在onActivityCreated方法中执行
         */
        @Override
        public void fetchData() {
            onLoadData();
        }
    
    
        @Override
        public void onLoadData() {
            onShowLoading();
            mPresenter.doLoadData(mCategoryId);
        }
    
    
        @Override
        public void onSetAdapter(List<?> list) {
            mDatas.clear();
            mDatas.addAll(list);
            mAdapter.notifyDataSetChanged();
            canLoadMore = true;
            mRecyclerView.stopScroll();
        }
    
    }
    
    

    VideoArticlePresenter

    VideoArticlePresenter是负责Model和View的交互。在它的构造方法中,持有一个View的引用,同时还新创建了一个Model对象。有了这两个对象,让View(颜值担当)和Model(程序猿)发生一些关系,不是轻而易举的事情吗。

    感觉这个交互很丝滑。

    package com.example.think.ui.video;
    
    import android.annotation.SuppressLint;
    import android.text.TextUtils;
    
    import com.example.think.bean.news.MultiNewsArticleDataBean;
    import com.example.think.net.NetCallBack;
    import com.example.think.utils.TimeUtil;
    import com.trello.rxlifecycle2.LifecycleProvider;
    import com.trello.rxlifecycle2.android.ActivityEvent;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import io.reactivex.Observable;
    import io.reactivex.functions.Consumer;
    import io.reactivex.functions.Predicate;
    
    /**
     * Author: Funny
     * Description: This is VideoArticlePresenter
     */
    public class VideoArticlePresenter implements IVideoContract.Presenter {
    
        private VideoArticleModel mModel;
        private VideoArticleFragment mView;
        private String mTime;
        private List<MultiNewsArticleDataBean> mDatas;
        private String mCategory;
        private final LifecycleProvider<ActivityEvent> mProvider;
    
        public VideoArticlePresenter(VideoArticleFragment view) {
            mView = view;
            mModel = new VideoArticleModel();
            mTime = TimeUtil.getCurrentTimeStamp();
            mDatas = new ArrayList<>();
            mProvider = mView.autoRxLifeCycle();
        }
    
        @Override
        public void doLoadData(String categoryId) {
            mCategory = categoryId;
            mModel.loadNetData(mProvider,categoryId, mTime, new NetCallBack<MultiNewsArticleDataBean>() {
                @SuppressLint("CheckResult")
                @Override
                public void success(List<MultiNewsArticleDataBean> datas) {
                    Observable.fromIterable(datas)
                            .filter(new Predicate<MultiNewsArticleDataBean>() {
                                @Override
                                public boolean test(MultiNewsArticleDataBean multiNewsArticleDataBean) throws Exception {
                                    //这个时间参数用于加载更多
                                    mTime = multiNewsArticleDataBean.getBehot_time();
                                    String source = multiNewsArticleDataBean.getSource();
                                    if (TextUtils.isEmpty(source)) {
                                        return false;
                                    }
    
                                    if (source.contains("头条") || source.contains("问答") || multiNewsArticleDataBean.getTag().contains("ad")) {
                                        return false;
                                    }
    
                                    //去除标题重复的新闻
                                    for (MultiNewsArticleDataBean data : mDatas) {
                                        if (data.getTitle().equals(multiNewsArticleDataBean.getTitle())) {
                                            return false;
                                        }
                                    }
    
                                    return true;
                                }
                            })
                            .toList()
                            .compose(mProvider.bindToLifecycle())
                            .subscribe(new Consumer<List<MultiNewsArticleDataBean>>() {
                                @Override
                                public void accept(List<MultiNewsArticleDataBean> dataBeans) throws Exception {
                                    if (dataBeans != null && dataBeans.size() > 0) {
                                        doSetAdapter(dataBeans);
                                    } else {
                                        doShowNoMore();
                                    }
                                }
                            });
                }
    
                @Override
                public void fail(Throwable throwable) {
                    doShowNetError();
                }
            });
        }
    
        @Override
        public void doSetAdapter(List<MultiNewsArticleDataBean> datas) {
            mDatas.addAll(datas);
            mView.onHideLoading();
            mView.onSetAdapter(mDatas);
        }
    
        @Override
        public void doLoadMoreData() {
            //加载更多数据和category无关,只和当前时间有关,加载更多时,时间参数要用数据里的behot_time
            doLoadData(mCategory);
        }
    
        @Override
        public void doShowNoMore() {
            mView.onHideLoading();
            mView.onShowNoMore();
        }
    
        @Override
        public void doRefresh() {
            mDatas.clear();
            mView.onShowLoading();
            mTime = TimeUtil.getCurrentTimeStamp();
            doLoadData(mCategory);
        }
    
        @Override
        public void doShowNetError() {
            mView.onHideLoading();
            mView.onShowNetError();
        }
    }
    
    

    最后附上github地址,上面的代码都在里面。

    https://github.com/FunnyLee/News

    这个项目采用谷歌官方MVP架构实现,使用Android主流技术(Retrofit、RxJava、Material Design等等),并且我会持续更新......

    相关文章

      网友评论

          本文标题:Google官方Mvp架构详解(基于仿今日头条News项目)

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