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也提供了不少基礎庫以及插件供開發者提升效率,改天有時間再詳細分析一下它提供的插件以及基礎庫。