在上一篇我已经介绍了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的具体实现类
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
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。
到此为止,MVP架构已经完成了,是不是很简单。这个是最基础的MVP架构,后面我也会结合Clean思想,进行重新架构。敬请期待哦~
3、总结
在这个架构设计中,也存在在一些不足,比如:1、在构造presenter的时候,传进去的Context有可能会出现内存泄漏,我现在解决的方法是传getApplicationContext()进去,它是和app一样的生命周期。2、在model的实现类中的DataRequestListener回调监听,当有多个请求的时候,我这边需要创建多个DataRequestListener,感觉不是很好,希望弄有好的解决方法和我一起探讨。
相关推荐投稿作者:谈恋爱的小猴子链接:https://www.jianshu.com/p/578893cde5e1