写在前面
最近看到了好多朋友写的关于MVP架构详解,浅谈…对于看文章三分钟热度的我都没有看完…趁着周末有时间,写了个demo,针对MVP进行菜鸟级的解析(目的是简单了解和快速应用),下面聚精会神三分钟,看看你能不能有所收获.
一.MVC和MVP
这部分是必须要了解的,我这里也是使用了网上比较好的总结.
MVC
- MVC的全称为Model-View-Controller,即模型-视图-控制器,提出MVC目的为了分离界面和业务逻辑。
- Model:处理数据和业务逻辑等
- View:显示界面,展示结果等
- Controller:控制流程,处理交互
android应用程序中mvc
- View : layout目录下的xml布局文件,设计应用的ui界面。展现数据
- Controller : Activity作为控制器,处理用户请求
- Model : 一般是一个javaBean对象
举个栗子:MVC模式就像是书店里工作模式.
- Model:可以理解为仓库管理员.他的工作是联系出版社,进货…
- View:书架,所有的数都展示在书架上
- Controller:老板,老板说这次我们需要买一批网络小说,这些书就出现在了书架上.这里面就涉及到M-V-C模式的.
MVP
MVP的全称为Model-View-Presenter,即模型-视图-协调器(主持者)
- Model:处理数据和业务逻辑等
- View:显示界面,展示结果等
- Presenter:协调Model和View模块工作,处理交互
关于优缺点等更深入的了解大家也可以看简书上相关文章,写得很仔细,我这里重点就是使用,所以就不做过多讲解.
- MVP的使用步骤:
1.创建 IPresenter 接口,把所有业务逻辑的接口都放在这里,并创建它的实现 PresenterCompl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)。
2.创建 IView 接口,把所有视图逻辑的接口都放在这里,其实现类是当前的 Activity/Fragment。
3.Activity 里包含了一个 IPresenter,而 PresenterCompl 里又包含了一个 IView 并且依赖了 Model。Activity 里只保留对 IPresenter 的调用,其它工作全部留到 PresenterCompl 中实现。
4.Model 并不是必须有的,但是一定会有 View 和 Presenter
案例分析
针对MVP架构,查阅相关的文章和使用后我写了一个Demo,这个demo是为说明使用而量身设计的,有一定的代表性,源码在github上有.
- 效果图
结构分析
通过结构分析在脑海中有了一个基本框架,接下来就是去实现这个框架.
- 共同点: 每个界面都是RecyclerView,有下拉刷新和上拉加载
- 不同:每个RecyclerView的item内容不同,也就是对应的adapter不同
代码结构
参考我的工程结构
抽取基类
- 创建BaseFragment,作为fragment的基类
public abstract class BaseFragment extends Fragment {
public Context mContext;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContext = getActivity();
View view = initView();
//绑定view
ButterKnife.bind(this, view);
//初始化
init();
return view;
}
public void init(){};
/**
* 实现的子类必须去实现的抽象方法,创建view
* @return
*/
public abstract View initView();
}
- 定义View接口
- 定义抽象方法: 数据加载成功
- View和Presenter是一对一或这一对多的,并且Presenter和View是通过接口交互的
public interface BaseView {
//数据加载成功后的回调
void OnLoadDataSuccess();
}
- 创建HomePresenter,对应 是首页的presenter(指挥官,老板)
public interface HomePresenter {
//初始化请求网络数据
void loadDataList();
//下拉刷新
void refresh();
//加载更多
void loadMoreData();
ArrayList<HomeDataBean> getDataList();
}
- 创建BaseListFragment,这里大家需要特别注意,那些方法是要去子类必须去执行的,整体的业务逻辑应该是什么样的
public abstract class BaseListFragment<T> extends BaseFragment implements BaseView {
@BindView(R.id.base_recycle_view)
RecyclerView mBaseRecycleView;
@BindView(R.id.base_down_refresh)
SwipeRefreshLayout mBaseDownRefresh;
private BaseListPresenter mPresenter;
private RecyclerView.Adapter mAdapter;
@Override
public void init() {
//初始化presenter
mPresenter = getPresenter(this);
initRecycleView();
//加载数据
mPresenter.loadDataList();
//设置RecyclerView的滚动事件
mBaseRecycleView.addOnScrollListener(scrollListener);
//设置下拉刷新进度框的颜色,参数可以设置多个颜色,转一圈换一种颜色
mBaseDownRefresh.setColorSchemeResources(R.color.colorPrimary);
//监听下拉
mBaseDownRefresh.setOnRefreshListener(listener);
}
private void initRecycleView() {
mAdapter = getAdapter();
mBaseRecycleView.setLayoutManager(new LinearLayoutManager(getContext()));//设置布局管理器
mBaseRecycleView.setAdapter(mAdapter);
}
@Override
public View initView() {
return View.inflate(mContext, R.layout.base_item_fragment, null);
}
//加载数据成功
@Override
public void OnLoadDataSuccess() {
mAdapter.notifyDataSetChanged();
//更新完成数据之后,隐藏进度圈
mBaseDownRefresh.setRefreshing(false);
}
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
//得到总共item条目数量,最后一个可见条目
int totaleItme = manager.getItemCount();
int lastVisibleItem = manager.findLastCompletelyVisibleItemPosition();
if (newState == RecyclerView.SCROLL_STATE_IDLE) { //闲置状态
if (lastVisibleItem == totaleItme - 1) { //最后一条,第一条是0
//加载更多数据,开始位置是当前集合中的对象个数
mPresenter.loadMoreData();
}
}
}
};
private SwipeRefreshLayout.OnRefreshListener listener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//下拉刷新事件的监听
mPresenter.refresh();
}
};
/**
* 子类必须返回presenter对象
*
* @return
*/
protected abstract BaseListPresenter getPresenter(BaseListFragment<T> tBaseListFragment);
/**
* 定义抽象方法,子类必须返回adapter对象
*
* @return
*/
public abstract RecyclerView.Adapter getAdapter();
}
上面给大家扔了一堆代码,看起来很枯燥,但是如果不放,我直接说会更枯燥.由于我画图水平一般,就没有拿出漂亮的结构图,见谅.上面代码就是在对app功能分析的基础上,去分析业务逻辑和基本框架,抽取共同点完成的.这里我们完成了一小步
实现
完成HomeFragment,实现BaseListFragment必须要实现两个抽象方法
public class HomeFragment extends BaseListFragment {
private HomePresenterImpl mHomePresenter;
@Override
protected BaseListPresenter getPresenter(BaseListFragment baseListFragment) {
mHomePresenter = new HomePresenterImpl(baseListFragment);
return mHomePresenter;
}
@Override
public RecyclerView.Adapter getAdapter() {
return new HomeRecycleViewAdapter(mContext, mHomePresenter.getDataList());
}
}
创建HomePresenter的实现类,处理首页界面的业务逻辑
public class HomePresenterImpl implements BaseListPresenter<HomeDataBean> {
private static final String TAG = "HomePresenterImpl";
private ArrayList<HomeDataBean> mDatas;
private Handler mHandler;
private HomeFragment mBaseListFragment;
public HomePresenterImpl(BaseListFragment baseListFragment) {
mBaseListFragment = (HomeFragment) baseListFragment;
mDatas = new ArrayList<>();
mHandler = new Handler();
}
@Override
public void loadDataList() {
loadData(0);
}
@Override
public void refresh() {
mDatas.clear();
loadData(0);
}
@Override
public void loadMoreData() {
loadData(mDatas.size());
}
@Override
public ArrayList<HomeDataBean> getDataList() {
return mDatas;
}
/**
* 请求网络数据
*
* @param offSize 从什么位置开始下载,每次请求数据的长度是固定的10条
*/
private void loadData(int offSize) {
OkHttpClient httpClient = new OkHttpClient();
String url = URLProviderUtil.getHomeUrl(offSize, 10);
Request request = new Request.Builder().get().url(url).build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
//得到返回的json数据
String json = response.body().string();
Gson gson = new Gson();
//将json数据解析成包含对象的集合
ArrayList<HomeDataBean> list = gson.fromJson(json, new TypeToken<List<HomeDataBean>>() {
}.getType());
//将从网络上请求到的数据添加到集合中
mDatas.addAll(list);
//不能在子线程更新UI,通过handler添加
mHandler.post(new Runnable() {
@Override
public void run() {
//数据加载完成-->通知更新数据..更新完成数据之后,隐藏进度圈
mBaseListFragment.OnLoadDataSuccess();
}
});
}
});
}
}
通过上面的操作完成了首页界面.
结构分析
完成首页界面后可能对这个MVP还是没有什么直接的感受,要我来说,主要还是有下面几点明显的优势的
- 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle
针对上面的代码项目我们发现view层的工作就是当一切工作顺利执行后去显示数据,刷新数据,显示进度圈,出现错误显示错误(这里没有做),view层没有去操作数据.
Modle层的操作写在了Present中,就是加载数据,加载数据,加载完成告诉presenter,presenter会继续通知view执行操作.
-
模块职责划分明显,层次清晰
-
对应每个模块分工明确,你做完自己的工作就好了
-
利于测试驱动开发
-
还有等等…好处
缺点
- Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
- 代码复杂度大
- 还有等等…
小结
本以为理解简单写起了就容易了,没想到还是这么难,具体还是要大家去体会了,多用才能熟能生巧.我的源码在GitHub上有.大家可以看看.以后有好多内容还是会继续分享,第一也是为了提升自己,另一方面我们也可以共同进步.有问题还希望多多指导.
我的仓库:https://github.com/hh-pan/Player.git