高階MVP+Retrofit+RxJava實戰——一步步帶你搭建一套好用的專案框架

study.png
本文導語:
如果對Rxjava+Retrofit聯網不熟悉的朋友,可以參考下我之前寫的幾篇文章,有比較詳細的講解。
1、 ofollow,noindex">優雅封裝Retrofit+RxJava聯網的統一管理類
3、 輕鬆搞定Retrofit不同網路請求方式的請求引數配置
為了方便大多數朋友理解,我使用Java程式碼寫了個 Demo 。會使用Kotlin的同學,可以直接使用Kotlin寫,在專案中使用起來更方便簡潔哦~
閱讀完本文你將收穫到:
1、如何將Retrofit+RxJava聯網框架,結合泛型MVP架構,優雅靈活地運用到專案中。
2、BaseMvpActivity和BaseMvpFragment的封裝
3、統一封裝介面返回的json資料
4、自定義介面對網路請求的異常回調進行統一的處理。
5、MVC、MVP架構的理解
使用MVP+Retrofit+RxJava框架後
下面是本文中Demo的演示效果:
還是以請求天氣資訊 http://t.weather.sojson.com/api/weather/city/101030100 介面為例

Animation.gif
我們看看在該框架下,Activity的程式碼是如何寫的:
/** * 測試MVP + Retrofit +RxJava */ public class MainActivity extends BaseMvpActivity<MainPresenter, MainModel> implements MainContract.View { @BindView(R.id.tv_quality) TextView tv_quality; @BindView(R.id.tv_pm) TextView tv_pm; @BindView(R.id.tv_wendu) TextView tv_wendu; @BindView(R.id.tv_notice) TextView tv_notice; @Override protected int getContentViewLayoutID() { return R.layout.activity_main; } @Override protected void initViewAndEvents() { //發起請求 mMvpPresenter.getWeather("101030100", mMultipleStateView); } /** * 介面請求成功的回撥方法 * * @param bean */ @Override public void getWeather(WeatherEntity bean) { Log.e("TAG", "請求成功"); tv_quality.setText("空氣質量:" + bean.quality); tv_pm.setText("Pm10" + bean.pm10); tv_wendu.setText("溫度:" + bean.wendu); tv_notice.setText("提醒:" + bean.ganmao); } }
可以看到,在此專案架構中,Activity的程式碼非常簡潔,此時的Activity,只需要關心發出指令(即發起請求)和接收指令的返回結果(即處理介面請求返回的資料),然後進行相應的UI更新操作即可。相比MVC,在MVP中,Presenter承擔了業務邏輯處理的職責,這樣大大減輕了Activity的負擔,讓其不再臃腫、難以維護。下面詳細的介紹這套框架的搭建思路。
《一》對Retrofit+RxJava聯網框架的優化
閱讀過 優雅封裝Retrofit+RxJava聯網的統一管理類 這篇文章的朋友應該知道,通過一個統一管理類,我們已經可以很方便的在專案中進行聯網操作了。如下:
RetrofitManager.getInstance().getRequestService().getWeather("101030100") .compose(RxSchedulers.io_main()) .subscribeWith(new DisposableObserver<Object>() { @Override public void onNext(Object result) { Log.e("TAG", "result=" + result.toString()); } @Override public void onError(Throwable e) { Log.e("TAG", "onError=" + e.getMessage()); } @Override public void onComplete() { Log.e("TAG", "onComplete"); } });
如果不做任何處理,當然也是可以實現需求的。不過往往在實際專案開發過程中,不同公司的後臺定義的json格式可能有所區別,不過大同小異的是,一般而言我們實際需要用到的資料都在data欄位裡。例如我們來看一下獲取天氣資訊的免費API:
http://t.weather.sojson.com/api/weather/city/101030100
image.png
(1)一般而言,後端返回的json資料會有一些統一狀態資訊。例如message、status、time等。每個json串都對應著Java中的一個實體,此時我們可以把返回json資料對應的實體型別進行簡單的封裝。如下:
public class BaseHttpResponse<T> { public int status; //具體含義由後端定義 public String message; //請求結果的描述-成功/失敗/引數錯誤等 public T data; //實際有用的資料 }
(2)我們通過一個自定義介面,來處理網路請求中的回撥:
public interface BaseObserverListener<T> { void onSuccess(T result); void onComplete(); void onError(Throwable e); void onBusinessError(ErrorBean errorBean); }
(3)對應的,我們在RetrofitManager中,對介面返回結果及資料進行統一處理:
/** * 建立請求 */ public <T> DisposableObserver<BaseHttpResponse<T>> doRequest(Observable<BaseHttpResponse<T>> observable, final BaseObserverListener<T> observerListener) { return observable .compose(RxSchedulers.<BaseHttpResponse<T>>io_main()) .subscribeWith(new DisposableObserver<BaseHttpResponse<T>>() { @Override public void onNext(BaseHttpResponse<T> result) { if (result.status == 200) { observerListener.onSuccess(result.data); } else { ErrorBean errorBean = new ErrorBean(); errorBean.setCode(result.status + ""); errorBean.setMsg(result.message); observerListener.onBusinessError(errorBean); } } @Override public void onError(Throwable e) { observerListener.onError(e); } @Override public void onComplete() { observerListener.onComplete(); } }); }
(4)實際上對我們而言,網路請求的返回結果,我們可以廣義的理解為兩種情況:成功或失敗。
① 成功肯定就一種情況,那就是請求獲取到了頁面展示需要的資料,也就是介面返回的結果,走到了onSuccess()的回撥中;
② 但是失敗的情形可能有多種 :介面請求失敗(服務端異常)、無網路、介面資料返回錯誤(如data返回null)等。
我們其實只關心介面成功,因為我們需要拿成功的資料去渲染頁面,那麼失敗的情形,很多時候就是給使用者一個友好的Toast提示或者是顯示錯誤頁面。那麼此時,我們可以把失敗的情形,做一下統一處理,同時將具體的處理交給View負責。如下:
public abstract class RxObserverListener<T> implements BaseObserverListener<T> { private IBaseView mView; protected RxObserverListener(IBaseView view) { this.mView = view; } /** * 統一處理異常情況:包括沒網、資料返回錯誤等 * * @param e */ @Override public void onError(Throwable e) { RetrofitException.ResponseThrowable responseThrowable = RetrofitException.getResponseThrowable(e); Log.e("TAG", "e.code=" + responseThrowable.code + responseThrowable.message); ErrorBean errorBean = new ErrorBean(); errorBean.setMsg(responseThrowable.message); errorBean.setCode(responseThrowable.code + ""); if (mView != null) { mView.showException(errorBean); mView.dismissDialogLoading(); Toast.makeText(MyApplication.getContext(), responseThrowable.message, Toast.LENGTH_SHORT); } } /** * 介面http結果返回200,但是後臺資料返回錯誤。 * @param errorBean */ @Override public void onBusinessError(ErrorBean errorBean) { if (mView != null) { mView.showBusinessError(errorBean); mView.dismissDialogLoading(); //CommonUtils.makeEventToast(BaseApplication.getInstance(), errorBean.getMsg(), false); Log.e("TAG", "onBusinessError msg=" + errorBean.getMsg()); } } @Override public void onComplete() { } }
public interface IBaseView { /** * show loading message * * @param msg */ void showLoading(MultipleStatusView multipleStatusView, String msg); /** * hide loading */ void hideLoading(); /** * dialog loading */ void showDialogLoading(String msg); /** * dismissdialog loading */ void dismissDialogLoading(); /** * show business error :網路異常及資料錯誤等異常情況 */ void showBusinessError(ErrorBean error); void showException(ErrorBean error); }
通過上面的處理,我們的網路請求就可以簡化成下面的形式:
RetrofitManager.getInstance().doRequest(mModel.getWeather(city_code), new RxObserverListener<WeatherEntity>() { @Override public void onSuccess(WeatherEntity result) { //介面返回成功 } });
《二》MVP架構的搭建及BaseMvpActivity的封裝
(1)建立一個基類View,定義View層的行為,所有View介面都必須實現。
public interface IBaseView { /** * show loading message * @param msg */ void showLoading(MultipleStatusView multipleStatusView, String msg); /** * hide loading */ void hideLoading(); /** * show business error :網路異常及資料錯誤等異常情況 */ void showBusinessError(ErrorBean error); void showException(ErrorBean error); }
(2)建立一個基類的Presenter,在類上規定View和Model的泛型,然後定義繫結和解綁的方法。
public abstract class BasePresenter<V, M > { public M mModel; public V mView; public RxManager rxManager = new RxManager(); /** * 繫結 */ public void setVM(V v, M m) { this.mView = v; this.mModel = m; } /** * 解綁: 防止記憶體洩漏 */ public void onDestroy() { rxManager.clear(); rxManager = null; mView = null; } }
(3)建立一個基類Model,定義Model層的行為,所有Model介面都必須實現。
public interface BaseModel { }
(4)定義一個契約類(介面)Contract。使用契約類來統一管理view與presenter的所有的介面,這種方式使得view與presenter中有哪些功能,一目瞭然,維護起來也很方便。例如Demo中的MainContract:
public interface MainContract { interface View extends IBaseView { void getWeather(WeatherEntity bean); } interface Model extends BaseModel { Observable<BaseHttpResponse<WeatherEntity>> getWeather(String city_code); } abstract class Presenter extends BasePresenter<MainContract.View, MainContract.Model> { public abstract void getWeather(String city_code, MultipleStatusView multipleStatusView); } }
(5)建立一個基類的Activity,通過反射建立Presenter的物件。這樣在具體的Activity中,我們就可以直接拿著Presenter的物件進行相應的操作。BaseMvpFragment的封裝思路同BaseMvpActivity,在這裡就不贅述了。
public abstract class BaseMvpActivity<P extends BasePresenter, M extends BaseModel> extends AppCompatActivity implements IBaseView { protected P mMvpPresenter; protected M mModel; protected MultipleStatusView mMultipleStateView; private Unbinder unBinder; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getContentViewLayoutID() != 0) { mMultipleStateView = new MultipleStatusView(this); setContentView(View.inflate(this, getContentViewLayoutID(), mMultipleStateView)); unBinder = ButterKnife.bind(this); } else { throw new IllegalArgumentException("You must return a right contentView layout resource Id"); } //MVP mMvpPresenter = ClassReflectHelper.getT(this, 0); mModel = ClassReflectHelper.getT(this, 1); if (this instanceof IBaseView) { if (mMvpPresenter != null && mModel != null) { mMvpPresenter.setVM(this, mModel); } } initViewAndEvents(); } protected abstract int getContentViewLayoutID(); protected abstract void initViewAndEvents(); @Override protected void onDestroy() { super.onDestroy(); unBinder.unbind(); if (mMvpPresenter != null) { mMvpPresenter.onDestroy(); } } @Override public void showLoading(MultipleStatusView multipleStatusView, String msg) { } @Override public void hideLoading() { } @Override public void showBusinessError(ErrorBean error) { mMultipleStateView.showError(); } // @Override public void showException(ErrorBean error) { mMultipleStateView.showNoNetwork(); } }
《三》MVC與MVP架構的理解

image.png
MVP是從更早的MVC演變而來的,我們先來簡單瞭解一下MVC框架。
(一)MVC(Model-View-Control)
Model(模型):即資料模型,負責資料持久化。
View(檢視):負責介面顯示和接收使用者的輸入操作。
Controller(控制器):負責業務邏輯處理。
Activity職責:
(1)C作為M和V之間的連線, 負責獲取輸入的業務資料, 然後將處理後的資料輸出到介面上做相應展示。因此C起到了橋樑的作用,來控制V層和M層直接的通訊以達到分離檢視和業務邏輯層。
(2)在MVC中,Activity持有了Model模型的物件,向Model模型發起資料請求。當Model模型處理資料結束後,通過Model層的介面回撥更新UI。
所以在MVC中,Activity作為Controller控制層負責業務邏輯的處理。
缺點:
由於在MVC中,控制層的重任通常落在了眾多的Activity的肩上,隨著介面操作及業務邏輯的複雜度越來越高,Activity的程式碼將會越來越臃腫,變得難以管理和維護。為了解決這個問題,MVP架構應運而生。
(二)MVP(Model-View-Presenter)
View:負責繪製UI元素、與使用者進行互動(在Android中體現為Activity)
Model:負責儲存、檢索、操縱資料(有時也實現一個Model interface用來降低耦合)
Presenter:作為View與Model互動的中間紐帶,處理與使用者互動的負責邏輯。
在MVP中,我們將Activity複雜的邏輯處理移至另外的一個類(Presenter)中時,Activity其實就是MVP模式中的View,它負責UI元素的初始化,建立UI元素與Presenter的關聯(Listener之類),同時自己也會處理一些簡單的邏輯(複雜的邏輯交由 Presenter處理)。
MVP的Presenter是框架的控制者,承擔了大量的邏輯操作,而MVC的Controller更多時候承擔一種轉發的作用。因此在App中引入MVP的原因,是為了將此前在Activty中包含的大量邏輯操作放到控制層中,避免Activity的臃腫。
兩種模式的主要區別:
① View與Model並不直接互動,而是通過與Presenter互動來與Model間接互動。而在MVC中View可以與Model直接互動(最主要的區別)
② 通常View與Presenter是一對一的,但複雜的View可能繫結多個Presenter來處理邏輯。而Controller是基於行為的,並且可以被多個View共享,Controller可以負責決定顯示哪個View
③ Presenter與View的互動是通過介面來進行的,更有利於新增單元測試。
MVP優點:
① 模型與檢視完全分離,我們可以修改檢視而不影響模型;
② 可以更高效地使用模型,因為所有的互動都發生在一個地方——Presenter內部;
③ 我們可以將一個Presenter用於多個檢視,而不需要改變Presenter的邏輯。這個特性非常的有用,因為檢視的變化總是比模型的變化頻繁;
寫在結尾:
1、關於MVC、MVP架構的介紹,更多詳細的案例分析可以參考: Android App的設計架構:MVC,MVP,MVVM與架構經驗談
2、關於MVP的優化: 高階MVP架構封裝演變全過程
3、本文Demo的Github地址: MVP_Retrofit_RxJava 。