美文网首页Android架构
Android架构之路--三步实现MVP架构(基础篇-下)

Android架构之路--三步实现MVP架构(基础篇-下)

作者: Dora_Liang | 来源:发表于2018-01-01 01:10 被阅读526次

    已授权开发者技术前线原创发布。
    在写文章前,先祝大家,元旦快乐,在新的一年2018年,还没找到另一半的兄弟们,都能找到对象。我们是一群面向对象编程的猿猿,没有对象怎么行。哈哈,扯远了,回到正题。

    在上一篇我已经介绍了什么是MVP,还有对Google官方的示例进行讲解,还有不懂得,可以看Android架构之路--三步实现MVP架构(基础篇-上)。现在我将带领大家一起架构MVP。

    1.MVP项目分包

    对于项目分包,有的人喜欢根据功能分包,也有人喜欢根据组件或者模块进行分包。分包原则:使得项目结构清晰,功能明确,便于查询与后期维护。具体看个人爱好,没有严格规定,大家先看一张我分包后的项目结构:


    1-1 项目结构

    项目结构:

    1、base:存放一些我们封装的基类。
    2、contract:存放契约类。
    3、database:存放一些手机数据sqlite数据。
    4、event:存放一些消息事件,我使用的是开源eventBus3.0。
    5、http:就是你封装的网络框架。
    6、listener:存放一些自己定义的监听。
    7、model:里面又分两个包,bean(存放实体类)、impl(存放model实现类)。
    8、presenter:就是MVP中的P,俗称媒介。
    9、ui:MVP中的V,我把adapter(适配器)和widget(自定义View)也放在这个包。
    10、utils:存放一些开发中的工具类。
    这样看起来是不是项目结构清晰许多了,那现在我们就挽起袖子,就是撸代码。

    2、撸代码

    在进行撸代码前,我们先进行对一些公用的方法进行抽离,封装代码。

    2.1、base类抽离

    /**
     * @author Ljh on 2018/01/01
     */
    
    public interface BaseView {
    
        /**
         * 显示加载弹窗
         */
        void showLoading();
    
        /**
         * 隐藏加载弹窗
         */
        void hideLoading();
    
        /**
         * 显示错误
         */
        void showError();
    
        /**
         * 显示空布局
         */
        void showEmptyView();
    
    }
    

    我们先对view进行抽离封装,可以看见它具备view的一些基本功能,以后继承它就可以。

    /**
     * @author Ljh on 2018/01/01
     */
    
    public interface BaseModel {
    }
    

    暂时BaseModel是空,关键的presenter来了,上一篇我们遗留的一个问题,现在在这里就要把它解决掉。

    /**
     * @author Ljh on 2018/01/01
     */
    
    public abstract class BasePresenter<T> {
    
        /**
         * View接口类型的弱引用
         */
        private Reference<T> mViewRef;
    
    
        /**
         * 绑定视图
         * @param mView 视图
         */
        public void attachView(T mView) {
    
            mViewRef = new WeakReference<>(mView);
    
        }
    
        protected T getView() {
            if(isViewAttached()){
                return mViewRef.get();
            }else {
                return null;
            }
    
        }
    
        private boolean isViewAttached() {
            return mViewRef != null && mViewRef.get() != null;
        }
    
        /**
         * 解绑视图
         */
        public void detachView() {
    
            if (mViewRef != null) {
                mViewRef.clear();
                mViewRef = null;
            }
        }
    
    }
    

    我们在这里使用弱引用,对处理内存泄漏起到很好作用,我又封装了一个detachView的方法,当视图消失的时候,调用这个方法,可以进行解绑。

    /**
     * @author Ljh on 2018/01/01
     */
    
    public abstract class BaseActivity<V, T extends BasePresenter<V>> extends SupportActivity {
    
        protected T mPresenter;
    
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(setLayoutId());
    
            mPresenter = createPresenter();
    
            if (mPresenter != null) {
                mPresenter.attachView((V) this);
            }
    
            initView();
    
            initData();
    
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
    
            if (mPresenter != null) {
                mPresenter.detachView();
            }
    
        }
    
        /**
         * 设置资源ID
         *
         * @return layoutID
         */
        protected abstract int setLayoutId();
    
        /**
         * 初始化控件
         */
        protected abstract void initView();
    
        /**
         * 初始化数据
         */
        protected abstract void initData();
    
        /**
         * 创建Presenter
         *
         * @return Presenter
         */
        protected abstract T createPresenter();
    }
    
    /**
     * @author Ljh on 2018/01/01
     */
    
    public abstract class BaseFragment<V, T extends BasePresenter<V>> extends SupportFragment {
    
        protected T mPresenter;
    
        protected View mRootView;
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mPresenter = createPresenter();
            if (mPresenter != null) {
                mPresenter.attachView((V) this);
            }
        }
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
            mRootView = inflater.inflate(setLayoutId(), container, false);
    
    
            initView();
    
            initData();
    
            return mRootView;
        }
    
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
    
            //关闭软键盘
            hideSoftInput();
            if (mPresenter != null) {
                mPresenter.detachView();
            }
    
        }
    
        /**
         * 设置资源ID
         *
         * @return layoutID
         */
        protected abstract int setLayoutId();
    
        /**
         * 初始化控件
         */
        protected abstract void initView();
    
    
        /**
         * 初始化数据
         */
        protected abstract void initData();
    
    
        /**
         * 注册EventBus
         */
        protected void register() {
            EventBus.getDefault().register(this);
        }
    
    
        /**
         * 注销EventBus
         */
        protected void unRegister() {
            EventBus.getDefault().unregister(this);
        }
    
        /**
         * 创建Presenter
         *
         * @return Presenter
         */
        protected abstract T createPresenter();
    
    }
    

    上面两个基类分别对Activity和Fragment进行封装,上面封装的BasePresenter进行绑定和解绑,我们可以直接放在这边进行,就不用每个Activity(或Fragment)都进行绑定与解绑。在这里推荐一个大佬开源的框架Fragmentation,挺不错,大家可以去学习一下。

    2.2、Contract契约类实现

    public interface MainContract {
    
        interface IMainView extends BaseView {
    
            /**
             * 显示Banner
             *
             * @param list  Banner集合
             * @param count 个数
             */
            void showBanner(List<Banner> list, int count);
    
            /**
             * 显示列表数据
             * @param list 列表数据集合
             */
            void showData(List<String> list);
        }
    
    
        abstract class AbstractHomePresenter extends BasePresenter<IMainView> {
    
            /**
             * 请求Banner
             */
            public abstract void requestBanner();
    
            /**
             * 请求列表信息
             */
            public abstract void requestData();
        }
    }
    

    2.3、Model的实现

    /**
     * @author Ljh on 2018/01/01
     */
    
    public interface IMainModel {
    
        /**
         * 请求Banner
         *
         * @param mContext 上下文对象
         * @param listener 请求回调监听
         */
        void loadBanner(Context mContext, DataRequestListener<List<Banner>> listener);
    
        /**
         * 请求数据
         * @param mContext 上下文对象
         * @param listener 请求回调监听
         */
        void loadMessage(Context mContext, DataRequestListener<List<String>> listener);
    
    }
    

    我们定义了Model的接口方法,接下来我们来实现Model的具体实现类

    /**
     * @author Ljh on 2018/01/01
     */
    
    public class MainModelImpl implements IMainModel {
    
        private List<Banner> mBannerList;
        private List<String> mDataList;
    
        private DataRequestListener<List<Banner>> mListener1;
        private DataRequestListener<List<String>> mListener2;
    
        @Override
        public void loadBanner(Context mContext, DataRequestListener<List<Banner>> mListener) {
    
            this.mListener1 = mListener;
    
            if (mBannerList == null) {
                mBannerList = new ArrayList<>();
            }
    
            //添加模拟数据
            Banner banner;
            for (int i = 0; i < 6; i++) {
    
                banner = new Banner();
                banner.setImgUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1514749322812&di=97929afb9de2f93bce92cbe953686edd&imgtype=0&src=http%3A%2F%2Ftupian.enterdesk.com%2Fuploadfile%2F2015%2F0423%2F20150423103831704.jpg");
                mBannerList.add(banner);
            }
            
            //回调给presenter
            mListener1.onSuccess(mBannerList);
    
    
        }
    
    
        @Override
        public void loadData(Context mContext, DataRequestListener<List<String>> mListener) {
    
            mListener2 = mListener;
    
            if (mDataList == null) {
                mDataList = new ArrayList<>();
            }
    
            //这边使用模拟数据,后面大家改成网络请求的数据
            mDataList.add("数据1");
            mDataList.add("数据2");
            mDataList.add("数据3");
            mDataList.add("数据4");
            mDataList.add("数据5");
            mDataList.add("数据6");
    
            //回调给presenter
            mListener2.onSuccess(mDataList);
    
        }
    
    }
    

    这边有一个实体类Banner

    /**
     * @author by Ljh on 2018/01/01.
     *         广告banner信息
     */
    
    public class Banner {
    
        /**
         *广告图片地址
         */
        private String imgUrl;
        /**
         * banner标题
         */
        private String title;
    
        public Banner() {
        }
    
        public Banner(String imgUrl, String title) {
            this.imgUrl = imgUrl;
            this.title = title;
        }
    
        public String getImgUrl() {
            return imgUrl;
        }
    
        public void setImgUrl(String imgUrl) {
            this.imgUrl = imgUrl;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    }
    

    2.4、Presenter实现

    
    /**
     * @author Ljh on 2018/01/01
     */
    
    public class MainPresenter extends MainContract.AbstractMianPresenter {
    
    
        private Context mContext;
    
        private IMainModel mModel = new MainModelImpl();
    
        public MainPresenter(Context mContext) {
    
            this.mContext = mContext;
        }
    
    
        @Override
        public void requestBanner() {
    
            final MainContract.IMainView mView = getView();
    
            if (mView == null) {
                return;
            }
    
            if (mModel != null) {
    
                mModel.loadBanner(mContext, new DataRequestListener<List<Banner>>() {
                    @Override
                    public void onSuccess(List<Banner> data) {
    
                        //是否有数据
                        if (data.size() > 0) {
    
                            mView.showBanner(data, data.size());
                        } else {
    
                            mView.showEmptyView();
                        }
                    }
    
                    @Override
                    public void onFail() {
    
                    }
                });
    
            }
        }
    
    
        @Override
        public void requestData() {
            final MainContract.IMainView mView = getView();
    
            if (mView == null) {
                return;
            }
    
            if (mModel != null) {
    
                mModel.loadData(mContext, new DataRequestListener<List<String>>() {
                    @Override
                    public void onSuccess(List<String> data) {
    
                        //是否有数据
                        if (data.size() > 0) {
    
                            mView.showData(data);
                        } else {
    
                            mView.showEmptyView();
                        }
    
                    }
    
                    @Override
                    public void onFail() {
    
                    }
                });
            }
    
        }
    
    }
    

    2.5、View实现

    先看布局,就两个控件,一个是第三方轮播控件BGABanner和RecyclerView,布局如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#F4F4F4">
    
        <cn.bingoogolapple.bgabanner.BGABanner
            android:id="@+id/banner_Alpha"
            style="@style/BannerDefaultStyle"
            app:banner_indicatorGravity="bottom|center_horizontal"
            app:banner_placeholderDrawable="@drawable/img_empty"
            app:banner_transitionEffect="alpha"/>
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_Main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        
    </LinearLayout>
    

    在看具体activity是怎么调用presenter:

    /**
     * @author Ljh on 2018/01/01
     */
    
    public class MainActivity extends BaseActivity<MainContract.IMainView, MainPresenter> implements MainContract.IMainView,
            BGABanner.Delegate<ImageView, String>, BGABanner.Adapter<ImageView, String> {
    
    
        private BGABanner mBanner;
        private RecyclerView mRecyclerView;
    
        private RequestOptions mOptions;
    
        private MainAdapter mAdapter;
    
        private List<String> mList;
    
    
        @Override
        protected int setLayoutId() {
            return R.layout.activity_main;
        }
    
        @Override
        protected void initView() {
    
            mBanner = (BGABanner) findViewById(R.id.banner_Alpha);
            mRecyclerView = (RecyclerView) findViewById(R.id.rv_Main);
    
    
            mOptions = new RequestOptions();
            mOptions.centerCrop()
                    .error(R.drawable.img_empty)
                    .placeholder(R.drawable.img_empty)
                    .priority(Priority.HIGH)
                    .dontAnimate()
                    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);
    
            mList = new ArrayList<>();
    
            //设置RecycleView样式
            mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
            mRecyclerView.setHasFixedSize(true);
    
            mAdapter = new MainAdapter(mList);
            mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM);
            mRecyclerView.setAdapter(mAdapter);
    
            mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
                @Override
                public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                    Toast.makeText(getApplicationContext(),"点击了位置"+position,Toast.LENGTH_SHORT).show();
    
                }
            });
    
        }
    
        @Override
        protected void initData() {
            //请求banner
            mPresenter.requestBanner();
            //请求列表数据
            mPresenter.requestData();
        }
    
        @Override
        public void showBanner(List<Banner> list, int count) {
    
            mBanner.setAutoPlayAble(count > 1);
            mBanner.setAdapter(this);
    
            List<String> imgUrls = new ArrayList<>();
    
            for (int i = 0; i < list.size(); i++) {
                imgUrls.add(list.get(i).getImgUrl());
            }
            mBanner.setData(imgUrls, null);
    
        }
    
        @Override
        public void showData(List<String> data) {
    
            this.mList = data;
    
            mAdapter.setNewData(mList);
            mAdapter.notifyDataSetChanged();
    
        }
    
    
        @Override
        public void showLoading() {
    
        }
    
        @Override
        public void hideLoading() {
    
        }
    
        @Override
        public void showError() {
    
        }
    
        @Override
        public void showEmptyView() {
    
        }
    
    
        @Override
        protected MainPresenter createPresenter() {
            return new MainPresenter(getApplicationContext());
        }
    
        @Override
        public void fillBannerItem(BGABanner banner, ImageView itemView, String model, int position) {
    
            Glide.with(itemView.getContext())
                    .load(model)
                    .apply(mOptions)
                    .into(itemView);
    
        }
    
        @Override
        public void onBannerItemClick(BGABanner bgaBanner, ImageView imageView, String s, int position) {
            Toast.makeText(getApplicationContext(),"点击了"+position,Toast.LENGTH_SHORT).show();
        }
    }
    

    图片加载我使用的glide框架,在适配方面我使用宇明大佬开源的BRVAH,可以减少70%的代码。下面是我的适配器代码:

    /**
     * @author Ljh on 2018/01/01
     */
    
    public class MainAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
    
    
        public MainAdapter(@Nullable List<String> data) {
            super(R.layout.item_main, data);
        }
    
        @Override
        protected void convert(BaseViewHolder helper, String item) {
    
            helper.setText(R.id.tv_item, item);
    
        }
    }
    

    是不是很简洁,布局就一个TextView。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="1dp"
        android:orientation="vertical"
        android:background="@android:color/white">
    
        <TextView
            android:id="@+id/tv_item"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="50dp" />
    
    </LinearLayout>
    

    好了,MVP架构已经完成了,是不是很简单。这个是最基础的MVP架构,后面我也会结合Clean思想,进行重新架构。敬请期待哦~

    3、总结

    在这个架构,也存在在一些不足,比如:
    1、在构造presenter的时候,传进去的Context有可能会出现内存泄漏,我现在解决的方法是传getApplicationContext()进去,它是和app一样的生命周期。
    2、在model的实现类中的DataRequestListener回调监听,当有多个请求的时候,我这边需要创建多个DataRequestListener,感觉不是很好,希望大佬们又好的解决方法。

    元旦放假4天睡4天,明天就又要上班了,心塞。在此,你们最爱的冠希哥,祝大家2018年事业有成,爱情美满。

    相关文章

      网友评论

      • 288cdc434d0d:你好,文章写得特别好,连我都看懂了:smile: ,关于构造presenter的时候,context可能引起的内存泄漏问题,可不可以把context也按弱引用进行使用。
        Dora_Liang:@iRookieLiang 可以的

      本文标题:Android架构之路--三步实现MVP架构(基础篇-下)

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