分享一个简单的MVP架构,适合初学者,这个MVP框架,没有那么严格。前两年刚开始步入工作的阶段看到了他们代码架构,就学会了,比较明了。不过这个框架有个缺点model是没有的,或者不够明显,直接在Presenter中使用网络框架获取数据,我会稍微修改一下这个框架,不过在代码上就不够结构化,有需要的小伙伴自己优化一下。想通过MVP的思想,从而引出后面的MVVM,也算对自己所学,整理一下。这些都是属于自己的理解,有错误的地方欢迎指正。谷歌官方也有发布一套MVP架构Demo,也可以使用它的,更加的严谨。
概念:M->Model 数据提供者 V->View 界面显示者 P->Presenter 控制和M与V的数据流通中枢。用户刚开始看到的界面View(activity或者fragment)通过点击或者刚开始初始化,调用Presenter的方法,Presenter对界面传过来的数据处理一下或者直接给Model调取获取数据的方法,Presenter获取到数据后,再使用View的回调将数据给到View层。
为什么要使用MVP架构?
1.普通不使用架构的代码找代码很麻烦,一个页面,比如表单填报东西以及选择项,可能会有五六个网络接口,每个网络接口设置几个TextView显示。网络框架一般都会有个成功回调,失败回调,再加上本身其自己的代码量,确实比较臃肿。使用了MVP的话,对于View(activity或者fragment)层来说,他的代码就只包含基础的onCreate之类,以及界面点击事件,调取Presenter的方法,和数据回调。其中界面显示就在数据回调中操作
2.解耦,每个模块负责各自的功能,在修改或者查找bug的时候能很快定位。比方说,某些选择项十来个,是本地代码写死的。现在要改成从后台获取,在数据结构不变的情况下,只需要修改Model层,把写死的数据删除,改成后台接口。即使数据结构和后台不一致,也可以再修改Presenter层将数据转化一下。
3.多人工作的可实现性。比如有人负责就对接口,写Model层;有人就写界面,并且定义好数据回调;再有一人可以负责Presenter层。虽然,,,,都是我一个人,哈哈哈,这是我想象的。
如何使用?其实MVP原理比较的简单,从代码难度上看,是对java基础的使用。
首先定义一个数据回调接口类,而View(activity或者fragment)实现这个接口类,在Presenter中持有接口类实体以及Model数据获取类的实体。从View层触发Presenter的获取数据的方法,从而调用了Model的网络接口或者本地数据,Presenter在网络接口回调中,调用View接口类实体的方法,从而改变界面。看下面代码
//这里简单的手打一下架构的实现方式,具体的后面架构应该怎么写后面再说,这里非常的简单,
//理解一下就行了,也不是通用的框架。
//View层需要的接口定义
public interface ITestView {
void getListSuccess(List<Data> dataList);
void getFail();
}
//我就直接写我的网络框架了,这不是重点
public class TestModel {
private RetrofitApi api = RetrofitUtil.getApi();
public Observable<List<Data>> getDataList() {
return api.getDataList().compose(new ObjTransform<>(null));
}
}
//Presenter层
public class TestPresenter {
private ITestView viewImp;//接口的实体类
private TestModel model;
public TestPresenter() {
model = new TestModel;
}
public void setView(ITestView view) {
viewImp = view;
}
public void getData() {
model.subscribe(new Observer<List<Data>>() {
@Override
public void onSubscribe(Disposable d) {
//这里一般是让Presenter跟随activity的生命周期
//假如activity销毁了,就解绑
}
@Override
public void onNext(List<Data> value) {
viewImp.getListSuccess(value);
}
@Override
public void onError(Throwable e) {
viewImp.getFail();
}
@Override
public void onComplete() {
}
});
}
}
//View层,就用个activity来示意吧
public class TestActivity extend AppCompatActivity implements ITestView {
private TestPresenter mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mPresenter = new TestPresenter();
mPresenter.setView(this);
//调用Presenter获取数据
mPresenter.getData();
}
@Override
public void getListSuccess(List<Data> dataList) {
//这里可以设置界面显示,比如把数据设置到recyclerView上啊,或者取第几个显示之类的
}
@Override
public void getFail() {
//这里获取数据失败了,可以停止刷新之类的
}
}
基本手打,跑不起来我不负责任啊,就如上面的代码,分成几个模块:接口定义模块ITestView,Model模块TestModel,Presenter模块TestPresenter,View模块TestActivity。非常简单,现在MVP原理基本就是上面这样,如何实现,以及架构化,通用化,就看后面的封装了。
从代码可以看出,View层是不关心这数据从哪里来的,是从后台获取的,本地数据库中的,还是自己写的假数据,它都不在意。我们只需要实现数据过来时,我们应该做出什么操作就行了。
对于Model层,其实假如都是根据后台获取数据的,或者本地获取数据的,不经常改动接口,model层有时候不写,这的确不太严谨,不过能节省一些时间,并且代码结构看着差别不大,比如Presenter中调取Model的方法,也可以直接写成:
RetrofitUtil.getApi().getDataList().compose(new ObjTransform<>(null))
.subscribe(new Observer() {});
Presenter层则是不可缺少的,虽然上面代码很少,但是实际使用中,六七个网络接口的实现,以及数据处理(说几种情况,比如后台存的84坐标系,而我们使用高德腾讯地图,需要坐标系转换一下,比如后台视频列表,获取第一帧图片显示,再比如数据结构的转换)它是Model和View的控制器,通过在数据获取的回调中,调取view的方法,从而改变界面显示。
比方说这个时候,后台接口变了,这时候你就改model层就行了;假如说数据客户端自己处理一下,把中间某些数据删除,那么改动Presenter层就行了;假如说,产品不满意,需要在界面上再增加几个字段,或者自定义一下控件,那么你改动View层就好了。这是我认为MVP的好处,解耦以及结构化。
那么再来看看这个通用架构怎么写,Model就不说了,数据获取类,在Model基类中可以放网络框架的实体,比如上面的api,也可以放入SharedPreferences插入数据和获取数据。
Presenter:
/**
* @desc BasePresenter
*/
public abstract class BasePresenter<V> {
protected Reference<V> mViewRef;
protected V viewImp;
public BasePresenter() {
}
public void attachView(V view){
mViewRef = new WeakReference<V>(view);
viewImp = getView();
}
protected V getView(){
return mViewRef.get();
}
public boolean isViewAttached(){
return mViewRef != null&&mViewRef.get()!=null;
}
public void detachView() {
if(mViewRef!=null) {
mViewRef.clear();
mViewRef = null;
}
}
}
如上图,也没什么代码量,attachView和之前写的setView作用相同,使用了弱引用,防止过久持有activity无法回收。
再看看View层:
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.xx.utils.DialogUtil;
import butterknife.ButterKnife;
import pub.devrel.easypermissions.EasyPermissions;
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
private DialogUtil mDialogWaiting;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
//判断是否使用MVP模式
mPresenter = createPresenter();
if (mPresenter != null) {
mPresenter.attachView((V) this);//因为之后所有的子类都要实现对应的View接口
}
//子类不再需要设置布局ID,也不再需要使用ButterKnife.bind()
if (!needDataBinding()) {
setContentView(provideContentViewId());
}
ButterKnife.bind(this);
initByBundle(savedInstanceState);
initView();
initData();
initListener();
}
protected void initByBundle(Bundle savedInstanceState) {
}
protected boolean needDataBinding() {
return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
protected void init() {
}
protected void initView() {
}
protected void initData() {
}
protected void initListener() {
}
//用于创建Presenter和判断是否使用MVP模式(由子类实现)
protected abstract T createPresenter();
//得到当前界面的布局文件id(由子类实现)
protected abstract int provideContentViewId();
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
/**
* 显示等待提示框
*/
protected void showWaitingDialog() {
DialogUtil.showWaittingDialog(this);
}
/**
* 隐藏等待提示框
*/
protected void hideWaitingDialog() {
DialogUtil.closeWaittingDialog();
}
}
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
protected T mPresenter;
protected boolean alive = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
//判断是否使用MVP模式
mPresenter = createPresenter();
if (mPresenter != null) {
mPresenter.attachView((V) this);//因为之后所有的子类都要实现对应的View接口
}
alive = true;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(provideContentViewId(), container, false);
ButterKnife.bind(this, rootView);
initView(rootView);
initByBundle(savedInstanceState);
return rootView;
}
protected void initByBundle(Bundle savedInstanceState) {
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData();
initListener();
}
public void showWaitDialog() {
DialogUtil.showWaittingDialog(getContext());
}
/**
* 隐藏等待提示框
*/
protected void hideWaitingDialog() {
DialogUtil.closeWaittingDialog();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
@Override
public void onDestroyView() {
alive = false;
super.onDestroyView();
ButterKnife.unbind(this);
}
protected void init() {
}
protected void initView(View rootView) {
}
protected void initData() {
}
protected void initListener() {
}
//用于创建Presenter和判断是否使用MVP模式(由子类实现)
protected abstract T createPresenter();
//得到当前界面的布局文件id(由子类实现)
protected abstract int provideContentViewId();
}
写一个抽象基类BaseActivity,泛型指定的是,接口定义类IView和Presenter类,有重写的那些initData和initListener就不再赘述了,展示等待弹窗和隐藏的话,可以按照自己的需求写。
定义了一个mPresenter实体,从onCreate看,mPresenter被抽象方法赋值,不为空的话绑定当前的View,需要实现该泛型接口定义类。再往下还用了ButterKnife,比较简单的架构,看看实际使用中是什么样子的:
放几个不暴露公司业务的代码,界面是一个关键字输入框,一个取消关键字按钮,一个列表,列表项如下图拥有选择功能,可以多选
功能是一个页面能选择一些列表项并返回给前页,实现方法用的是startActivityForResult:
先是view的接口定义类,只定义了一个获取列表数据的方法,写了个BaseView,用来放置Toast提醒
public interface BaseView {
void showToast(String str);
}
import java.util.List;
public interface IChooseCompanyView extends BaseView {
void getListSuccess(List<IdAndName> companyList);
}
然后是Presenter层:
public class ChooseCompanyPresenter extends BasePresenter<IChooseCompanyView> {
private String keyword;
private ArrayList<IdAndName> chooseCompany = new ArrayList<>();
public ArrayList<IdAndName> getChooseCompany() {
return chooseCompany;
}
public void setChooseCompany(ArrayList<IdAndName> chooseCompany) {
this.chooseCompany = chooseCompany;
}
public boolean isChooseCompany(IdAndName data) {
for (IdAndName pollutantData : chooseCompany) {
if (pollutantData.id == data.id) {
return true;
}
}
return false;
}
public void removeChoose(IdAndName data) {
int position = -1;
for (int i = 0; i < chooseCompany.size(); i++) {
if (chooseCompany.get(i).id == data.id) {
position = i;
break;
}
}
if (position != -1) {
chooseCompany.remove(position);
}
}
public void addChooseData(IdAndName data) {
chooseCompany.add(data);
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public void getList() {
RetrofitUtil.getApi().getCompanyList(keyword)
.compose(new ObjTransform<>(null))
.subscribe(new HttpObserver<List<IdAndName>>() {
@Override
public void onNext(List<IdAndName> idAndNames) {
super.onNext(idAndNames);
viewImp.getListSuccess(idAndNames);
}
@Override
public void onError(Throwable t) {
super.onError(t);
viewImp.showToast(t.getMessage());
}
});
}
}
代码很简单,上面还有防止重复选择,以及去除列表项的功能,有需要的小伙伴可以看看。
再看看activity
public class ChooseCompanyActivity extends BaseActivity<IChooseCompanyView, ChooseCompanyPresenter> implements IChooseCompanyView {
@Bind(R.id.head_view)
StatusBarHeadView headView;
@Bind(R.id.et_search)
EditText etSearch;
@Bind(R.id.tv_cancel)
TextView tvCancel;
@Bind(R.id.recycler_view)
RecyclerView recyclerView;
@Bind(R.id.bt_commit)
Button btCommit;
private CompanyAdapter companyAdapter;
public static Intent instance(Context context, ArrayList<IdAndName> companyList) {
return new Intent(context, ChooseCompanyActivity.class)
.putExtra("companyList", companyList);
}
@Override
protected void initData() {
ArrayList<IdAndName> dataArrayList = (ArrayList<IdAndName>) getIntent().getSerializableExtra("companyList");
if (dataArrayList != null) {
mPresenter.setChooseCompany(dataArrayList);
}
AppUtils.rvLinearManager(recyclerView, this);
companyAdapter = new CompanyAdapter();
companyAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
IdAndName company = companyAdapter.getItem(position);
if (company != null) {
if (mPresenter.isChooseCompany(company)) {
mPresenter.removeChoose(company);
} else {
mPresenter.addChooseData(company);
}
}
companyAdapter.notifyDataSetChanged();
}
});
recyclerView.setAdapter(companyAdapter);
mPresenter.getList();
}
@Override
protected void initListener() {
headView.setIvLeftOnClickListening(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btCommit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPresenter.getChooseCompany().size() == 0) {
ToastInstance.ShowText("请选择");
return;
}
Intent intent = new Intent();
intent.putExtra("chooseCompany", mPresenter.getChooseCompany());
setResult(Activity.RESULT_OK, intent);
finish();
}
});
etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mPresenter.setKeyword(etSearch.getText().toString());
companyAdapter.setNewData(null);
mPresenter.getList();
return true;
}
return false;
}
});
tvCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
etSearch.setText("");
mPresenter.setKeyword(null);
companyAdapter.setNewData(null);
mPresenter.getList();
}
});
}
@Override
public void getListSuccess(List<IdAndName> companyList) {
companyAdapter.setNewData(companyList);
}
@Override
public void showToast(String str) {
ToastInstance.ShowText(str);
}
@Override
protected ChooseCompanyPresenter createPresenter() {
return new ChooseCompanyPresenter();
}
@Override
protected int provideContentViewId() {
return R.layout.activity_choose_company;
}
private class CompanyAdapter extends BaseQuickAdapter<IdAndName, BaseViewHolder> {
public CompanyAdapter() {
super(R.layout.item_company_choose, null);
}
@Override
protected void convert(BaseViewHolder holder, IdAndName item) {
AppCompatCheckBox checkBox = holder.getView(R.id.checkbox);
checkBox.setChecked(mPresenter.isChooseCompany(item));
checkBox.setText(item.name);
}
}
}
不过这个例子不怎么好,功能比较少,没发挥出presenter的功效,假如有多表单,多接口的地方,实际使用就会舒服很多,那些代码就不放了,过于冗长。