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的特色。设计模式
RIBs的组件主要由Router、Interactor、Builder、Presenter、View组成,按Uber的设计,Presenter和View不是必须的,应对UI无关的业务场景。除了Builder,其它几个都是VIPER模式有的组件。bash
咱们能够很容易地用他们提供的插件生成初始代码,下图是用IntellJ插件生成的模板代码示例。服务器
RIBs的路由,和别的VIPER设计相同的是,都用于页面的跳转。架构
不一样的是: 1.RIBs的Router维护了一个子模块的Router列表,同时负责把子模块的View添加到视图树上。 2.Router不和Presenter通讯,而是和Interactor通讯,从上面的架构图能看出来。app
Router类依赖Interactor,架构图里的Interactor会调用Router,来实现跳转。而Router也会调用Interactor,但场景很少,有如下两个:框架
1.handleBackPress,处理实体键的回退事件 2.向子模块传递savedInstanceStateide
RIBs的交互器用于获取数据,从服务器或者从数据库中,和别的VIPER大同小异。它依赖Presenter和Router,从架构图中也能看出,Interactor会把数据Model传给Presenter,Presenter再跟View交互,显示到View上。而Presenter会处理View的点击调用,调用Interactor获取数据或处理逻辑。
RIBs的Builder是VIPER设计模式里没有的东西,用于初始化Interactor、Router等组件,而且定义依赖关系。
能够看出,Builder依赖View、Router,在build方法中建立Interactor。各组件如何组合起来,如何初始化一直是个问题,这部分代码写在Activity里明显会形成冗余。在View、Router、Interactor其中一个里负责建立也不符合它们的职责,用一个Builder类来负责建立符合逻辑。
这两部分的设计也颇有意思。通常在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。
若是采用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到各个组件。
另外一个跟生命周期息息相关的问题,就是如何解决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(); } 复制代码
通常不管MVVM模式仍是VIPER模式,咱们都须要处理父组件与子组件的通讯问题,子组件间的平行调用问题。
一样有不少种方法能够解决,RIBs的通讯图示
/**
* 在子组件定义接口
*/
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,当数据变化时,子组件作出响应。
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生成的。
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也提供了不少基础库以及插件供开发者提升效率,改天有时间再详细分析一下它提供的插件以及基础库。