Kotlin 版的可參考:https://github.com/SheHuan/WanAndroid
這幾年 MVP 在 Android 開發(fā)中已經開始被廣泛使用跋破,逐漸成為一種主流的設計思想瓶蝴。在 MVP 出現(xiàn)之前舷手,我們使用最多的可能就是 MVC 了男窟,那么我們?yōu)槭裁匆褂?MVP,它解決了 MVC 使用中的那些痛點呢弟劲,那我們先從 MVC 說起兔乞。
一凉唐、淺談 MVC
MVC 的全稱是 Model-View-Controller台囱,這三部分在 Android 中可以按照如下的層次劃分:
-
Model(數(shù)據模型層):
主要負責網絡請求簿训、數(shù)據庫等其它 I/O 操作 -
View(視圖層):
主要包括 XML 布局文件(但功能較弱) -
Controller(控制器層):
一般就是 Activity 或者 Fragment米间,負責 MVC 整個流程的調度屈糊、協(xié)作逻锐。但是由于 XML 的視圖處理能力較弱雕薪,需要 Activity 或者 Fragment 承擔部分視圖層工作所袁。
在 Android 中可以用網絡請求的例子來梳理一下 MVC 的流程纲熏,例如,在 Activity(Controller)中通過一個 Button 點擊事件調用 Model 層代碼發(fā)起網絡請求勺拣,當 Model 層網絡請求成功響應后药有,在對應的回調函數(shù)中去更新 View 層的一個 RecyclerView愤惰。按照事件的流向赘理,例子中 MVC 三部分的關系如下:
你可能還見過不同的 MVC 關系圖商模,其實都是對的施流,因為不同的 MVC 實現(xiàn)可能會有不同的事件流向瞪醋,選擇適合自己的就行。
上圖看起來層次結構很清晰践盼,但還是存在一些問題的咕幻,雖然我們可以把網絡請求的 Model 層解耦成一個單獨的模塊谅河,但從網絡請求的發(fā)起到響應結果的處理都集中在了 Activity 中确丢,View 層的功能基本被弱化的可以忽略了鲜侥,基本由 Activity 承擔了描函,所以 Activity 同時承擔了 Controller 和 View 層的職責,帶來了耦合的問題胆数,隨著業(yè)務量的增加必然會導致 Activity 變的臃腫起來必尼,增加維護成本判莉。
那么面對這些問題 MVP 是如何應對的呢券盅,它和 MVC 有何異同呢......
二膛檀、更好的 MVP
MVP 的全稱是 Model-View-Presenter宿刮,它保留了 MVC 中的 View 層和 Model 層僵缺,用 Presenter 層替代了 Controller 層磕潮,在 Android 開發(fā)中三個層次的職責劃分如下:
-
Model(數(shù)據模型層):
主要負責網絡請求容贝、數(shù)據庫等其它 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 層的實現(xiàn)田篇、調用對 View 層隱藏起來斯辰,這樣可以有效的降低 Activity坡疼、Fragment 的代碼量柄瑰,避免代碼臃腫教沾。
所以相比 MVC授翻,MVP 的職責劃分更加清晰堪唐,能夠更好額解耦淮菠,但也會增加一些類文件荤堪。
下邊是一個 MVP 的事件流向圖澄阳,可以看到碎赢,在 MVP 中我們約定 Model 層和 View 層是不能直接通信的揩抡,而是通過 Presenter 層完成交互。
三屋摔、簡單上手
理論性的東西說了一堆钓试,難免有些瞌睡弓熏,先看一個使用的例子。
首先說明一下糠睡,我們的基礎框架中用到了 RxJava2挽鞠、Retrofit2
這里使用了 玩Android 的開放api接口進行測試,測試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 需要實現(xiàn)的接口油挥,以及接下來 Presenter 類中需要實現(xiàn)的業(yè)務方法:
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
類,同時實現(xiàn)了 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
攘乒,同時實現(xiàn)了SampleContract
中的View
接口,并完成對應 Presenter 類的初始化:
public class MainActivity extends BaseMvpActivity<SamplePresenterImpl> implements SampleContract.View {
// 初始化 Presenter 類
@Override
protected SamplePresenterImpl initPresenter() {
return new SamplePresenterImpl(context, this);
}
// 通過 Presenter 發(fā)起初始化網絡請求
@Override
protected void loadData() {
presenter.getBannerData();
presenter.getFriendData();
}
// 設置布局文件 id
@Override
protected int initLayoutResID() {
return R.layout.activity_main;
}
// 數(shù)據初始化
@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 契約接口與之對應堤魁,然后再實現(xiàn)一個具體的 Presenter 類。
更多實現(xiàn)細節(jié)可參考:https://github.com/SheHuan/EasyMvp
四返十、MVP 的封裝過程
既然要封裝妥泉,我們就需要盡可能的把通用的代碼抽離出來,將一些常見的問題洞坑、需求在基礎代碼中處理好盲链。封裝后基礎框架目錄結構如下:
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接口創(chuàng)建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();
}
/**
* 通用耗時任務執(zhí)行方法
*/
public <E> Disposable commonExecute(BasePresenter presenter, final ExecuteListener<E> listener, BaseObserver<E> observer) {
......
}
/**
* 同時執(zhí)行兩個網絡請求藤违,統(tǒng)一處理請求結果
*/
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) {
......
}
/**
* 依次執(zhí)行兩個網絡請求
*/
public <E1, E2> Disposable orderExecute(BasePresenter presenter, Observable<BaseResponse<E1>> observable1, final OrderExecuteListener<E1, E2> listener, BaseObserver<E2> observer) {
......
}
}
BaseObserver
類相當于是網絡請求的觀察者類,可以用來監(jiān)聽網絡請求的開始纵揍、完成顿乒、發(fā)生錯誤等:
public abstract class BaseObserver<E> implements Observer<E> {
private WeakReference<Context> wrContext;
private Disposable disposable;
private BaseNiceDialog dialog;
// 發(fā)生異常時,是否顯示對應的 Toast 提示
private boolean showErrorTip;
/**
* @param context 由于loading通過DialogFragment實現(xiàn)泽谨,無法使用Application Context璧榄,需要使用Activity Context
* @param showLoading 是否顯示加載中l(wèi)oading
* @param showErrorTip 發(fā)生異常時,是否使用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吮炕,對應的響應數(shù)據bean可以這樣定義:
public class BaseResponse<T>{
private String errorCode;
private String errorMsg;
private T data;
......
}
BaseResponse
類的前兩個字段需要根據自己接口的數(shù)據格式進行修改。
RequestManager
類中還有一個ExceptionConvert
類访得,當網絡請求過程中遇到異常時龙亲,不會直接拋出異常,而是進一步轉發(fā)異常信息悍抑,方便統(tǒng)一處理:
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
類會對異常信息進行統(tǒng)一的轉換處理鳄炉,如果需要定制部分異常提示信息則可以著手修改該類。
到此 Model 層的核心功能就實現(xiàn)了搜骡,也是我們封裝過程中最復雜的部分拂盯。
2、Presente 層
接下來看 Presenter 層的實現(xiàn)记靡,就是一個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
方法中已經有所體現(xiàn)。還有就是取消 RxJava 訂閱關系寸痢,防止內存泄漏呀洲、發(fā)生異常。
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();
// 默認數(shù)據請求
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 層的實現(xiàn)相對簡單,BaseMvpFragment
中我們實現(xiàn)了 Fragment 懶加載操作滓窍,在BaseActivity
卖词、BaseFragment
中默認初始化了 ButterKnife 方便控件的綁定。
大家應該還見過其它結構的 MVP 代碼贰您,因為 MVP 更是一種設計思想坏平,并沒有限制性的規(guī)定你的代碼必須要怎么寫,所以只要我們的思路正確锦亦,適合自己的封裝就是好的舶替。
更多實現(xiàn)細節(jié)可參考:https://github.com/SheHuan/EasyMvp