Advanced RxJava and Retrofit

譯自: May 2017 Meetup: Advanced RxJava and Conductor
Slides

前言

我猜在你學習 RxJava + Retrofit 的歷程中,肯定見過這種代碼:

interface SeatGeekApi {
  @GET("events") Observable<EventResponse> getUpcomingEvents();
}
  
Retrofit mRetrofit = new Retrofit.Builder()
    .baseUrl("https://api.seatgeek.com/")
    .build();
  
SeatGeekApi mApi = mRetrofit.create(SeatGeekApi.class);
  
mApi.getUpComingEvents()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(events -> {
      // handle events
    }, error -> {
      // handle errors
    });

上面的代碼缺少了哪些東西?

  • 請求失敗時需要 retry
  • 處理特定的 響應碼
  • UI 中顯示 loading indicators
  • 對系統 configuration changes 的響應
  • 其他特殊事件

I. 重試請求 Retrying Request

網絡請求在移動設備上會因各種原因發生錯誤,通常我們需要添加一些重試請求的機制。

一種 naive 的實現:

private void makeRequest() {
  getRequestObservable()
      .subscribe(getObserver());
}
  
private Observer<Response> getObserver() {
  return new Observer<Response>() {
    ...
    @Override public void onError(Throwable e) {
      // 發生錯誤時發送新的請求
      if (someCondition) {
        makeRequest();
      }
    }
  };
}

使用 retryWhen() 實現:

RxJava 的 retryWhen() 方法可以便捷地實現這個需求:

  • retryWhen() 只會在每個 subscription 中調用一次
  • retryWhen() 返回的是 parent 數據流的 errors
  • 只要從 retryWhen() 返回的 Observable 對象不是 complete 或者 error 的,parent Observable 將會被重新訂閱。
// 簡單的演示重試 3 次請求,分別延遲 5s,10s,15s.
getRequestObservable()
  .retryWhen(attempt -> {
    attempt
        .zipWith(Observable.range(1, 3), (n, i) -> i)
        .flatMap(i -> {
          return Observable.timer(5 * i, TimeUnit.SECONDS);
        })
  })
  .subscribe(viewModel -> {
    // handle updated request state
  });

一些優化:

  • 頻繁的自動 retry 可能會導致 app DDoS 服務器。
  • 比較好的處理方式是讓用戶選擇 retry,我們可以使用 RxRelay 中的Relay/Subject。
PublishRelay<Long> retryRequest = PublishRelay.create();
    
getRequestObservable()
    .retryWhen(attempt ->retryRequest)
    .subscribe(viewModel -> {
      // handle updated request state
    });

@OnClick(R.id.retry_view)
public void onRetryClicked() {
  retryRequest.call(System.currentTimeMillis);
}

II. 處理特殊響應碼 Response Codes

使用 Response<T>

  • 利用 Observable<Response<T>> 可以使用響應的元數據 (metadata)。
  • 這種方法的優點是服務器響應碼在 400-500 之間不會產生異常。
interface SeatGeekApi {
  @GET("events") Observable<Response<EventResponse>> getUpComingEvents();
}
  
Retrofit mRetrofit = new Retrofit.Builder()
    .baseUrl("https://api.seatgeek.com/")
    .build();

mApi.getUpComingEvents()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(eventsResponse -> {
      int responseCode = eventsResponse.code();
      switch (responseCode) {
        HTTP_301:
          ...
        HTTP_403:
          ...
      }
    }, error -> {
      // ONLY handle i/o errors
    });

一些優化

  • 僅僅使用 Response<T> 處理所有的 response code
    可能會顯得比較累贅,幸運的是 Retrofit 提供了 HttpException 處理 errors
  • 可以在 onError 回調里檢查 exception 是否為 HttpException
mApi.getUpComingEvents()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(eventsResponse -> {
      // handle something with events
    }, error -> {
      if (error instanceof HttpException) {
        Response response = ((HttpException)error).response();
        switch (response.code()) {...}
      } else {
        // handle other errors
      }
    });
  • 為了增強代碼可讀性,我們還可以使用 share().
interface SeatGeekApi {
  @GET("events") Observable<Response<EventResponse>> getUpComingEvents();
}

Observable<Response<EventResponse>> eventsResponse = mApi.getUpcomingEvents()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .share();

eventsResponse
    .filter(Response::isSuccessful)
    .subscribe(this::handleSuccessfulResponse);

eventsResponse
    .filter(response -> response.code() == HTTP_403)
    .subscribe(this::handle403Response);

eventsResponse
    .filter(response -> response.code() == HTTP_304)
    .subscribe(this::handle304Response);

III. 顯示進度條 Loading Indicator

一種 naive 的實現

mApi.getUpcomingEvents()
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .doOnSubscribe(() -> loadingIndicator.show())
  .doOnUnsubscribe(() -> loadingIndicator.hide())
  .subscribe(events -> {
    // do something with events
  }, error -> {
    // handle errors
  });

上例中存在的問題:

  • UI 跟數據流深度耦合
  • 無法響應數據流的變化,僅在簡單的請求時有效

使用分割數據流 Splitting Stream

  • 嘗試新的方法,UI 更新位于另一條 stream
  • 我們可以靈活的根據 loading 狀態更新 UI.
enum RequestState {
  IDLE, LOADING, COMPLETE, ERROR
}

BehaviorRelay<RequestState> state = BehaviorRelay.create(RequestState.IDLE);

void publishRequestState(RequestState requestState) {
  Observable.just(requestState)
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(state);
}

mApi.getUpcomingEvents()
  .doOnSubscribe(() -> publishRequestState(RequestState.LOADING))
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .doOnError(t -> publishRequestState(RequestState.ERROR))
  .doOnComplete(() -> publishRequestState(RequestState.COMPLETE))
  .subscribe(events -> {
    // do something with events
  }, error -> {
    // handle errors
  });
  • 請求狀態發生變化時,在另一條 stream 修改 UI 的變化:
state.subscribe(requestState -> {
    switch (requestState) {
      IDLE:
        break;
      LOADING:
        loadingIndicator.show();
        errorView.hide();
        break;
      COMPLETE:
        loadingIndicator.hide();
        break;
      ERROR:
        loadingIndicator.hide();
        errorView.show();
        break;
    }
  });

一些優化

  • 我們可以將這種模式擴展到各種 request result
BehaviorRelay<RequestState> state = BehaviorRelay.create(RequestState.IDLE);
BehaviorRelay<EventsResponse> response = BehaviorRelay.create();
BehaviorRelay<Throwable> errors = BehaviorRelay.create();
...
void executeRequest() {
  mApi.getUpcomingEvents()
      .doOnSubscribe(() -> publishRequestState(RequestState.LOADING))
      .subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())
      .doOnError(t -> publishRequestState(RequestState.ERROR))
      .doOnComplete(() -> publishRequestState(RequestState.COMPLETE))
      .subscribe(response, errors);
  }

  • 我們同樣可以將所有分割的 streams 合并成一條:
BehaviorRelay<RequestState> state = BehaviorRelay.create(RequestState.IDLE);
BehaviorRelay<Optional<EventsResponse>> response = BehaviorRelay.create(Optional.empty());
BehaviorRelay<Optional<Throwable>> errors = BehaviorRelay.create(Optional.empty());
  
class RequestViewModel {
  public final RequestState mState;
  public final Optional<EventsResponse> mResponse;
  public final Optional<Throwable> mErrors;
  
  RequestViewModel(...) {...}
}

Observable.combineLatest(state, response, errors, RequestViewModel::new)
  .subscribe(viewModel -> {
    // handle updated request state
  });

IV. 管理 Configuration Changes

RxJava,以及像 AsyncTask 等其他異步模型默認情況下不會響應 Android 的生命周期。RxJava 提供了數據流 unsubscribe 的方法,但是問題是該如何正確的使用它。

Disposable mDisposable = Observable.combineLatest(state, response, errors, RequestViewModel::new)
  .subscribe(viewModel -> {
    // handle updated request state
  });

...

mDisposable.dispose(); // when?

一種基本的實現

創建 Disposable 時保存 Disposable 的引用,在生命周期發生回調時調用 dispose() 方法。

class MyActivity extends Activity {
  Disposable mDisposable;

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    mDisposable = Observable
        .combineLatest(state, response, errors, ViewModel::new)
        .subscribe(getSubscriber());
  }

  @Override protected void onStop() {
    super.onStop();
    mDisposable.dispose();
  }
}

CompositeDisposable

  • CompositeDisposable 非常適合 UI 訂閱了多條 stream 的場景。
  • 使用 add() 方法添加每條 stream 的 Disposable 對象。
  • CompositeDisposable 的清理方法非常適合在 MVP 框架下的 unbind() 方法使用。
class MyActivity extends Activity {
  CompositeDisposable mDisposables = new CompositeDisposable();

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...    
    mDisposables.add(state.subscribe(getStateSubscriber()));
    mDisposables.add(state.subscribe(getStateSubscriber()));
    mDisposables.add(state.subscribe(getStateSubscriber()));
  }

  @Override protected void onStop() {
    super.onStop();
    mDisposables.clear();
  }
}

推薦的方式—— RxLifecycle

  • RxLifecycle 提供了便捷的方式將我們的 Disposable 與 Activity/Fragment 的生命周期綁定。
  • 當相應的生命周期回調發生時,Disposable 將會調用 dispose() 方法。
class MyActivity extends RxAppCompatActivity {
  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    Observable.combineLatest(state, response, errors, ViewModel::new)
        .compose(bindToLifecycle())
        .subscribe(viewModel -> {
          // handle updated request state
        });

RxLifecycle + MVP

public class RxPresenter<T extends MvpView> extends Presenter<T> {

  BehaviorRelay<PresenterLifecycle> mPresenterLifecycle =
      BehaviorRelay.create(STOPPED);

  @Override public void attach(T mvpView) {
    mPresenterLifecycle.call(STARTED);
  }

  @Override public void detach() {
    mPresenterLifecycle.call(STOPPED);
  }

  protected <T>LifecycleTransformer<T> bindToLifecycle() {
    return RxLifecycle.bind(presenterLifecycleObservable, lifecycle -> {
      switch (lifecycle) {
        case STARTED:
          return STOPPED;
        case STOPPED:
        default:
          return STARTED;
      }
    });
  }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,869評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,086評論 25 708
  • 帝都中元節,午后驟雨,至天際皓月,轉為細雨綿綿,一時落河岸邊,人人持傘,手捧蓮花燈,為已故親人祈福。 盼兒侍奉父親...
    陌上花開mshk閱讀 773評論 0 9
  • 知女范微信公眾平臺:zhinvfan 自從《太陽的后裔》播出爆紅后, “撩妹”一夜成為網絡熱詞。 悲哀的是,真不是...
    愛美文78閱讀 327評論 0 0
  • 亙古至今 人人都渴望擁有知己 懂得自己欣賞自己 理解乃至包容自己…… 古有管鮑君子之交 亦有伯牙為子期病故而...
    娑婆如斯閱讀 930評論 0 104