Uber RIBs框架源碼分析

Uber最近開源了他們的移動端框架RIBs,RIBs是一個跨平臺框架,支持着不少Uber的移動應用。RIBs這個名字,取自Router、Interactor、Builder的縮寫。git

早在2016年,Uber就在Engineering the Architecture Behind Uber’s New Rider App一文中介紹了他們重構Uber app所採用的架構和技術,從源碼咱們能看出,RIBs就是VIPER模式的一個實現,並在VIPER的基礎上作了很多改進。github

閱讀本文前須要瞭解VIPER模式,如以前不瞭解,可谷歌一下。數據庫

文章構成

文章將會分紅三部分,第一部分介紹RIBs框架的基本組成。第二部分闡述框架須要解決的問題,以及RIBs怎麼解決這些問題。第三部簡述RIBs的特色。設計模式

1.RIBs的基本構成
2.主要問題的解決
  • RIBs如何處理生命週期
  • RIBs如何解決Android生命週期引發的RxJava內存泄漏
  • 組件間如何通訊
  • 如何處理組件間的解耦
3.RIBs的特色
  • Router樹
  • 單Activity應用
  • 易於單元測試

RIBs的基本構成

RIBs的組件主要由Router、Interactor、Builder、Presenter、View組成,按Uber的設計,Presenter和View不是必須的,應對UI無關的業務場景。除了Builder,其它幾個都是VIPER模式有的組件。bash

image

咱們能夠很容易地用他們提供的插件生成初始代碼,下圖是用IntellJ插件生成的模板代碼示例。服務器

image

Router

RIBs的路由,和別的VIPER設計相同的是,都用於頁面的跳轉。架構

不一樣的是: 1.RIBs的Router維護了一個子模塊的Router列表,同時負責把子模塊的View添加到視圖樹上。 2.Router不和Presenter通訊,而是和Interactor通訊,從上面的架構圖能看出來。app

image

Router類依賴Interactor,架構圖裏的Interactor會調用Router,來實現跳轉。而Router也會調用Interactor,但場景很少,有如下兩個:框架

1.handleBackPress,處理實體鍵的回退事件 2.向子模塊傳遞savedInstanceStateide

Interactor

RIBs的交互器用於獲取數據,從服務器或者從數據庫中,和別的VIPER大同小異。它依賴Presenter和Router,從架構圖中也能看出,Interactor會把數據Model傳給Presenter,Presenter再跟View交互,顯示到View上。而Presenter會處理View的點擊調用,調用Interactor獲取數據或處理邏輯。

image

Builder

RIBs的Builder是VIPER設計模式裏沒有的東西,用於初始化Interactor、Router等組件,而且定義依賴關係。

image

能夠看出,Builder依賴View、Router,在build方法中建立Interactor。各組件如何組合起來,如何初始化一直是個問題,這部分代碼寫在Activity裏明顯會形成冗餘。在View、Router、Interactor其中一個裏負責建立也不符合它們的職責,用一個Builder類來負責建立符合邏輯。

View和Presenter

這兩部分的設計也頗有意思。通常在MVP裏,咱們會把Activity當作View,會有一個IView的接口,以及一個IPresenter的接口。若是按照面向接口的原則,VIPER框架可能有4個接口,以下圖所示:

image

這同時也帶來一個接口過多的問題,形成接口方法冗餘,例如Interactor調用Presenter,Presenter接着調用View,這三個接口內會有三個表達含義類似的方法,如Interactor內requestLogin(),Presenter裏updateLoginStatus(),View裏會有一個showLoginSuccess()。儘管是不一樣職責,未免過於累贅。

RIBs的Router、Interactor、View都無需定義接口,直接繼承基類。Presenter是惟一須要定義的接口,在Interactor內定義Presenter接口,View實現Presenter接口,而後經過Rxbinding綁定控件,Presenter單向調用View。

image

幾個主要問題的解決

1.RIBs如何處理生命週期

若是採用MVP模式,咱們須要在Presenter裏有各類生命週期的方法,若是採用MVVM,咱們須要在ViewModel裏面處理生命週期。VIPER則須要在Interactor裏處理生命週期。簡單來講,就是把Activity或者Fragment的生命週期回調,映射到Interactor裏的相關方法。

有不少方法能達到這個目的,最原始的一種,是在Activity裏依賴Interactor,在每個生命週期方法內,調用Interactor的相關方法。

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    interactor.onCreate();
  }

  @Override
  protected void onResume() {
    super.onResume();
    interactor.onResume();

  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    interactor.onDestroy();
  }
複製代碼

另外一種方法是使用Google提供的LifeCycle組件,在Interactor基類裏註解方法,而後經過getLifecycle().addObserver(Interactor)添加監聽。

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    @CallSuper
    public void onCreate() {
        mCompositeDisposable = new CompositeDisposable();
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    @CallSuper
    public void onStart() {
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    @CallSuper
    public void onResume() {

    }
複製代碼

Uber採用的是第一種,在RibActivity基類裏獲取到router,在生命週期回調裏dispatch到各個組件。

2.RIBs如何解決Android生命週期引發的RxJava內存泄漏

另外一個跟生命週期息息相關的問題,就是如何解決RxJava可能會致使的內存泄漏問題。

通常咱們會用RxLifecycle這個庫,RxLifecycle須要咱們拿到RxActivity的引用,但在Interactor裏引用Activity不是好的實踐。沒有Android的Context引用的話,咱們能夠把Interactor當作一個純Java類進行單元測試,效率會比較高。另外RxLifecycle的做者也在Why Not RxLifecycle?一文中闡述了RxLifecycle存在的問題,並建議咱們不要使用。

一個簡潔清晰的處理是用CompositeDisposable把RxJava請求存起來,在Interactor生命週期結束時統一釋放。

Uber的工程師可能以爲這麼作不優雅,開發了一個AutoDispose來處理這個問題。

//AutoDispose庫的使用
myObservable
    .doStuff()
    .as(autoDisposable(this))   // 一行代碼解決內存溢出問題
    .subscribe(s -> ...);
複製代碼

AutoDispose庫的原理和RxLifecycle大同小異,但在RxLifecycle的基礎上作了改進,例如它不須要傳遞一個RxActivity上下文,取而代之的是一個LifecycleScopeProvider接口。下面是Interactor裏的相關代碼,這段邏輯其實就是AutoDispose庫的使用,很少作解釋了。

public abstract class Interactor<P, R extends Router>
    implements LifecycleScopeProvider<InteractorEvent> {

  private static final Function<InteractorEvent, InteractorEvent> LIFECYCLE_MAP_FUNCTION =
      new Function<InteractorEvent, InteractorEvent>() {
        @Override
        public InteractorEvent apply(InteractorEvent interactorEvent) {
          switch (interactorEvent) {
            case ACTIVE:
              return INACTIVE;
            default:
              throw new LifecycleEndedException();
          }
        }
      };

  private final BehaviorRelay<InteractorEvent> behaviorRelay = BehaviorRelay.create();
  private final Relay<InteractorEvent> lifecycleRelay = behaviorRelay.toSerialized();

  /** @return an observable of this controller's lifecycle events. */ @Override public Observable<InteractorEvent> lifecycle() { return lifecycleRelay.hide(); } @Override public Function<InteractorEvent, InteractorEvent> correspondingEvents() { return LIFECYCLE_MAP_FUNCTION; } @Override public InteractorEvent peekLifecycle() { return behaviorRelay.getValue(); } 複製代碼

3.組件間如何通訊

通常不管MVVM模式仍是VIPER模式,咱們都須要處理父組件與子組件的通訊問題,子組件間的平行調用問題。

一樣有不少種方法能夠解決,RIBs的通訊圖示

riblet_comms.png
咱們着重看一下Interactor的調用,從圖中看出,父子組件的通訊是經過接口以及Observable streams的方式。

/**
   * 在子組件定義接口
   */
  interface LoggedOutPresenter {

    Observable<Pair<String, String>> playerNames();
  }

/**
   * 在父組件實現接口,並注入到子組件中供子組件調用
   */
class LoggedOutListener implements LoggedOutInteractor.Listener {

    @Override
    public void requestLogin(UserName playerOne, UserName playerTwo) {
      // Switch to logged in. Let’s just ignore userName for now.
      getRouter().detachLoggedOut();
      getRouter().attachLoggedIn(playerOne, playerTwo);
    }
  }
複製代碼

對於父組件調用子組件,Uber更推薦Observable streams的方式,父組件將observable data stream暴露給子組件的Interactor,當數據變化時,子組件作出響應。

4.如何處理組件間的解耦

RIBs在Builder處理View、Router、Interactor的依賴問題。如下是教學代碼的一個例子

@dagger.Module
  public abstract static class Module {
    //提供子組件跟父組件通訊的接口實例
    @RootScope
    @Provides
    static LoggedOutInteractor.Listener loggedOutListener(RootInteractor rootInteractor) {
      return rootInteractor.new LoggedOutListener();
    }

    //提供Presenter實例
    @RootScope
    @Binds
    abstract RootInteractor.RootPresenter presenter(RootView view);

     //提供Router實例
    @RootScope
    @Provides
    static RootRouter router(Component component, RootView view, RootInteractor interactor) {
      return new RootRouter(
          view,
          interactor,
          component,
          new LoggedOutBuilder(component),
          new LoggedInBuilder(component));
    }
  }

  @RootScope
  @dagger.Component(modules = Module.class, dependencies = ParentComponent.class)
  interface Component extends
      InteractorBaseComponent<RootInteractor>,
      LoggedOutBuilder.ParentComponent,
      LoggedInBuilder.ParentComponent,
      BuilderComponent {

    @dagger.Component.Builder
    interface Builder {

      @BindsInstance
      Builder interactor(RootInteractor interactor);

      @BindsInstance
      Builder view(RootView view);

      Builder parentComponent(ParentComponent component);

      Component build();
    }
  }

  interface BuilderComponent {

    RootRouter rootRouter();
  }

  @Scope
  @Retention(CLASS)
  @interface RootScope {

  }
複製代碼

Builder出了用於初始化各個組件外,還負責依賴注入,子Interactor的接口實例就是在Builder生成的。

3.RIBs的特色

  • 業務邏輯驅動app,而不是View驅動
  • 整個應用只有一個Activity
  • 易於單元測試

RIBs的Router基類裏維護了一個保存子Router的List,因爲維護了Router樹,在根Router裏咱們能一層層往下找到任何一個子組件的Router。也是由於有了Router樹,單Activity成爲可能。

private final List<Router> children = new CopyOnWriteArrayList<>();

  //dispatch 子組件
  protected void dispatchDetach() {
    checkForMainThread();

    getInteractor().dispatchDetach();
    willDetach();

    for (Router child : children) {
      detachChild(child);
    }
  }
複製代碼

RIBs文檔中解釋了單Activity的緣由,多Acitivity會致使在全局中有更多的狀態,代碼會不穩健。具體的情景可能還得探討,Android使用Activity做爲頁面確實會致使一些問題,Activity並不像Router樹有着清晰的層次和邏輯結構。

It contains a single RootActivity and a RootRib. All future code will be written nested under RootRib. RIB apps should avoid containing more than one activity since using multiple activities forces more state to exist inside a global scope. This reduces your ability to depend on invariants and increases the chances you'll accidentally break other code when making changes.

至於單元測試,因爲RIBs各組件的職責很是清晰,對Router和Interactor進行單元測試全覆蓋是很是容易的事。

總結

RIBs框架的代碼量很是小,類很少,是一個短小精悍的框架。做爲VIPER模式的一個具體實現,從設計上能看出Uber的工程師深思熟慮,而且用符合邏輯的方式去解決了開發中遇到的問題。寫一個VIPER框架並不難,用優美的方式去解決問題纔是難點,Uber的工程師在這方面作得很是好。同時RIBs也提供了不少基礎庫以及插件供開發者提升效率,改天有時間再詳細分析一下它提供的插件以及基礎庫。

相關文章
相關標籤/搜索