Scope
ArkTs上的Scope 和Kotlin 写成Scope 类似,用于生命周期管理
typescript
export class Scope {
private abortSignalSet = new Set<ScopeAbortSignal>()
private cancelled = false
cancel(): void {
this.abortSignalSet.forEach((abortSignal): void => abortSignal.abort())
this.abortSignalSet.clear()
this.cancelled = true
}
launch<T>(block: (abortSignal: ScopeAbortSignal) => Promise<T>): ScopeAbortSignal {
const abortSignal = new ScopeAbortSignal()
if (this.cancelled) {
throw new ScopeCancelError("Scope has cancelled")
}
this.abortSignalSet.add(abortSignal)
block(abortSignal)
.then((result: T) => {
abortSignal.clear()
this.abortSignalSet.delete(abortSignal)
})
.catch((e: Error) => {
abortSignal.clear()
this.abortSignalSet.delete(abortSignal)
if(e instanceof ScopeCancelError) {
KLog.debug("Scope", () => `ScopeCancelError`)
} else if (e instanceof Error){
throw e
} else {
throw new Error(JSONExt.stringifySafe(e))
}
})
return abortSignal
}
}
export class ScopeAbortSignal {
private listener: Set<()=>void> = new Set()
private _isCancelled = false
addListener(listener: ()=>void) {
this.listener.add(listener)
}
removeListener(listener: ()=>void) {
this.listener.delete(listener)
}
oneshotListener(listener: ()=>void) {
const oneshot = ()=> {
listener()
this.listener.delete(oneshot)
}
this.listener.add(oneshot)
}
invokeWhenAbort(reject:(error?: Object) => void) {
this.oneshotListener(()=> {
reject(new ScopeCancelError())
})
}
abort() {
this._isCancelled = true
this.listener.forEach(listener => {
listener()
})
this.clear()
}
clear() {
this.listener.clear()
}
isCancelled(): boolean {
return this._isCancelled
}
}
用起来也比较简单:
dart
this.scope.launch(async (abortSignal) => {
await this.flow.collect(abortSignal, async (data) => {
// logic
})
})
this.scope.cancel()的时候,可以通过abortSignal感知,执行相关的暂停工作。后面主要是配合Flow 使用。
Flow
Flow 比较重要,后续响应式变成主要依赖Flow, 同时我们要实现一些flow的常用操作符,如map,filter, combine, first 等等,尽量对标Kotlin Flow。
Flow 操作符
typescript
export abstract class Flow<T> {
abstract collect(signal: ScopeAbortSignal, collector: FlowCollector<T>): Promise<void>
collectIn(scope: Scope): ScopeAbortSignal {
return scope.launch(async (signal: ScopeAbortSignal) => {
await this.collect(signal, async ()=> {})
})
}
transform<R>(transform: (signal: ScopeAbortSignal, input: T) => Promise<R>): Flow<R> {
return new TransformMediator<T, R>(this, transform)
}
map<R>(transform: (input: T, signal: ScopeAbortSignal) => Promise<R>): Flow<R> {
return this.transform(async (signal: ScopeAbortSignal, input: T) => {
return transform(input, signal)
})
}
filter(predicate: (input: T, signal: ScopeAbortSignal) => Promise<boolean>): Flow<T> {
return new FilterMediator<T>(this, predicate)
}
async first(signal: ScopeAbortSignal,
predicate: (input: T, signal: ScopeAbortSignal) => Promise<boolean>): Promise<T> {
let result: T | undefined
await new CollectWhileMediator<T>(this, async (signal: ScopeAbortSignal, input: T) => {
if (await predicate(input, signal)) {
result = input
return false
}
return true
}).collect(signal, (value: T, signal: ScopeAbortSignal) => Promise.resolve())
return result as T
}
stateIn(scope: Scope, initValue: T): StateFlow<T> {
const flow = mutableStateFlow<T>(initValue)
scope.launch(async (signal: ScopeAbortSignal) => {
await this.collect(signal, async (value: T, signal: ScopeAbortSignal) => {
flow.value = value
})
})
return flow
}
}
贴一下TransformMediator的实现,大家可以参考一下,其他操作符的实现也都是类似操作
typescript
class TransformMediator<INPUT, OUTPUT> extends Flow<OUTPUT> {
private up: Flow<INPUT>
private transformer: (signal: ScopeAbortSignal, input: INPUT) => Promise<OUTPUT>
constructor(up: Flow<INPUT>, transform: (signal: ScopeAbortSignal, input: INPUT) => Promise<OUTPUT>) {
super()
this.up = up
this.transformer = transform
}
collect(signal: ScopeAbortSignal, collector: FlowCollector<OUTPUT>): Promise<void> {
return this.up.collect(signal, async (value: INPUT, signal: ScopeAbortSignal) => {
const newValue = await this.transformer(signal, value)
await collector(newValue, signal)
})
}
}
这里着重提一下first 操作符,该操作符与将flow 中断,返回目标值。为了中断flow,需要throw error, 看一下实现:
typescript
class CollectWhileMediator<T> extends Flow<T> {
private up: Flow<T>
private predicate: (signal: ScopeAbortSignal, input: T) => Promise<boolean>
constructor(up: Flow<T>, predicate: (signal: ScopeAbortSignal, input: T) => Promise<boolean>) {
super()
this.up = up
this.predicate = predicate
}
async collect(signal: ScopeAbortSignal, collector: FlowCollector<T>): Promise<void> {
const ownerId = util.generateRandomUUID()
try {
await this.up.collect(signal, (value: T, signal: ScopeAbortSignal) => {
return CancellablePromiseFactory.create({
abortSignal: signal,
executor: async (resolve, reject) => {
if (!(await this.predicate(signal, value))) { // 外面返回true 直接中断
reject(new CollectCancelError(ownerId))
} else {
resolve()
}
}
})
})
} catch (e) {
if (e instanceof CollectCancelError && e.message === ownerId) {
KLog.debug('CollectWhileMediator', () => `CollectCancelError`)
// 中断后在这里返回
return
} else {
throw e instanceof Error ? e : new Error(`Unexpected error: ${JSONExt.stringifySafe(e)}`)
}
}
}
}
最后再提一下 combine 操作符, combine 可以同时监听多个flow,其中有一个flow 变化,就会调用transform:
javascript
export function combineArray<T, R>(flows: Flow<T>[],
transform: (data: T[], signal: ScopeAbortSignal) => Promise<R>): Flow<R> {
return new CombineMediator<T, R>(flows, transform)
}
看一下核心实现 CombineMediator:
typescript
class CombineMediator<T, R> extends Flow<R> {
private flows: Flow<T>[]
private transformer: (data: T[], signal: ScopeAbortSignal) => Promise<R>
private latestValue: T[]
private hasValue: boolean[]
private mediator = mutableStateFlow<[T[], boolean[]]>([[], []])
constructor(flows: Flow<T>[], transformer: (data: T[], signal: ScopeAbortSignal) => Promise<R>) {
super();
this.flows = flows
this.transformer = transformer
this.latestValue = new Array(flows.length)
this.hasValue = new Array(flows.length).fill(false)
}
async collect(signal: ScopeAbortSignal, collector: FlowCollector<R>): Promise<void> {
const n = this.flows.length;
for (let i = 0; i < n; i++) {
const f = this.flows[i];
f.collect(signal, async (value) => {
this.latestValue[i] = value;
this.hasValue[i] = true;
this.mediator.value = [Array.from(this.latestValue), Array.from(this.hasValue)]
})
}
await this.mediator.collect(signal, async (value: [T[], boolean[]], signal: ScopeAbortSignal) => {
if (value[1].every(Boolean)) {
const newValue = await this.transformer(value[0], signal)
await collector(newValue, signal)
}
})
}
}
StateFlow
StateFlow 会保存最后一次设置的值,后面我们编写代码时主要是用这个Flow
scala
export abstract class StateFlow<T> extends Flow<T> {
abstract get value(): T
}
export abstract class MutableStateFlow<T> extends StateFlow<T> {
abstract set value(val: T)
abstract asStateFlow(): StateFlow<T>
}
typescript
class Slot {
sequence = -1
promiseResolve?: () => void
cancel: boolean = false
allocateWaitPromise(): Promise<void> {
return new Promise((resolve, reject) => {
this.promiseResolve = resolve
})
}
clear() {
this.cancel = true
this.promiseResolve?.()
}
}
export class StateFlowImpl<T> extends MutableStateFlow<T> {
private sequence = 0
private slotRecord = new Set<Slot>()
private _value: T
constructor(initialState: T) {
super()
this._value = initialState
}
set value(val: T) {
this.updateState(val)
}
get value(): T {
return this._value
}
updateState(update: T) {
if (update === this._value) {
return
}
this.sequence++
this._value = update
this.slotRecord.forEach((slot) => {
slot.promiseResolve?.()
})
}
collect(signal: ScopeAbortSignal, collector: FlowCollector<T>): Promise<void> {
const slot = new Slot()
this.slotRecord.add(slot)
return CancellablePromiseFactory.create({
abortSignal: signal,
executor: async (resolve, reject) => {
while (true) {
if (slot.cancel) {
KLog.debug(TAG, () => `cancel`)
return
}
if (slot.sequence < this.sequence) {
slot.sequence = this.sequence
Log.debug(TAG, () => `slot.sequence=${slot.sequence} this.sequence=${this.sequence}`)
try {
await collector(this.value, signal)
} catch (e) {
if (e instanceof CollectCancelError) {
throw e
} else {
if (BuildProfile.DEBUG) {
ToastUtil.toast(`collector error ${e}\n ${e.stack}`)
}
Log.error(TAG, () => `collector error ${e} \n${e.stack}`)
}
}
// Log.debug(TAG, () => `collector end`)
} else {
// Log.debug(TAG, () => `collector waiting`)
await slot.allocateWaitPromise()
slot.promiseResolve = undefined
// Log.debug(TAG, () => `collector wait end`)
}
}
},
onFinished: () => {
slot.clear()
this.slotRecord.delete(slot)
},
})
}
asStateFlow(): StateFlow<T> {
return this
}
}
export function mutableStateFlow<T>(initialValue: T): MutableStateFlow<T> {
return new StateFlowImpl<T>(initialValue)
}
StateFlow 的collect 是一个while(true)的循环,内部用async/await 去控制挂起等待,
可以看到我们用到了 CancellablePromiseFactory,这个是一个Promise的简单封装,代码如下:
scss
export class CancellablePromiseFactory {
static create<T>(param: CancellablePromiseFactoryParam<T>): Promise<T> {
return new Promise<T>(async (resolve, reject) => {
param.abortSignal.invokeWhenAbort((error) => {
param.onCancelled?.()
param.onFinished?.()
reject(error)
})
try {
await param.executor(
(it) => {
param.onFinished?.()
resolve(it)
},
(it) => {
param.onFinished?.()
reject(it)
})
} catch (e) {
param.onFinished?.()
reject(e)
}
})
}
}
鸿蒙State 转 Flow
类似于Compose 的 snapshotFlow{}, 当状态变化,会自动触发Flow 发射新值,非常好用。
typescript
export class ObservedDataFlow<T> extends StateFlowImpl<T> {
private observer: Observer // 需要 持有Observer,否则Observer被回收,就无法感知到状态变化了
readonly tag?: string
constructor(calculate: () => T, tag?: string) {
super(calculate())
this.tag = tag
this.observer = new Observer(() => {
const result = calculate()
this.value = result as T
KLog.debug('ObservedDataFlow', ()=>`tag=${tag} : calculate result = ${result}`)
return result as Object
})
}
}
@ObservedV2
class Observer {
private calculate: () => Object
constructor(calculate: () => Object) {
this.calculate = calculate
}
@Computed
get value(): Object {
return this.calculate()
}
}
export function flowOf<T>(calculate: () => T, tag?: string): Flow<T> {
return new ObservedDataFlow<T>(calculate, tag)
}
用起来非常简单,看一下例子:
dart
@Local name: string = ''
const flow = flowOf(()=>this.name)
scope.launch(async (abortSignal)=>{
await this.flow
.map(async (it) => it + '-mapped')
.collect(abortSignal, async (data)=>{
print(data)
})
})
当name 改变的时候,会自动触发flow 发送对应值,从而触发打印。
DistinctState
鸿蒙上对State的读写涉及到反射,有性能损耗,所以应该减少对state 的不必要读写。
DistinctState 就是为了减少不必要的state的读写,实现比较简单,请看源码。同理还有DistinctListState, DistinctMapState 等,这里不再赘述。
kotlin
@ObservedV2
export class DistinctState<T> {
// _raw 不是状态变量,对其读没有性能损耗
private _raw: T
@Trace private _state: T
get raw(): T {
return this._raw
}
get state(): T {
return this._state
}
private tag: string
constructor(initValue: T, tag: string = '') {
this._raw = initValue
this._state = initValue
this.tag = tag
}
update(newValue: T): boolean {
try {
if (equals(this.raw, newValue)) {
return false
}
} catch (e) {
Log.error('DistinctState', ()=>`equals error ${e} \n ${e.stack}`)
}
this._raw = newValue
this._state = newValue
return true
}
}
DeepLink
提供一个使用简单的DeepLink工具类
- 添加配置:
less
class DeepLinkConfig{
@DeepLink('test://zoo?name=elephant&animal_id={animal_id}&is_new={is_new}')
openPage(
@DeeplinkOrigin() originUrl: string,
@DeeplinkParam('animal_id') animalId: string,
@DeeplinkParam('is_new', DeepLinkParamType.Boolean) isNew: boolean
){
console.log('originUrl =>', originUrl);
console.log('animalId =>', animalId);
console.log('isNew=>', isNew);
}
}
- 调用routeDeepLink('test://zoo?name=elephant&animal_id=123&is_new=true'), 打印内容如下:
ini
originUrl =>test://zoo?name=elephant&animal_id=123&is_new=true
animalId =>123
isNew=>true
该项目目前已开源并上传到OpenHarmony三方中心仓: ohpm.openharmony.cn/#/cn/detail...
关于「ArkTs 开发工具套件」的介绍就告一段落了,如果大家在使用过程中有任何问题,欢迎留言讨论。 Android工程师的kmp(kotlin/js) for harmony开发指南 这一系列文章旨在系统性的提供一套完整的Kotlin/Js For Harmony的解决方案。后续系列文章会介绍如何复用ViewModel,序列化卡顿优化,鸿蒙开发套件,架构设计思路等等,欢迎关注!