文章目录
前言
最近的招聘面试过程中,发现很多工作了很多年的面试者任然在使用
MVC
架构在开发相对复杂的app应用,即使是使用了MVP
架构的人往往也对整体架构的分离不是很清晰,对于架构的优点和可扩展的方向不甚了解。今天就准备一篇博客来简述一下什么是MVP,如何实现一个简单的MVP架构,使用MVP有什么好处。
参考资料
– Android MVP开发模式 google 官方Mvp架构详解
https://blog.csdn.net/jungle_pig/article/details/65626469
– 谷歌官方架构
https://github.com/googlesamples/android-architecture
概述
MVP模式 概念
之前有一个MVC模式: Model-View-Controller.
MVC模式 有两个主要的缺点: 首先, View持有Controller和Model的引用; 第二, 它没有把对UI逻辑的操作限制在单一的类里, 这个职能被Controller和View或者Model共享.
所以后来提出了MVP模式来克服这些缺点.
MVP(Model-View-Presenter)模式:
- Model: 数据层. 负责与网络层和数据库层的逻辑交互.
- View: UI层. 显示数据, 并向Presenter报告用户行为.
- Presenter: 从Model拿数据, 应用到UI层, 管理UI的状态, 决定要显示什么, 响应用户的行为.
MVP模式的最主要优势就是耦合降低, Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity, Fragment等关联, 便于写单元测试.
基本的Model-View-Presenter架构
整体结构分为以下几块:
- 控制器,通常由Activity来承担
- 视图,通常使用Fragment来实现
- Presenter,用于承担Fragment视图的数据操作和复杂的业务流程,并在执行完成后通知View进行视图更新操作。
- Contract,定义View和Presenter中应该分别实现的接口
每个功能都有:
- 一个定义View和Presenter接口的Contract接口;
- 一个Activity用来管理fragment的创建;
- 一个实现了View接口的Fragment, 并提供presenter的创建;
- 一个实现了Presenter接口的presenter.
环境准备
引用的第三方框架
项目中需要使用
butterknife
来实现对视图的注解绑定,在app
工程下的build.gradle
文件中的dependencies
配置中添加如下配置
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
在整个工程下的build.gradle
中添加
classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
模型类
public class WeatherBean {
//最低温度
private int minTempetarute;
//最高温度
private int maxTempetarute;
//城市名称
private String cityName;
public int getMinTempetarute() {
return minTempetarute;
}
public void setMinTempetarute(int minTempetarute) {
this.minTempetarute = minTempetarute;
}
public int getMaxTempetarute() {
return maxTempetarute;
}
public void setMaxTempetarute(int maxTempetarute) {
this.maxTempetarute = maxTempetarute;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
}
数据适配器
public class WeatherListAdapter extends BaseAdapter {
private List<WeatherBean> dataList =
new ArrayList<WeatherBean>();
private Context mContext;
public WeatherListAdapter(Context context,
List<WeatherBean> list)
{
mContext = context;
if(null != list) {
dataList.addAll(list);
}
}
public List<WeatherBean> getDataSource()
{
return dataList;
}
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position,
View convertView, ViewGroup parent) {
ViewHolder holder;
if(null == convertView)
{
convertView = LayoutInflater.from(mContext).
inflate(R.layout.weather_list_item, null);
holder = new ViewHolder(convertView);
holder.cityNameTextView = convertView.
findViewById(R.id.cityNameTextView);
holder.temperatureRangeTextView = convertView.
findViewById(R.id.temperatureRangeTextView);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
WeatherBean weatherBean = dataList.get(position);
holder.cityNameTextView
.setText(weatherBean.getCityName());
holder.temperatureRangeTextView
.setText(weatherBean.getMinTempetarute()
+ " ~~ " + weatherBean.getMaxTempetarute() + " ℃");
return convertView;
}
class ViewHolder
{
@BindView(R.id.cityNameTextView)
TextView cityNameTextView;
@BindView(R.id.temperatureRangeTextView)
TextView temperatureRangeTextView;
public ViewHolder(View view)
{
ButterKnife.bind(this, view);
}
}
}
布局文件
MainActivity的布局文件activity_main.xml
内容如下:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/containerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
adapter中使用的weather_list_item.xml
内容如下:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/cityNameTextView"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:gravity="left"
android:textSize="16dp"
android:layout_margin="20dp"
android:textColor="#ffffff"/>
<TextView
android:id="@+id/temperatureRangeTextView"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:gravity="left"
android:textSize="14dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginRight="20dp"
android:textColor="#ffffff"/>
</LinearLayout>
简单示例搭建
创建基础接口
/**
* 视图基本操作接口
*
* */
public interface IBaseView<T> {
/**
* 是否已经添加到父视图中
* */
public boolean isAddedToParentView();
/**
* 获取当前的上下文
* */
public Context getCurrentContext();
}
public interface IBasePresenter {
}
创建Contract接口
Contract接口主要用于分别定义
View
和Presenter
的行为,然后自定义的Presenter类实现Contract.Presenter
接口,Fragment或Activity实现Contract.View
接口。
public interface FirstFragmentContract {
//定义视图操作行为
interface View extends IBaseView<Presenter>
{
public void onFirstLoadDataArrived(List list);
public void onLoadMoreDataArrived(List list);
public void onRefreshError();
public void onLoadMoreError();
}
//定义代理人操作行为
interface Presenter
{
public void refershData();
public void loadMoreData();
}
}
创建Presenter类
public class FirstPresenter
implements FirstFragmentContract.Presenter {
private FirstFragmentContract.View mView;
private int page = 1;
public FirstPresenter(FirstFragmentContract.View view)
{
mView = view;
}
@Override
public void refershData() {
if(null == mView){
Log.i("Debug", "mView is null");
mView.onRefreshError();
return;
}
if(page > 1){
mView.onRefreshError();
return;
}
mView.onFirstLoadDataArrived(getWeatherList(page));
page++;
}
@Override
public void loadMoreData() {
if(null == mView){
Log.i("Debug", "mView is null");
return;
}
if(page > 3){
mView.onLoadMoreError();
return;
}
mView.onLoadMoreDataArrived(getWeatherList(page));
page++;
}
private List getWeatherList(int page){
ArrayList<WeatherBean> dataList = new ArrayList<>();
for(int i = 0; i < 10; i++)
{
WeatherBean weatherBean = new WeatherBean();
weatherBean.setCityName("南京" + page);
int minTempetarute = (int)(Math.random() * 30);
int maxTempetarute = minTempetarute +
(int)(Math.random() * 30);
weatherBean.setMinTempetarute(minTempetarute);
weatherBean.setMaxTempetarute(maxTempetarute);
dataList.add(weatherBean);
}
return dataList;
}
}
创建Fragment类
public class FirstFragment extends Fragment
implements FirstFragmentContract.View{
FirstPresenter firstPresenter;
private View view;
@BindView(R.id.weatherListView)
ListView weatherListView;
@BindViews({R.id.refreshDataButton, R.id.loadDataButton})
List<Button> buttonList;
private WeatherListAdapter weatherListAdapter;
Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState)
{
super.onCreateView(inflater, container,
savedInstanceState);
if(null == view)
{
firstPresenter = new FirstPresenter(this);
view = inflater.inflate(
R.layout.first_fragment, null);
//这里必须进行绑定
unbinder = ButterKnife.bind(this, view);
weatherListAdapter =
new WeatherListAdapter(getContext(), null);
weatherListView.setAdapter(weatherListAdapter);
firstPresenter.refershData();
}
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
if(null != unbinder){
unbinder.unbind();
}
}
@Override
public void onFirstLoadDataArrived(List list) {
weatherListAdapter.getDataSource().addAll(0, list);
weatherListAdapter.notifyDataSetChanged();
}
@Override
public void onLoadMoreDataArrived(List list) {
weatherListAdapter.getDataSource().addAll(list);
weatherListAdapter.notifyDataSetChanged();
}
@Override
public void onRefreshError() {
Toast.makeText(getContext(), "未发现新数据" ,
Toast.LENGTH_LONG).show();
buttonList.get(0).setEnabled(false);
}
@Override
public void onLoadMoreError() {
Toast.makeText(getContext(), "已加载完所有数据" ,
Toast.LENGTH_LONG).show();
buttonList.get(1).setEnabled(false);
}
@Override
public boolean isAddedToParentView() {
return true;
}
@Override
public Context getCurrentContext() {
return getContext();
}
@OnClick({R.id.refreshDataButton, R.id.loadDataButton})
public void onViewClicked(View v){
switch (v.getId())
{
case R.id.refreshDataButton:
firstPresenter.refershData();
break;
case R.id.loadDataButton:
firstPresenter.loadMoreData();
break;
}
}
}
创建Activity
public class MainActivity extends AppCompatActivity {
@BindView(R.id.containerLayout)
LinearLayout containerLayout;
Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
FirstFragment firstFragment = new FirstFragment();
FragmentTransaction fragmentTransaction =
getFragmentManager().beginTransaction();
fragmentTransaction.add(
R.id.containerLayout, firstFragment);
fragmentTransaction.commit();
}
@Override
public void onDestroy()
{
super.onDestroy();
if(null != unbinder)
{
unbinder.unbind();
}
}
}
总结
MVP通过将整体结构分为: 1. Model层,用户进行数据封装和操作 2. View层,通常使用Fragment来实现视图的加载与操作,将fragment添加到activity中进行管理。 3. Presenter层,作为视图层和数据业务处理层之间的中间代理人,通常作为业务层来进行代码分离和解耦。4. Contract接口层,预先定义数据和视图接口规范数据层和视图层的操作行为。
每一层都队里完成自己的工作,通过接口进行回调,大大降低了代码的耦合度,代码层级分明加强了程序的可维护性,方便业务拓展。
后期还可以通过加入RxJava、Retrofit、Dagger来进一步降级代码耦合性,增加代码的可维护性。