安卓MVP模式下的Rxjava+Retrofit統一異常處理與生命週期管理

張欽發表於2019-03-25

一、引入

9102年了,終於準備用mvp來重構一下了

之前寫過Mvc模式下的Retrofit統一異常處理,這次用MVP重構過程中發現諸多不足之處,便重新進行修繕,使其在我看來更加優雅emmmmmm,文中不足之處,還望諸位同仁多多指點。

二、基本流程描述

  1. 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);
    
    }
    複製程式碼
  2. BasePresenter

    BasePresenter方法中只定義了繫結View與解綁View的介面

    public interface BasePresenter<T extends BaseView> {
    
    	void attachView(T view);
    
    	void detachView();
    }
    複製程式碼
  3. 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() {
    
    }
    複製程式碼
  4. 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();
    
    }
    複製程式碼
  5. SamplePresenter

    SamplePresenter實現了BasePresenter中的繫結view與解綁view

  6. 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...
    }
    複製程式碼
  7. 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;
    		}
    	}
    }
    複製程式碼
  8. ServerException

    自定義伺服器異常

    public class ServerException extends RuntimeException {
    
    	public int code;
    
    	public ServerException(int code, String message) {
    		super(message);
    		this.code = code;
    	}
    }
    複製程式碼
  9. 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;
    		}
    	}
    }
    複製程式碼
  10. RetrofitClient

    使用單例封裝的Retrofit,這裡就不寫了,相信大家都寫過

  11. ServiceApi

    這個是Api介面與10對應,這裡用的Observable,感覺沒必要用Flowable,用Flowable的話下面12這個就不是這麼寫了

    public interface ServiceApi {
    
    	/**
    	* 測試介面
    	*/
    	@POST("test")
    	Observable<BaseResponse<LoginModel>> login();
    }
    複製程式碼
  12. RetrofitSubscriber(Observer)

    通過BaseView呼叫在BaseActivity/Fragment中實現的addSubscribeDisposable新增到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);
    		}
    	}
    }
    複製程式碼
  13. 放一張我畫的流程圖,比較魔性

    安卓MVP模式下的Rxjava+Retrofit統一異常處理與生命週期管理

三、使用示例

|-contract
|-presenter
|-model
    |-bean
|-ui
複製程式碼
  1. 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);
    	}
    }
    複製程式碼
  2. 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;
    		}
    	}
    
    }
    複製程式碼
  3. 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) }
    					}
    				})
    	}
    }
    複製程式碼
  4. Model

    class LoginModel {
    
    	fun login(): Observable<LoginBean> {
    		return RetrofitClient
    				.getInstance()
    				.gService
    				.login()
    				.compose(RxSchedulersUtils.rxObservableSchedulerHelper())
    	}
    }
    複製程式碼

三、原始碼

  1. mvpretrofit是本文對應的程式碼

  2. retrofit是mvc模式下對應的程式碼

    github.com/sdwfqin/And…

相關文章