当前位置: 首页 > 工具软件 > RIBs > 使用案例 >

Uber RIBs框架源码分析

季博
2023-12-01

Uber最近开源了他们的移动端框架RIBs,RIBs是一个跨平台框架,支持着很多Uber的移动应用。RIBs这个名字,取自Router、Interactor、Builder的缩写。

早在2016年,Uber就在Engineering the Architecture Behind Uber’s New Rider App一文中介绍了他们重构Uber app所采用的架构和技术,从源码我们能看出,RIBs就是VIPER模式的一个实现,并在VIPER的基础上做了不少改进。

阅读本文前需要了解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模式有的组件。

我们可以很容易地用他们提供的插件生成初始代码,下图是用IntellJ插件生成的模板代码示例。

Router

RIBs的路由,和别的VIPER设计相同的是,都用于页面的跳转。

不同的是: 1.RIBs的Router维护了一个子模块的Router列表,同时负责把子模块的View添加到视图树上。 2.Router不和Presenter通信,而是和Interactor通信,从上面的架构图能看出来。

Router类依赖Interactor,架构图里的Interactor会调用Router,来实现跳转。而Router也会调用Interactor,但场景不多,有以下两个:

1.handleBackPress,处理实体键的回退事件 2.向子模块传递savedInstanceState

Interactor

RIBs的交互器用于获取数据,从服务器或者从数据库中,和别的VIPER大同小异。它依赖Presenter和Router,从架构图中也能看出,Interactor会把数据Model传给Presenter,Presenter再跟View交互,显示到View上。而Presenter会处理View的点击调用,调用Interactor获取数据或处理逻辑。

Builder

RIBs的Builder是VIPER设计模式里没有的东西,用于初始化Interactor、Router等组件,并且定义依赖关系。

可以看出,Builder依赖View、Router,在build方法中创建Interactor。各组件如何组合起来,如何初始化一直是个问题,这部分代码写在Activity里明显会造成冗余。在View、Router、Interactor其中一个里负责创建也不符合它们的职责,用一个Builder类来负责创建符合逻辑。

View和Presenter

这两部分的设计也很有意思。一般在MVP里,我们会把Activity当做View,会有一个IView的接口,以及一个IPresenter的接口。如果按照面向接口的原则,VIPER框架可能有4个接口,如下图所示:

这同时也带来一个接口过多的问题,造成接口方法冗余,例如Interactor调用Presenter,Presenter接着调用View,这三个接口内会有三个表达含义相似的方法,如Interactor内requestLogin(),Presenter里updateLoginStatus(),View里会有一个showLoginSuccess()。尽管是不同职责,未免过于累赘。

RIBs的Router、Interactor、View都无需定义接口,直接继承基类。Presenter是唯一需要定义的接口,在Interactor内定义Presenter接口,View实现Presenter接口,然后通过Rxbinding绑定控件,Presenter单向调用View。

几个主要问题的解决

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的通信图示

我们着重看一下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也提供了很多基础库以及插件供开发者提高效率,改天有时间再详细分析一下它提供的插件以及基础库。

 类似资料: