Android:Retrofit + RxJava MVP 架構實際應用
前言
目前 MVP 架構在 Android app 開發中非常流行。通過 ofollow,noindex">谷歌官方 例子和個人的一些理解形成自己的 Retrofit + RxJava 的 MVP 架構,並且用這實際開發當中。看此文需要一定的 Retrofit 和 RxJava 基礎,對 mvp-clean 有一定了解。
MVP 簡單介紹
- M:Model,資料處理層,獲取資料、整理資料等;
- V:View,展示資料層,
Activity
、Fragment
、Dialog
等都可以充當這個角色; - P:Presenter,邏輯處理層,處理資料和 UI 的關係、處理來自 V 層邏輯等;
下圖分析:
V 層和 M 層直接關聯了嗎?並沒有,這是 MVP 和 MVC 一個很大的區別。V 層和 M 層是通過 P 層聯絡起來的。反映到程式碼中就是 P 持有 V 例項和 M 例項。

MVP
具體實現
1. 總體包目錄
- base 包:一些 base 基類,
BaseFragment
和BaseMvpFragment
和BaseActivity
、BaseMvpFragment
差不多。 - net 包:網路請求和處理相關的類在此包下。
- utils 包:需要用到的簡單工具類。

image.png
2. 關鍵類的說明
- BaseMvpActivity:封裝了公共方法的
Activity
,用了處理專案裡Activity
一般都會使用到的方法,例如配合 Presenter 統一處理記憶體洩漏問題。
public abstract class BaseMvpActivity<P extends BasePresenter<? extends IBaseView>> extends BaseActivity implements IBaseView { //主Presenter protected P mPresenter; //多個Presenter時候需要的容器 private ArraySet<BasePresenter> mPresenters = new ArraySet<>(4); @Override protected void init(@Nullable Bundle savedInstanceState) { initLoading(); mPresenter = getPresenter(); addToPresenters(mPresenter); initView(); initListener(); initData(); } @Override protected void onDestroy() { for (BasePresenter presenter : mPresenters) { presenter.detachView(); } mPresenters.clear(); super.onDestroy(); } @Override public void showLoading() { } @Override public void showLoading(String msg) { } @Override public void hideLoading() { } @Override public void showMsg(String msg) { toastS(msg); } /** * 初始化Presenter,其他多個Presenter也在該方法建立並呼叫addToPresenters加入到集合 * @return 主Presenter */ protected abstract P getPresenter(); /** * 根據具體專案需求建立loading */ protected void initLoading() { } /** * 初始化View */ protected void initView(){ } /** * 初始化Listener */ protected abstract void initListener(); /** * 初始化資料 */ protected abstract void initData(); /** * 把其他的Presenter新增到Presenters集合裡 * 這樣會自動繫結View和管理記憶體釋放 */ protected <T extends BasePresenter> void addToPresenters(T presenter) { presenter.attachView(this); mPresenters.add(presenter); } }
- BasePresenter:Presenter 基類,裡面包含 Presenter 需要公共方法,例如統一處理記憶體洩漏問題。Presenter 我堅決不把
Context
(Activity
、Fragment
之類的)傳進來,要使用Context
就用Application
的Context
。帶個Context
可能有些方便,但是我覺得很影響我的單元測試。
public abstract class BasePresenter<V extends IBaseView> { private V mView; //Disposable容器,收集Disposable,主要用於記憶體洩漏管理 private CompositeDisposable mDisposables; protected V getView() { return mView; } /** * @param view 繫結View */ @SuppressWarnings("unchecked") public <T extends IBaseView> void attachView(T view) { this.mView = (V) view; mDisposables = new CompositeDisposable(); } /** * 解綁關聯 */ public void detachView() { mDisposables.clear(); mDisposables = null; mView = null; } /** * @param disposable 新增Disposable到CompositeDisposable *通過解除disposable處理記憶體洩漏問題 */ protected boolean addDisposable(Disposable disposable) { if (isNullOrDisposed(disposable)) { return false; } return mDisposables.add(disposable); } /** * @param d 判斷d是否為空或者dispose * @return true:一次任務未開始或者已結束 */ protected boolean isNullOrDisposed(Disposable d) { return d == null || d.isDisposed(); } /** * @param d 判斷d是否dispose * @return true:一次任務還未結束 */ protected boolean isNotDisposed(Disposable d) { return d != null && !d.isDisposed(); } /** * 獲取 Model 例項 */ protected <M extends IBaseModel> M getModel(Class<M> clazz) { return ModelManager.getInstance().create(clazz); } }
- HttpManager:網路訪問管理,例如 baseUrl 配置;
public final class HttpManager { private Retrofit mRetrofit; private String mBaseUrl; private OkHttpClient mOkHttpClient; private Boolean debug = true; private static final Logger LOG = Logger.getLogger(HttpManager.class.getName()); private HttpManager() { } public static HttpManager getInstance() { return Holder.INSTANCE; } /** * @param mBaseUrl 設定BaseUrl *放在第一位設定 */ public HttpManager setBaseUrl(String mBaseUrl) { this.mBaseUrl = mBaseUrl; return Holder.INSTANCE; } /** * 設定OkHttpClient */ public HttpManager setOkHttpClient(OkHttpClient okHttpClient) { this.mOkHttpClient = okHttpClient; return Holder.INSTANCE; } /** * @param retrofit 設定retrofit *放在最後設定 */ public void setRetrofit(Retrofit retrofit) { this.mRetrofit = retrofit; } /** * debug */ public HttpManager setDebug(Boolean debug) { this.debug = debug; return Holder.INSTANCE; } /** * @return mRetrofit.create(clazz) */ public <T> T getApiService(Class<T> clazz) { return mRetrofit.create(clazz); } /** * 自帶建立retrofit */ public Retrofit createRetrofit() { Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(mBaseUrl) .client(mOkHttpClient) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(ObserveOnMainCallAdapterFactory.createMainScheduler()) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())); return builder.build(); } /** * @return OkHttpclient */ public OkHttpClient createDefaultClient() { ... } private static class Holder { private static final HttpManager INSTANCE = new HttpManager(); } /** * info 等級log */ public static class InterceptorLogInfo implements HttpLoggingInterceptor.Logger { @Override public void log(@NonNull String message) { LOG.log(Level.INFO, message); } } }
- HttpResultObserver:
DisposableSingleObserver
的子類,用來接收網路資料回撥;
public abstract class HttpResultObserver<T> extends DisposableSingleObserver<T> { @Override public void onSuccess(T t) { //dispose 一次任務 dispose(); onResult(t); } @Override public void onError(Throwable e) { //dispose 一次任務 dispose(); onFailure(e); } /** * @param t 獲取結果 */ protected abstract void onResult(T t); /** * @param e 獲取結果失敗 */ protected abstract void onFailure(Throwable e); }
- ObserveOnMainCallAdapterFactory:指定資料回撥到主執行緒。配合 Retrofit 使用的。
public final class ObserveOnMainCallAdapterFactory extends CallAdapter.Factory{ private final Scheduler mScheduler; public ObserveOnMainCallAdapterFactory(Scheduler scheduler) { this.mScheduler = scheduler; } @Nullable @Override public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) { Class<?> rawType = getRawType(returnType); if (rawType != Single.class) { return null; } final CallAdapter<Object, Single<?>> delegate = (CallAdapter<Object, Single<?>>) retrofit.nextCallAdapter(this, returnType, annotations); return new CallAdapter<Object, Object>() { @Override public Type responseType() { return delegate.responseType(); } @Override public Object adapt(@NonNull Call<Object> call) { Single<?> s = delegate.adapt(call); return s.observeOn(mScheduler); } }; } /** * 在android主執行緒處理下游資料 */ public static CallAdapter.Factory createMainScheduler() { return new ObserveOnMainCallAdapterFactory(AndroidSchedulers.mainThread()); } }
- ModelManager:建立管理 Model,這裡我把 Model 看做是資料倉庫,用來提供直觀資料(比如提供 User 資料)的。Model 是通過 ModelManager 建立的。每個 Model 只建立一次。因為 Model 相對比較獨立而且不需要頻繁建立銷燬。
public final class ModelManager { private final ConcurrentHashMap<Class<? extends IBaseModel>, ? extends IBaseModel> DATA_CACHE; private ModelManager() { DATA_CACHE = new ConcurrentHashMap<>(8); } /** * @return ModelManager單例例項 */ public static ModelManager getInstance() { return Holder.INSTANCE; } private static class Holder { private static final ModelManager INSTANCE = new ModelManager(); } /** * 建立獲取 Model 層例項 * @param clazz IBaseModel 子類 class */ @SuppressWarnings("unchecked") public <M extends IBaseModel> M create(final Class<M> clazz) { IBaseModel model = DATA_CACHE.get(clazz); if (model != null) { return (M) model; } synchronized (DATA_CACHE) { model = DATA_CACHE.get(clazz); if (model == null) { try { Constructor<M> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); model = constructor.newInstance(); } catch (... e) { .... } } } return (M) model; } }
3. 使用方式
以在一個 Activity 請求 GitHub 兩個介面為例,一個介面請求個人使用者資訊,一個介面請求公司使用者資訊,所以例子將模擬兩個模組,user 和 orgs 。使用過程中你會發現一個View 可以有多個 Presenter,一個 Activity 可以實現多個 View 介面。其實還不止這些,其實 Presenter 也可以用有多個 Model。
完整例子-
目錄結構
XxContract
為模板建立生成,可以在Setting>Editor>File and Code Templates>Files
設定。我的模板如下:
package ${PACKAGE_NAME}; #parse("File Header.java") public interface ${NAME}{ interface View extends IBaseView{ } interface Presenter{ } }

目錄結構
- 在合適的位置初始化 HttpManager
public class SimpleApplication extends Application { @Override public void onCreate() { super.onCreate(); HttpManager.getInstance() .setBaseUrl("https://api.github.com/") .setDebug(BuildConfig.DEBUG) .setOkHttpClient(HttpManager.getInstance().createDefaultClient()) .setRetrofit(HttpManager.getInstance().createRetrofit()); } }
- MainActivity: 一般主頁可能會有多個 Presenter,這裡有兩個 Presenter
public class MainActivity extends BaseMvpActivity<UserPresenter> implements UserContract.View, OrgContract.View { ... //公司資訊Presenter(次Presenter) private OrgPresenter mOrgPresenter; private ProgressDialog mLoading; @Override protected int getLayoutId() { return R.layout.activity_main; } @Override protected void initLoading() { mLoading = new ProgressDialog(this); } @Override protected UserPresenter getPresenter() { mOrgPresenter = new OrgPresenter(); addToPresenters(mOrgPresenter); return new UserPresenter(); } @Override protected void initView() { ... } @Override protected void initListener() { //點選獲取個人資訊 btnClick.setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(android.view.View v) { mPresenter.getUser("togallop"); } }); //點選獲取使用者資訊 btnClick2.setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(android.view.View v) { mOrgPresenter.getOrg("google"); } }); } @Override public void showUser(String msg) { tvMsg.setText(msg); } @Override public void showOrg(String org) { tvMsg.setText(org); } @Override public void showLoading(String msg) { mLoading.setMessage(msg); if (!mLoading.isShowing()) { mLoading.show(); } } @Override public void showLoading() { if (!mLoading.isShowing()) { mLoading.show(); } } @Override public void showMsg(String msg) { toastS(msg); } @Override public void hideLoading() { if (mLoading.isShowing()) { mLoading.dismiss(); } } }
- UserPresenter:聯結
UserContract.View
和UserMode
l,在 Presenter 裡,addDisposable(disposable)
主要作用是用來防止記憶體洩漏的,如果網路請求還沒結束,頁面已經關閉,則可能造成記憶體洩漏,通過addDisposable(disposable)
管理,可以切斷 P 和 V 的關聯,從而防止記憶體洩漏,也可以取消網路請求。
public class UserPresenter extends BasePresenter<UserContract.View> implements UserContract.Presenter { private UserModel mModel; public UserPresenter() { mModel = getModel(UserModel.class); } @Override public void getUser(String userName) { //做一些判斷 if (TextUtils.isEmpty(userName)) { getView().showMsg("使用者名稱不能為空"); return; } //顯示loading getView().showLoading("正在載入..."); Disposable disposable = mModel.getUser(userName, new HttpResultObserver<String>() { @Override protected void onResult(String s) { //結果回撥顯示 getView().showUser(s); getView().hideLoading(); } @Override protected void onFailure(Throwable e) { //獲取資料是失敗回撥處理 getView().showMsg(e.getMessage()); getView().hideLoading(); } }); addDisposable(disposable); } }
- UserMode: 就是一個使用者相關的模組,和使用者相關的請求都可以寫在這裡
public class UserModel extends BaseModel { public Disposable getUser(String userName, HttpResultObserver<String> observer) { return getApiService().getUser(userName).subscribeWith(observer); } }
- ApiService: 介面管理
public interface ApiService { @GET("users/{username}") Single<String> getUser(@Path("username") String userName); @GET("orgs/{org}") Single<String> getOrg(@Path("org") String org); }
對於有後臺有固定返回格式的的資料,也可以統一處理。比如返回結果類似這樣的:
{ code:200 msg:"success" data:{} }
那麼可以這麼處理,建立回撥泛型類
public abstract class HttpResultObserver2<T> extends HttpResultObserver<Result<T>> { @Override public void onSuccess(Result<T> t) { switch (t.code) { case 200: onSuccess(t.data); break; default: { HttpResultException e = new HttpResultException(t.code, t.msg); toast(e.getMsg()); onFailure(HttpError.RESULT_ERROR, e); e.printStackTrace(); break; } } } @Override public void onError(Throwable t) { onFailure(error, (Exception) t); } /** * 請求成功回撥 * * @param result 回撥資料 */ public abstract void onResult(T result); /** * 請求失敗回撥 * * @param error,自定義異常 * @param e失敗異常資訊 */ public abstract void onFailure(HttpError error, @NonNull Exception e); }
APIService:Single 泛型型別為 <Result<xxx>>
Single<Result<String>> uploadImage(...)
使用方式和上面介紹的一樣。
總結
MVP 架構是死的,具體實現是活的;任何離開具體業務的程式碼都是不現實的,我的這部分程式碼只是一個參考,可以根據具體需求在我的這份程式碼上擴充套件,實現自己的需求。舉些例子:要統一處理異常資訊,可以繼承 HttpResultObserver
統一處理異常;處理記憶體洩漏也可以有很多方式, RxLifecycle/Reference/LifeCycle
等;不一定要用 Single
,用 Flowable/Maybe
等都行,看具體需要,網路請求是一次性的,所以我用 Single
,處理也方便。總而言之,最好的不一定適合你,適合你的才是最好的。