一、引入
9102年了,終於準備用mvp來重構一下了
之前寫過Mvc模式下的Retrofit統一異常處理,這次用MVP重構過程中發現諸多不足之處,便重新進行修繕,使其在我看來更加優雅emmmmmm,文中不足之處,還望諸位同仁多多指點。
二、基本流程描述
-
BaseView
BaseView介面定義了可能用到的方法,特別是
addSubscribe
,用來管理RxJava生命週期。public interface BaseView { /** * 顯示吐司 * * @param msg 提示訊息 */ void showMsg(String msg); /** * 顯示載入動畫 */ void showProgress(); /** * 顯示提示 */ void showTip(@QMUITipDialog.Builder.IconType int iconType, CharSequence tipWord); /** * 關閉載入動畫 */ void hideProgress(); /** * 關閉提示 */ void hideTip(); /** * 跳轉頁面 */ void startActivitySample(Class<?> cls); /** * Rx事件管理 * * @param subscription */ void addSubscribe(Disposable subscription); } 複製程式碼
-
BasePresenter
BasePresenter方法中只定義了繫結View與解綁View的介面
public interface BasePresenter<T extends BaseView> { void attachView(T view); void detachView(); } 複製程式碼
-
BaseActivity/Fragment
這個類是封裝了一些常用方法,並且實現了BaseView的全部介面。 並且預留了兩個用於mvp模式的空方法
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initContentView(R.layout.activity_base); setContentView(getLayout()); mTopBar = (QMUITopBar) findViewById(R.id.base_topbar); ButterKnife.bind(this); mContext = this; mSwipeBackLayout = getSwipeBackLayout(); if (isStartSwipeBack()) { mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); } else { mSwipeBackLayout.setEnableGesture(false); } AppManager.addActivity(this); // 在此處呼叫繫結Presenter方法 initPresenter(); initEventAndData(); } @Override protected void onDestroy() { unSubscribe(); removePresenter(); AppManager.removeActivity(this); super.onDestroy(); } protected void initPresenter() { } protected void removePresenter() { } 複製程式碼
-
BaseMvpActivity/Fragment
實現了BaseActivity/Fragment中的預留方法
public abstract class BaseMvpActivity<T extends BasePresenter> extends BaseActivity { protected T mPresenter; @Override protected void initPresenter() { mPresenter = createPresenter(); if (mPresenter != null) { mPresenter.attachView(this); } } @Override protected void removePresenter() { if (mPresenter != null) { mPresenter.detachView(); } } /** * 建立Presenter * * @return */ protected abstract T createPresenter(); } 複製程式碼
-
SamplePresenter
SamplePresenter實現了BasePresenter中的繫結view與解綁view
-
BaseResponse
RESTful API的基類,看下程式碼很容易明白,但是需要注意下
isOk(BaseView view)
方法public class BaseResponse<T> { private int statusCode; private String message; private T data; public boolean isOk(BaseView view) { // statusCode == 1伺服器請求成功 if (statusCode == 1) { return true; } else { // 伺服器正常請求返回的錯誤 NetworkError.error(view, new ServerException(statusCode, message)); return false; } } // get/set... } 複製程式碼
-
NetworkError
根據不同的標誌統一處理異常以及伺服器返回的錯誤
public class NetworkError { public static void error(BaseView view, Throwable throwable) { RetrofitException.ResponeThrowable responeThrowable = RetrofitException.retrofitException(throwable); // 此處可以通過判斷錯誤程式碼來實現根據不同的錯誤程式碼做出相應的反應 switch (responeThrowable.code) { case RetrofitException.ERROR.UNKNOWN: case RetrofitException.ERROR.PARSE_ERROR: case RetrofitException.ERROR.NETWORD_ERROR: case RetrofitException.ERROR.HTTP_ERROR: case RetrofitException.ERROR.SSL_ERROR: view.showMsg(responeThrowable.message); break; case -1: // 跳轉到登陸頁面 view.startActivitySample(LoginActivity.class); break; default: view.showMsg(responeThrowable.message); break; } } } 複製程式碼
-
ServerException
自定義伺服器異常
public class ServerException extends RuntimeException { public int code; public ServerException(int code, String message) { super(message); this.code = code; } } 複製程式碼
-
RetrofitException
自定義網路異常,獲取錯誤原因
public class RetrofitException { private static final int UNAUTHORIZED = 401; private static final int FORBIDDEN = 403; private static final int NOT_FOUND = 404; private static final int REQUEST_TIMEOUT = 408; private static final int INTERNAL_SERVER_ERROR = 500; private static final int BAD_GATEWAY = 502; private static final int SERVICE_UNAVAILABLE = 503; private static final int GATEWAY_TIMEOUT = 504; public static ResponeThrowable retrofitException(Throwable e) { ResponeThrowable ex; if (e instanceof HttpException) { HttpException httpException = (HttpException) e; ex = new ResponeThrowable(e, ERROR.HTTP_ERROR); switch (httpException.code()) { case UNAUTHORIZED: case FORBIDDEN: case NOT_FOUND: case REQUEST_TIMEOUT: case GATEWAY_TIMEOUT: case INTERNAL_SERVER_ERROR: case BAD_GATEWAY: case SERVICE_UNAVAILABLE: default: ex.message = "網路錯誤"; break; } return ex; } else if (e instanceof ServerException) { // 伺服器下發的錯誤 ServerException resultException = (ServerException) e; ex = new ResponeThrowable(resultException, resultException.code); ex.message = resultException.getMessage(); return ex; } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { ex = new ResponeThrowable(e, ERROR.PARSE_ERROR); ex.message = "解析錯誤"; return ex; } else if (e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof UnknownHostException) { ex = new ResponeThrowable(e, ERROR.NETWORD_ERROR); ex.message = "連線失敗"; return ex; } else if (e instanceof SSLHandshakeException) { ex = new ResponeThrowable(e, ERROR.SSL_ERROR); ex.message = "證照驗證失敗"; return ex; } else { ex = new ResponeThrowable(e, ERROR.UNKNOWN); ex.message = "未知錯誤"; return ex; } } /** * 約定異常 */ class ERROR { /** * 未知錯誤 */ public static final int UNKNOWN = 1000; /** * 解析錯誤 */ public static final int PARSE_ERROR = 1001; /** * 網路錯誤 */ public static final int NETWORD_ERROR = 1002; /** * 協議出錯 */ public static final int HTTP_ERROR = 1003; /** * 證照出錯 */ public static final int SSL_ERROR = 1005; } public static class ResponeThrowable extends Exception { public int code; public String message; public ResponeThrowable(Throwable throwable, int code) { super(throwable); this.code = code; } } } 複製程式碼
-
RetrofitClient
使用單例封裝的Retrofit,這裡就不寫了,相信大家都寫過
-
ServiceApi
這個是Api介面與
10
對應,這裡用的Observable
,感覺沒必要用Flowable
,用Flowable
的話下面12
這個就不是這麼寫了public interface ServiceApi { /** * 測試介面 */ @POST("test") Observable<BaseResponse<LoginModel>> login(); } 複製程式碼
-
RetrofitSubscriber(Observer)
通過
BaseView
呼叫在BaseActivity/Fragment中實現的addSubscribe
將Disposable
新增到CompositeDisposable
中,在頁面銷燬時先中斷請求,以免造成view銷燬了還去呼叫導致空指標異常。並且根據Observer
的介面通過BaseView
來處理載入動畫(在BaseActivity/Fragment中實現)。public abstract class RetrofitSubscriber<T> implements Observer<T> { private final WeakReference<BaseView> mView; public RetrofitSubscriber(BaseView view) { super(); mView = new WeakReference<>(view); } @Override public void onSubscribe(Disposable d) { if (!NetworkUtils.isConnected()) { mView.get().showMsg("網路未連線,請檢查網路"); d.dispose(); } else { mView.get().showProgress(); mView.get().addSubscribe(d); } } @Override public void onComplete() { if (mView != null && mView.get() != null) { mView.get().hideProgress(); } } @Override public void onError(Throwable e) { if (mView != null && mView.get() != null) { mView.get().hideProgress(); } onNetError(e); } @Override public void onNext(T response) { if (response instanceof BaseResponse) { if (((BaseResponse) response).isOk(mView.get())) { onSuccess(response); } else { onServiceError(response); } } else { onOtherSuccess(response); } } /** * 請求成功並且伺服器未下發異常 * * @param response */ protected abstract void onSuccess(T response); /** * 請求成功, 返回非繼承自BaseResponse的非標準Bean或字串 * * @param response */ protected void onOtherSuccess(T response) { } /** * 請求成功,伺服器下發異常 * * @param response */ protected void onServiceError(T response) { } /** * 網路異常 * * @param e */ protected void onNetError(Throwable e) { if (mView != null && mView.get() != null) { NetworkError.error(mView.get(), e); } } } 複製程式碼
-
放一張我畫的流程圖,比較魔性
三、使用示例
|-contract
|-presenter
|-model
|-bean
|-ui
複製程式碼
-
Contract
public interface LoginContract { interface View extends BaseView { /** * 登陸成功 * @param loginModel */ void loginSuccess(LoginModel loginModel); } interface Presenter extends BasePresenter<View> { /** * 登陸 */ void login(String userName, String pwd); } } 複製程式碼
-
Activity
public class LoginActivity extends BaseMvpActivity<LoginPresenter> implements LoginContract.View { @BindView(R.id.et_login_user) ClearEditText mEtLoginUser; @BindView(R.id.et_login_password) ClearEditText mEtLoginPassword; @Override protected int getLayout() { return R.layout.activity_login; } @Override protected void initEventAndData() { initView(); } @Override protected LoginPresenter createPresenter() { return new LoginPresenter(); } private void initView() { // ... } private void toLogin() { mPresenter.login(mEtLoginUser.getText().toString(), mEtLoginPassword.getText().toString(); } @Override public void loginSuccess(LoginModel loginModel) { startActivity(new Intent(mContext, MainActivity.class)); finish(); } @OnClick({R.id.tv_login_submit}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.tv_login_submit: //上傳使用者名稱和密碼的方法 toLogin(); break; } } } 複製程式碼
-
Presenter
class LoginPresenter : SamplePresenter<LoginContract.View>(), LoginContract.Presenter { // 登入Model private val loginModel by lazy { LoginModel() } override fun login(userName: String, pwd: String) { mView.showProgress() loginModel .login() .subscribe(object : RetrofitSubscriber<BaseResponse<LoginModel>>(mView) { override fun onSuccess(response: BaseResponse<LoginModel>) { // 當前物件不為空時執行 mView?.apply { loginSuccess(response) } } }) } } 複製程式碼
-
Model
class LoginModel { fun login(): Observable<LoginBean> { return RetrofitClient .getInstance() .gService .login() .compose(RxSchedulersUtils.rxObservableSchedulerHelper()) } } 複製程式碼
三、原始碼
-
mvpretrofit是本文對應的程式碼
-
retrofit是mvc模式下對應的程式碼