復(fù)雜的repository可以用單個(gè)Java表達(dá)式編譯出來(lái)。
Java表達(dá)式由下面幾部分構(gòu)成,順序如下:
// 聲明RepositoryCompiler,并初始化,返回REventSource實(shí)例;
1. `Repositories.repositoryWithInitialValue(...)`;
// 指定事件源(Observable),可以多個(gè),返回RFrequency實(shí)例;
2. Event sources - `.observe(...)`;
// 設(shè)置通知頻率(比如click頻率限制),返回RFlow實(shí)例;
3. Frequency of reaction - `.onUpdatesPer(...)` or `.onUpdatesPerLoop()`;
// 設(shè)置數(shù)據(jù)源(Supplier),返回RFlow或RTermination實(shí)例;
4. Data processing flow - `.getFrom(...)`, `.mergeIn(...)`, `.transform(...)`, etc.;
// 其他配置,返回RConfig;
5. Miscellaneous configurations - `.notifyIf(...)`, `.onDeactivation(...)`, etc.;
// 編譯成Repository實(shí)例。
6. `.compile()`.
當(dāng)compiled repository運(yùn)行時(shí), 先將指定當(dāng)事件源注冊(cè)內(nèi)部的updatable,同時(shí)啟動(dòng)數(shù)據(jù)處理流程來(lái)計(jì)算為首次準(zhǔn)備的值。 當(dāng)從事件源收到事件的時(shí)候,這個(gè)數(shù)據(jù)處理流程會(huì)再次啟動(dòng)來(lái)更新值以響應(yīng)。
在第一次計(jì)算結(jié)果完成之前,repository將使用初始值(repositoryWithInitialValue()方法設(shè)置的初始值)作為數(shù)據(jù)。 當(dāng)數(shù)據(jù)一旦有更新時(shí),repository的觀察者們都會(huì)收到通知。當(dāng)不活動(dòng)時(shí),事件源將注銷內(nèi)部的updatable,數(shù)據(jù)處理流程不再運(yùn)行,所以數(shù)據(jù)會(huì)變成舊的。重新活動(dòng)后,數(shù)據(jù)會(huì)重新保持最新的。
The different stages of this expression are represented by the “compiler state interfaces” nested in
RepositoryCompilerStates
,
表達(dá)式的不同階段都返回RepositoryCompilerStates
中內(nèi)嵌的接口(compiler state interfaces)對(duì)象,這樣可以每個(gè)階段只暴露合適的方法, 引導(dǎo)開發(fā)者完成正確的表達(dá)式(使用IDE自動(dòng)完成時(shí))。
這些方法的完整文檔可以在這些接口中找到;特別的部分:
RFrequency
和 REventSource
RFlow
和 RSyncFlow
RConfig
repository編譯表達(dá)式不能從中間斷開,企圖將中間結(jié)果存到本地變量或者強(qiáng)制轉(zhuǎn)換(cast)為另一個(gè)接口,這些方法都不支持。
編譯repository會(huì)導(dǎo)致一些開銷, 但之后的操作相當(dāng)輕量級(jí)。repository最適合與高級(jí)組件的生命周期進(jìn)行綁定,例如Activity、可重用的視圖或者一個(gè)為整個(gè)應(yīng)用服務(wù)的全局單例。
由于編譯的開銷,編譯repository確實(shí)如此(還是會(huì)在運(yùn)行時(shí)發(fā)生編譯情況)。
compiled repository表達(dá)式清晰的說(shuō)明了什么時(shí)候(when)響應(yīng)事件, 在什么線程(where)上響應(yīng)事件, 提供什么數(shù)據(jù)(what)。 compiled repository指定的事件源和發(fā)生頻率,這兩個(gè)就是repository什么時(shí)候(when)響應(yīng)事件的概念。
數(shù)據(jù)處理流程指定原數(shù)據(jù)(依賴)和計(jì)算出repository的數(shù)據(jù),這就是repository提供什么數(shù)據(jù)(what)的概念。
由于內(nèi)部使用了updatable, 必須在有Looper線程上被注冊(cè)到事件源, compiled repository關(guān)聯(lián) worker Looper (下一篇詳細(xì)介紹 Asynchronous programming)。
數(shù)據(jù)處理過(guò)程中,指令可以轉(zhuǎn)交給Java執(zhí)行器執(zhí)行,這些明確的線程結(jié)構(gòu)就是repository(where)的概念。
整個(gè)數(shù)據(jù)處理流程由指令組成,每一個(gè)指令接受一個(gè)輸入值,并為下一條產(chǎn)生一個(gè)輸出值。 第一條指令的輸入值類型是repository的數(shù)據(jù)類型,最后一條指令也是輸出相同類型的數(shù)據(jù)。 compiler狀態(tài)接口使用泛型參數(shù)有助于保證類型安全,輸入?yún)?shù)類型逆變性(Contravariance,下一條指令可以接受前一條產(chǎn)生的數(shù)據(jù)類型的父類) 和輸出值類型協(xié)變性(Covariance,最后一條指令可以產(chǎn)生repository的數(shù)據(jù)類型的子類)
// 1. 協(xié)變性 (Covariance): List<ASub> 是一個(gè) List<A>,反之不成立
// 2. 逆變性 (Contravariance): List<A> 是一個(gè) List<ASub>,反之不成立
// 3. 不變性 (Invariance):List<ASub> 不是一個(gè) List<A>,List<A> 也不是一個(gè) List<ASub>
當(dāng)數(shù)據(jù)處理流程開始運(yùn)行,
通過(guò)Repository.get()
拉取的數(shù)據(jù)作為第一條指令的輸入值。如果之前還沒(méi)有更新過(guò)數(shù)據(jù),拉取的數(shù)據(jù)可能是初始值或者使用RepositoryConfig.RESET_TO_INITIAL_VALUE
重置的值。
這些指令順序的執(zhí)行轉(zhuǎn)換輸入值。
數(shù)據(jù)處理流程通常在運(yùn)行完_then_指令后結(jié)束并產(chǎn)生一個(gè)終值或者使用終止語(yǔ)句(termination clause)(RTermination
接口,
詳情在下面“Attempts and Result”中)結(jié)束流程并返回一個(gè)值,這種情況下repository數(shù)據(jù)值會(huì)更新并發(fā)送通知。
使用.thenSkip()
指令或者終止語(yǔ)句(termination clause)也可以終止流程,跳過(guò)剩余的流程,這種情況下repository跳過(guò)數(shù)據(jù)更新、跳過(guò)發(fā)送通知。
為了允許數(shù)據(jù)處理流程調(diào)用客戶代碼邏輯,Agera定制了下列只帶有一個(gè)方法的接口:
Supplier.get()
: a 0-input, 1-output operator;Function.apply(TFrom)
: a 1-input, 1-output operator;Merger.merge(TFirst, TSecond)
: a 2-input, 1-output operator.使用它們的指令是:
.getFrom(Supplier)
and variants;.transform(Function)
and variants;.mergeIn(Supplier, Merger)
and variants;http://wiki.jikexueyuan.com/project/agera-wiki-cn/images/operators.png" alt="Operators" />
對(duì)于高級(jí)功能,數(shù)據(jù)處理流程提供了非線性操作符(數(shù)據(jù)從"側(cè)邊"流出,或者流程終止),通過(guò)下列接口支持
Receiver.accept(T)
: a 1-input, 0-output operator;Binder.bind(TFirst, TSecond)
: a 2-input, 0-output operator;Predicate.apply(T)
: an operator that checks the input value for a yes-or-no answer.使用它們的指令是:
.sendTo(Receiver)
and variants;.bindWith(Supplier, Binder)
and variants;.check(Predicate).or…
and variants;http://wiki.jikexueyuan.com/project/agera-wiki-cn/images/sideoperators.png" alt="Side operators" />
為了幫助構(gòu)建模塊化的結(jié)構(gòu), Repository
實(shí)現(xiàn)了 Supplier
接口, MutableRepository
實(shí)現(xiàn)了 Supplier
和 Receiver
接口, 在復(fù)雜的repository中,它們可以直接當(dāng)作操作符使用。
Result
這些函數(shù)式接口Supplier
, Function
and Merger
都是不拋任何異常的, 但實(shí)踐中,許多操作可能會(huì)失敗。
為了捕獲異常,Agera 提供了一個(gè)封裝類 Result
,它可以封裝易失敗操作的結(jié)果(成功或者失敗)或者_(dá)attempt_的結(jié)果值。
_attempt_可以作為Supplier
, Function
或 Merger
的實(shí)現(xiàn),返回Result
。
數(shù)據(jù)處理流程提供能感知錯(cuò)誤的指令,這樣就可以在錯(cuò)誤的情況下終止流程:
.attemptGetFrom(Supplier).or…
;.attemptTransform(Function).or…
;.attemptMergeIn(Supplier, Merger).or…
,其中 .or…
(和.check
指令的第二部分相同)是終止語(yǔ)句,使用RTermination
狀態(tài)接口實(shí)現(xiàn), 如前文所述, 可以跳過(guò)更新 (.orSkip()
) 或 用計(jì)算的新值來(lái)結(jié)束數(shù)據(jù)流程(.orEnd(Function)
)。
所有.attempt*
指令保證下一條指令只接收成功的結(jié)果,使用輸出Result<T>
的操作符,輸出一個(gè)能感知錯(cuò)誤的結(jié)果T
,而不是Result<T>
。
mRepository = Repositories.repositoryWithInitialValue(Result.<Integer>absent())
.observe(mObservable)
.onUpdatesPerLoop()
// attemptGetFrom 輸出 Result<Integer>
.attemptGetFrom(new Supplier<Result<Integer>>() {
@NonNull
@Override
public Result<Integer> get() {
return Result.success(100);
}
})
.orSkip()
// 輸入 Integer
.thenTransform(new Function<Integer, Result<Integer>>() {
@NonNull
@Override
public Result<Integer> apply(@NonNull Integer input) {
return Result.success(input);
}
})
.compile();
相反, 操作符也可以是恢復(fù)操作, 這意味著它需要Result
作為輸入, 或者是一個(gè)_attempt recovery_操作符,這意味著它輸入和輸出都是Result
。
要把這樣的操作符放在數(shù)據(jù)處理流程中, 前面的指令必須不是感知錯(cuò)誤的(就算使用.attempt*
操作符),所以恢復(fù)操作符可以接受成功和失敗的結(jié)果(Result
類型)。
repository必須在有Looper的線程編譯(通常是主線程),這個(gè)Looper成為repository的[[worker looper|Observables-and-updatables#threading]], 所有這些過(guò)程都運(yùn)行在這個(gè)Looper的線程上:
數(shù)據(jù)處理流程不要求在Looper線程上完全同步執(zhí)行。專門的指令 .goTo(Executor)
和 .goLazy()
會(huì)開啟異步執(zhí)行。
它們不改變輸入值;它們?cè)谶\(yùn)行時(shí)控制流程的持續(xù):.goTo(Executor)
將剩下的執(zhí)行指令發(fā)送到該Executor,
而 .goLazy()
暫停執(zhí)行,直到需要新的數(shù)據(jù)的時(shí)候調(diào)用 Repository.get()
將繼續(xù)執(zhí)行剩下的執(zhí)行。
在.goTo(Executor)
指令之后, 線程將釋放用于處理其他事件,repository可能同時(shí)發(fā)生被設(shè)為不活動(dòng)狀態(tài)和有更新事件。
在后面這種情況下,數(shù)據(jù)處理流程會(huì)重新運(yùn)行,為了減少競(jìng)爭(zhēng)條件,不用啟動(dòng)一個(gè)并行線程,會(huì)運(yùn)行在同一個(gè)線程中。
repository在不活動(dòng)和并發(fā)更新是,可以取消正在執(zhí)行的任務(wù)(ps:就是goTo()上執(zhí)行的命令),這樣可以減少資源浪費(fèi)(repository不活動(dòng)的情況下),
也可以更快的重新運(yùn)行(并發(fā)更新的情況下,ps:因?yàn)橹魂P(guān)心最新的數(shù)據(jù)嘛)。取消流程可以抑制更新repository數(shù)據(jù)和更新通知。 取消任務(wù)可以通過(guò).onDeactivation(int)
and .onConcurrentUpdate(int)
方法來(lái)設(shè)置, 定義在RConfig
狀態(tài)接口中。
在.goLazy()
指令之后, 注冊(cè)的觀察者在任何情況下都會(huì)收到更新通知,但是是否真的更新數(shù)據(jù)取決于剩余的指令。
調(diào)用Repository.get()
后會(huì)恢復(fù)同步執(zhí)行,因?yàn)檫@個(gè)方法必須返回值,所有的取消都會(huì)忽略。
此外,如果Repository.get()調(diào)用之前,repository收到了更新通知,立即重啟流程,暫停狀態(tài)和保存的中間結(jié)果都會(huì)丟掉,剩余的指令也不會(huì)再運(yùn)行。
調(diào)用Repository.get()
后恢復(fù)指令執(zhí)行到達(dá).goLazy()
之前,repository會(huì)返回之前的數(shù)據(jù)。.goLazy()
可以跳過(guò)不必要的計(jì)算, 有策略地使用可以提高程序性能。