Android 实现一个的 MVP 基础框架
这几年 MVP 在 Android 开发中已经开始被广泛使用,逐渐成为一种主流的设计思想。在 MVP 出现之前,我们使用最多的可能就是 MVC 了,那么我们为什么要使用 MVP,它解决了 MVC 使用中的那些痛点呢,那我们先从 MVC 说起。
一、浅谈 MVC
MVC 的全称是 Model-View-Controller,这三部分在 Android 中可以按照如下的层次划分:
-
Model(数据模型层):
主要负责网络请求、数据库等其它 I/O 操作 -
View(视图层):
主要包括 XML 布局文件(但功能较弱) -
Controller(控制器层):
一般就是 Activity 或者 Fragment,负责 MVC 整个流程的调度、协作。但是由于 XML 的视图处理能力较弱,需要 Activity 或者 Fragment 承担部分视图层工作。
在 Android 中可以用网络请求的例子来梳理一下 MVC 的流程,例如,在 Activity(Controller)中通过一个 Button 点击事件调用 Model 层代码发起网络请求,当 Model 层网络请求成功响应后,在对应的回调函数中去更新 View 层的一个 RecyclerView。按照事件的流向,例子中 MVC 三部分的关系如下:
MVC你可能还见过不同的 MVC 关系图,其实都是对的,因为不同的 MVC 实现可能会有不同的事件流向,选择适合自己的就行。
上图看起来层次结构很清晰,但还是存在一些问题的,虽然我们可以把网络请求的 Model 层解耦成一个单独的模块,但从网络请求的发起到响应结果的处理都集中在了 Activity 中,View 层的功能基本被弱化的可以忽略了,基本由 Activity 承担了,所以 Activity 同时承担了 Controller 和 View 层的职责,带来了耦合的问题,随着业务量的增加必然会导致 Activity 变的臃肿起来,增加维护成本。
那么面对这些问题 MVP 是如何应对的呢,它和 MVC 有何异同呢......
二、更好的 MVP
MVP 的全称是 Model-View-Presenter,它保留了 MVC 中的 View 层和 Model 层,用 Presenter 层替代了 Controller 层,在 Android 开发中三个层次的职责划分如下:
-
Model(数据模型层):
主要负责网络请求、数据库等其它 I/O 操作,和 MVC 中的 Model 层类似 -
View(视图层):
主要是 Activity、Fragment、XML布局文件 -
Presenter(控制器层):
自定义的 Presenter 类,类似于 MVC 中 Controller 的作用,是 MVP 中的核心,主要负责 Model 层和 View 层的交互
在 MVP 中 Activity、Fragment、XML都归属到了 View 层,即不存在 MVC 中 V、C层之间耦合的问题。在 MVC 中,Model 层功能代码的调用是在 Activity、Fragment 中进行的,但在 MVP 中这个职责将由 Presenter 去完成,然后将结果通过接口传递给 View 层,可以将 Model 层的实现、调用对 View 层隐藏起来,这样可以有效的降低 Activity、Fragment 的代码量,避免代码臃肿。
所以相比 MVC,MVP 的职责划分更加清晰,能够更好额解耦,但也会增加一些类文件。
下边是一个 MVP 的事件流向图,可以看到,在 MVP 中我们约定 Model 层和 View 层是不能直接通信的,而是通过 Presenter 层完成交互。
MVP三、简单上手
理论性的东西说了一堆,难免有些瞌睡,先看一个使用的例子。
首先说明一下,我们的基础框架中用到了 RxJava2、Retrofit2
这里使用了 玩Android 的开放api接口进行测试,测试demo的目录结构如下:
demo首先看 WanAndroidApis
接口,按照 Retrofit2 的要求,需要在里边声明网络请求的接口:
public interface WanAndroidApis {
String BASE_URL = Url.WAN_ANDROID_UTL;
@GET("banner/json")
Observable<BaseResponse<List<BannerBean>>> banner();
@GET("friend/json")
Observable<BaseResponse<List<FriendBean>>> friend();
}
bean 目录对应接口返回 JSON 对应的类,就不多说了。
SampleContract
是一个契约接口,这里约定了 Activity 需要实现的接口,以及接下来 Presenter 类中需要实现的业务方法:
public interface SampleContract {
interface View extends BaseView {
void onBannerSuccess(List<BannerBean> data);
void onBannerError(ResponseException e);
void onFriendSuccess(List<FriendBean> data);
void onFriendError(ResponseException e);
}
interface Presenter {
void getBannerData();
void getFriendData();
}
}
SamplePresenterImpl
类对应 MVP 中的 P,继承了我们基础框架中封装的BasePresenter
类,同时实现了 SampleContract
中的Presenter
接口:
public class SamplePresenterImpl extends BasePresenter<SampleContract.View> implements SampleContract.Presenter {
public SamplePresenterImpl(Context context, SampleContract.View view) {
super(context, view);
}
@Override
public void getBannerData() {
RequestManager.getInstance().execute(this, RetrofitManager.getInstance().create(WanAndroidApis.class).banner(),
new BaseObserver<List<BannerBean>>(context, true, true) {
@Override
protected void onSuccess(List<BannerBean> data) {
view.onBannerSuccess(data);
}
@Override
protected void onError(ResponseException e) {
view.onBannerError(e);
}
});
}
@Override
public void getFriendData() {
RequestManager.getInstance().execute(this, RetrofitManager.getInstance().create(WanAndroidApis.class).friend(),
new BaseObserver<List<FriendBean>>(true) {
@Override
protected void onSuccess(List<FriendBean> data) {
view.onFriendSuccess(data);
}
@Override
protected void onError(ResponseException e) {
view.onFriendError(e);
}
});
}
}
最后来看MainActivity
,继承了我们自己封装的基类BaseMvpActivity
,同时实现了SampleContract
中的View
接口,并完成对应 Presenter 类的初始化:
public class MainActivity extends BaseMvpActivity<SamplePresenterImpl> implements SampleContract.View {
// 初始化 Presenter 类
@Override
protected SamplePresenterImpl initPresenter() {
return new SamplePresenterImpl(context, this);
}
// 通过 Presenter 发起初始化网络请求
@Override
protected void loadData() {
presenter.getBannerData();
presenter.getFriendData();
}
// 设置布局文件 id
@Override
protected int initLayoutResID() {
return R.layout.activity_main;
}
// 数据初始化
@Override
protected void initData() {
}
// 控件初始化
@Override
protected void initView() {
}
@Override
public void onBannerSuccess(List<BannerBean> data) {
Log.e("banner", "success");
}
@Override
public void onBannerError(ResponseException e) {
Log.e("banner", "error");
}
@Override
public void onFriendSuccess(List<FriendBean> data) {
Log.e("friend", "success");
}
@Override
public void onFriendError(ResponseException e) {
Log.e("friend", "error");
}
}
到这里一个基本的使用就完成了,在 Fragment 中的使用也是类似的。解耦效果显而易见,职责划分更加清晰。
其实核心的就是每个有 MVP 需求的 Activity 或 Fragment 需要定义一个 Contract 契约接口与之对应,然后再实现一个具体的 Presenter 类。
更多实现细节可参考:https://github.com/SheHuan/EasyMvp
四、MVP 的封装过程
既然要封装,我们就需要尽可能的把通用的代码抽离出来,将一些常见的问题、需求在基础代码中处理好。封装后基础框架目录结构如下:
easymvp
1、Model 层
首先看 Model 层的封装,Model 层主要封装了基于 Retrofit2 的网络请求,即RetrofitManager
类:
public class RetrofitManager {
private OkHttpClient okHttpClient;
private RetrofitManager() {
}
public static RetrofitManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final RetrofitManager INSTANCE = new RetrofitManager();
}
/**
* 根据不同的ApiService接口创建ApiService对象
*/
public <S> S create(Class<S> service) {
Retrofit retrofit = new Retrofit.Builder()
.client(getOkHttpClient(true)) // 设置请求超时、日志相关的拦截器
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(getBaseUrl(service))
.build();
return retrofit.create(service);
}
/**
* 解析接口中的BASE_URL,解决BASE_URL不一致的问题,如果所有的BASE_URL都一致,则可不用该方法
*/
private <S> String getBaseUrl(Class<S> service) {
try {
Field field = service.getField("BASE_URL");
return (String) field.get(service);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
.......
}
Retrofit2 和 RxJava2 是一对好搭档,可以方便的帮助我们完成网络请求的线程切换以及相应处理,所以RequestManager
就是一个可以直接使用的网络请求类:
public class RequestManager {
private RequestManager() {
}
public static RequestManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final RequestManager INSTANCE = new RequestManager();
}
/**
* 通用网络请求方法
*/
public <E> Disposable execute(BasePresenter presenter, Observable<BaseResponse<E>> observable, BaseObserver<E> observer) {
observable
.map(new ResponseConvert<E>())
.onErrorResumeNext(new ExceptionConvert<E>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
presenter.addDisposable(observer.getDisposable());
return observer.getDisposable();
}
/**
* 通用耗时任务执行方法
*/
public <E> Disposable commonExecute(BasePresenter presenter, final ExecuteListener<E> listener, BaseObserver<E> observer) {
......
}
/**
* 同时执行两个网络请求,统一处理请求结果
*/
public <E1, E2, E3> Disposable zipExecute(BasePresenter presenter, Observable<BaseResponse<E1>> observable1, Observable<BaseResponse<E2>> observable2, final ZipExecuteListener<E1, E2, E3> listener, BaseObserver<E3> observer) {
......
}
/**
* 依次执行两个网络请求
*/
public <E1, E2> Disposable orderExecute(BasePresenter presenter, Observable<BaseResponse<E1>> observable1, final OrderExecuteListener<E1, E2> listener, BaseObserver<E2> observer) {
......
}
}
BaseObserver
类相当于是网络请求的观察者类,可以用来监听网络请求的开始、完成、发生错误等:
public abstract class BaseObserver<E> implements Observer<E> {
private WeakReference<Context> wrContext;
private Disposable disposable;
private BaseNiceDialog dialog;
// 发生异常时,是否显示对应的 Toast 提示
private boolean showErrorTip;
/**
* @param context 由于loading通过DialogFragment实现,无法使用Application Context,需要使用Activity Context
* @param showLoading 是否显示加载中loading
* @param showErrorTip 发生异常时,是否使用Toast提示
*/
public BaseObserver(Context context, boolean showLoading, boolean showErrorTip) {
wrContext = new WeakReference<>(context);
this.showErrorTip = showErrorTip;
if (showLoading) {
initLoading();
}
}
public BaseObserver(boolean showErrorTip) {
this.showErrorTip = showErrorTip;
wrContext = new WeakReference<>(App.getContext());
}
@Override
public void onSubscribe(Disposable d) {
disposable = d;
showLoading();
}
@Override
public void onNext(E data) {
hideLoading();
onSuccess(data);
}
@Override
public void onError(Throwable e) {
hideLoading();
ResponseException responseException = (ResponseException) e;
if (showErrorTip) {
Toast.makeText(wrContext.get(), responseException.getErrorMessage(), Toast.LENGTH_SHORT).show();
}
onError(responseException);
}
@Override
public void onComplete() {
}
public Disposable getDisposable() {
return disposable;
}
protected abstract void onSuccess(E data);
protected abstract void onError(ResponseException e);
/**
* 初始化loading
*/
private void initLoading() {
dialog = NiceDialog.init()
.setLayoutId(R.layout.loading_layout)
.setWidth(100)
.setHeight(100)
.setDimAmount(0);
}
/**
* 显示loading
*/
private void showLoading() {
if (dialog != null) {
dialog.show(((BaseActivity) wrContext.get()).getSupportFragmentManager());
}
}
/**
* 取消loading
*/
private void hideLoading() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
}
在这里边我们可以按需显示耗时任务过程中的 Loading,以及异常提示 Toast。
RequestManager
类还用到了一个ResponseConvert
类,将BaseResponse
响应根据接口约定的响应码进一步处理
public class ResponseConvert<E> implements Function<BaseResponse<E>, E> {
@Override
public E apply(BaseResponse<E> baseResponse) {
if (!"0".equals(baseResponse.getErrorCode())) {
// 手动抛出异常
throw new ApiException(baseResponse.getErrorCode(), baseResponse.getErrorMsg());
}
return baseResponse.getData();
}
}
由于测试代码使用了 玩Android 的api,对应的响应数据bean可以这样定义:
public class BaseResponse<T>{
private String errorCode;
private String errorMsg;
private T data;
......
}
BaseResponse
类的前两个字段需要根据自己接口的数据格式进行修改。
RequestManager
类中还有一个ExceptionConvert
类,当网络请求过程中遇到异常时,不会直接抛出异常,而是进一步转发异常信息,方便统一处理:
public class ExceptionConvert<E> implements Function<Throwable, ObservableSource<? extends E>> {
@Override
public ObservableSource<? extends E> apply(Throwable throwable) throws Exception {
return Observable.error(ExceptionHandler.handle(throwable));
}
}
ExceptionHandler
类会对异常信息进行统一的转换处理,如果需要定制部分异常提示信息则可以着手修改该类。
到此 Model 层的核心功能就实现了,也是我们封装过程中最复杂的部分。
2、Presente 层
接下来看 Presenter 层的实现,就是一个BasePresenter
类:
public abstract class BasePresenter<V extends BaseView> {
protected V view;
protected Context context;
private CompositeDisposable compositeDisposable;
public BasePresenter(Context context, V view) {
this.context = context;
this.view = view;
compositeDisposable = new CompositeDisposable();
}
/**
* 保存RxJava绑定关系
*/
public void addDisposable(Disposable disposable) {
if (!compositeDisposable.isDisposed()) {
compositeDisposable.add(disposable);
}
}
/**
* 取消单个RxJava绑定
*/
public void removeDisposable(Disposable disposable) {
if (!compositeDisposable.isDisposed()) {
compositeDisposable.remove(disposable);
}
}
/**
* 当Activity、Fragment destory时,取消当前Presenter的全部RxJava绑定,置空view
*/
public void detach() {
if (!compositeDisposable.isDisposed()) {
compositeDisposable.clear();
}
view = null;
}
}
核心的功能就是保存 RxJava 订阅时返回的Disposable
对象,这一点在RequestManager
的execute
方法中已经有所体现。还有就是取消 RxJava 订阅关系,防止内存泄漏、发生异常。
3、View 层
Presenter
类是在 View 层,即基类 BaseMvpActivity
、BaseMvpFragment
中完成初始化以及Disposable
和View
接口对象的释放操作:
public abstract class BaseMvpActivity<P extends BasePresenter> extends BaseActivity {
protected P presenter;
// 初始化Presenter
protected abstract P initPresenter();
// 默认数据请求
protected abstract void loadData();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = initPresenter();
loadData();
}
@Override
protected void onDestroy() {
if (presenter != null) {
presenter.detach();
}
super.onDestroy();
}
}
View 层的实现相对简单,BaseMvpFragment
中我们实现了 Fragment 懒加载操作,在BaseActivity
、BaseFragment
中默认初始化了 ButterKnife 方便控件的绑定。
大家应该还见过其它结构的 MVP 代码,因为 MVP 更是一种设计思想,并没有限制性的规定你的代码必须要怎么写,所以只要我们的思路正确,适合自己的封装就是好的。
更多实现细节可参考:https://github.com/SheHuan/EasyMvp