(6)Kotlin/Js For Harmony——ArkTs 开发工具套件

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工具类

  1. 添加配置:
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);
    }
}
  1. 调用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,序列化卡顿优化,鸿蒙开发套件,架构设计思路等等,欢迎关注!

相关推荐
奇风2 小时前
uni-app + DevEco 鸿蒙跨平台应用开发实战1-环境安装分享
uniapp·harmonyos·鸿蒙应用开发·鸿蒙跨平台应用开发
爱笑的眼睛112 小时前
HarmonyOS Scroll滚动容器深度性能优化指南
华为·harmonyos
●VON4 小时前
Electron for HarmonyOS 开发环境搭建
javascript·electron·harmonyos
万少5 小时前
上架元服务-味寻纪 技术分享
前端·harmonyos
大雷神6 小时前
windows中flutter开发鸿蒙实操
harmonyos
路人甲ing..6 小时前
Ubuntu怎么安装tar.gz (android-studio为例)
linux·ubuntu·kotlin·android studio
u***j3248 小时前
HarmonyOS在智能家居中的实践
华为·智能家居·harmonyos
柒儿吖19 小时前
Electron for 鸿蒙PC 窗口问题完整解决方案
javascript·electron·harmonyos
xiaocao_102319 小时前
鸿蒙手机上有哪些比较好用的记事提醒工具?
华为·智能手机·harmonyos