轻任务中心的执行状态、球态摘要和展开窗口已经通过同一套快照同步,并能在前后台切换时重新核对任务事实。真正上线前还有一条关键路径:当轻量窗口创建失败、当前页面上下文失效、用户没有授权相册资源,或者后台恢复的任务数据已经过期,任务应该怎样继续?如果一个悬浮入口失效就把整个任务标记成失败,用户可能会误以为正在整理的文件也丢失了;如果权限不足时仍自动重试,界面又可能陷入反复弹出请求的循环。

这篇关注能力可用性与任务执行事实的分离。任务能否继续、界面能否悬浮、资源是否已经得到授权,是三个不同判断。闪控球和展开窗口承担快捷入口,任务中心承担稳定的操作入口;某个入口异常时,优先让用户在应用内恢复任务控制,而不是让执行服务跟随界面消失。

闪控窗与闪控球异常降级封面

一、建立明确的故障分类

延续前文TaskPhase、TaskSnapshot和revision模型,新增的是故障来源与展示策略。一个正在运行的文件整理任务,即使暂时不能显示悬浮入口,也可以继续向任务仓库发布进度;而缺少必要文件授权的图片备份任务需要暂停在“等待用户选择资源”的阶段。故障分类越清楚,恢复入口越容易保持准确。

条件对业务任务的影响界面处理
当前窗口浮层不可用不自动改变任务阶段收起轻量入口,显示任务中心入口
页面UI上下文已失效停止该页面的界面订阅由新页面重新订阅最近快照
用户未选择所需图片资源准备不完整提示选择图片,保留已有进度
暂时无法访问网络同步步骤需要等待保留断点并提供重试
本地文件已被移走断点资源无效说明缺失资源,要求重新选择
旧异步请求迟到可能污染新状态依据任务ID与版本丢弃旧结果

这里特别要区分应用窗口内的浮层与跨应用显示的系统级窗口形态。ArkUI公开的OverlayManager用于在当前窗口内的页面之上展示自定义UI,层级还位于对话框等组件之下;它适合作为轻量任务入口的应用内承载方式。更广范围的窗口展示需依据目标设备、公开能力和具体权限要求另行设计,不能把当前窗口内浮层理解成对所有应用窗口都具有相同覆盖范围。

二、把故障转换成稳定的降级决策

业务层先不处理具体UI对象,而是把输入事实归一为一个决策。任务的运行阶段继续交给执行仓库;显示策略只回答“在哪里让用户继续操作”。这能避免在窗口创建失败的回调中随意修改RUNNING、PAUSED等任务阶段。

export enum SurfaceFault {
  NONE = 'none',
  HOST_UNAVAILABLE = 'host_unavailable',
  RESOURCE_REQUIRED = 'resource_required',
  TRANSIENT_NETWORK = 'transient_network',
  RESOURCE_MISSING = 'resource_missing'
}

export enum SurfaceTarget {
  OVERLAY = 'overlay',
  TASK_PAGE = 'task_page'
}

export class SurfaceDecision {
  constructor(
    public target: SurfaceTarget,
    public message: string,
    public requiresAction: boolean
  ) {}
}

export function decideSurface(fault: SurfaceFault): SurfaceDecision {
  switch (fault) {
    case SurfaceFault.NONE:
      return new SurfaceDecision(SurfaceTarget.OVERLAY, '', false)
    case SurfaceFault.RESOURCE_REQUIRED:
      return new SurfaceDecision(SurfaceTarget.TASK_PAGE,
        '选择需要处理的图片后继续', true)
    case SurfaceFault.TRANSIENT_NETWORK:
      return new SurfaceDecision(SurfaceTarget.TASK_PAGE,
        '网络暂时不可用,可稍后重试', true)
    case SurfaceFault.RESOURCE_MISSING:
      return new SurfaceDecision(SurfaceTarget.TASK_PAGE,
        '原始文件位置发生变化,请重新选择', true)
    default:
      return new SurfaceDecision(SurfaceTarget.TASK_PAGE,
        '轻量入口暂不可用,请在任务中心操作', false)
  }
}

SurfaceDecision属于展示策略,任务数据不会因为切换到TASK_PAGE就清零。用户下一次进入任务中心时,仍能看到最近进度、失败原因和可以采取的操作。对偶发网络错误,界面可以提供重试;对资源已失效的情况,应该先要求重新选择正确文件,避免重复执行同一个已经无法完成的步骤。

三、在当前窗口内管理轻量入口

使用ComponentContent创建浮层节点,并交给当前UIContext关联的OverlayManager管理。为了防止重复创建,控制器必须记录已经添加的节点;在页面离开时移除节点,避免返回页面后出现两份球态。创建失败要能回退到普通任务中心按钮,清理时也要让引用及时失效。

import { ComponentContent } from '@kit.ArkUI'

class BubbleArgs {
  constructor(public label: string, public percent: number) {}
}

@Builder
function TaskBubble(args: BubbleArgs) {
  Column() {
    Text(args.label).fontSize(12).fontColor(Color.White)
    Text(`${args.percent}%`)
      .fontSize(20).fontWeight(FontWeight.Bold).fontColor(Color.White)
  }
  .width(86).height(86)
  .justifyContent(FlexAlign.Center)
  .backgroundColor('#1668DC')
  .borderRadius(43)
  .margin({ top: 120, left: 16 })
}

@Entry
@Component
struct TaskCenter {
  @State private notice: string = '任务中心已就绪'
  private bubble?: ComponentContent<BubbleArgs>

  private openBubble(): void {
    if (this.bubble) {
      return
    }
    const context = this.getUIContext()
    const content = new ComponentContent(context,
      wrapBuilder<[BubbleArgs]>(TaskBubble),
      new BubbleArgs('文件整理', 67))
    try {
      context.getOverlayManager().addComponentContent(content, 0)
      this.bubble = content
      this.notice = '轻量入口已展开'
    } catch (error) {
      this.notice = '轻量入口暂不可用,请在任务中心操作'
    }
  }

  private closeBubble(): void {
    const content = this.bubble
    if (!content) {
      return
    }
    this.bubble = undefined
    this.getUIContext().getOverlayManager().removeComponentContent(content)
  }

  aboutToDisappear(): void {
    this.closeBubble()
  }

  build() {
    Column({ space: 18 }) {
      Text('任务中心').fontSize(26).fontWeight(FontWeight.Bold)
      Text(this.notice)
      Button('打开轻量入口').onClick(() => this.openBubble())
      Button('收起入口').onClick(() => this.closeBubble())
      Text('文件整理:67%')
    }.padding(24).width('100%')
  }
}

这一段展示的是当前应用窗口内的轻量UI。实际任务中心需要将67%改为订阅到的真实任务投影,并把打开入口的动作交给统一表面控制器。在跨页面导航时,应由更长生命周期的管理者持有UI策略;页面自身仍须保证订阅和浮层节点能成对释放。窗口生命周期也要独立跟踪,不能仅凭UIAbility进入前台就推断每个辅助窗口都已显示。

DevEco Studio中的故障策略与任务中心界面

四、权限处理从用户意图出发

以图片备份任务为例,用户主动选择图片是比反复申请大范围媒体访问更贴近需求的入口。可以使用PhotoViewPicker让用户确定本次处理的资源;选择结果中的URI交给资源适配器。只有拿到有效URI,任务才能从等待资源阶段转入准备执行。用户取消选择时应保持当前状态,允许稍后再试,而不是把取消操作当成系统故障。

import { photoAccessHelper } from '@kit.MediaLibraryKit'

export class UserPhotoSelection {
  async selectOne(): Promise<string | null> {
    const options = new photoAccessHelper.PhotoSelectOptions()
    options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
    options.maxSelectNumber = 1
    const picker = new photoAccessHelper.PhotoViewPicker()
    try {
      const result = await picker.select(options)
      return result.photoUris.length > 0 ? result.photoUris[0] : null
    } catch (error) {
      return null
    }
  }
}

获取URI后仍应检查资源是否可读、格式是否符合处理要求,并将资源与任务ID关联。用户选择的某个URI在后续阶段失效时,任务进入等待资源或明确的错误状态;下一次选择成功后再继续。需要其它运行时权限的具体功能,必须依照所使用Kit的权限声明与授权回调处理,避免在每次页面刷新时自动触发授权流程。任务中心应说明权限用途,并为拒绝后的操作提供清晰出口。

五、故障恢复以任务版本为准

上篇引入了恢复中间阶段RECONCILING,本篇把异常入口与执行状态连接起来:前台恢复时先向执行仓库查询当前任务事实,再决定是否显示球态、展开窗口或降级后的任务页面。不能先依据旧快照把任务直接标记为正在执行,再去等待查询结果。对每次恢复分配递增的epoch,迟到的错误回调只能影响它所属的那次操作。

export class RecoveryGate {
  private epoch: number = 0

  async reconcile(taskId: string,
    query: (taskId: string) => Promise<number>): Promise<number | null> {
    const current = ++this.epoch
    try {
      const progress = await query(taskId)
      if (current !== this.epoch) {
        return null
      }
      return Math.max(0, Math.min(100, progress))
    } catch (error) {
      if (current !== this.epoch) {
        return null
      }
      throw error
    }
  }

  invalidate(): void {
    this.epoch++
  }
}

进度读取结果需要配合任务仓库的revision一起使用。上面返回的数字只代表当前查询给出的进度,不能独立决定任务属于DONE、RUNNING还是PAUSED。例如100%的上传字节进度之后,服务端仍可能进行确认步骤,必须等待任务服务的阶段事件才能发布完成态。业务事件与页面展示事件保持分离,才能在窗口异常时保护任务的事实记录。

六、给用户留下可预测的恢复操作

任务入口消失时,最重要的是让用户知道从哪里继续。可在任务中心固定展示三个层级的信息:任务名称与最近进度、当前障碍、下一步按钮。窗口浮层不可用时显示“进入任务中心”;图片资源不足时显示“选择图片”;网络恢复前显示“重试同步”。这些按钮必须指向不同命令,不能都简单地调用“重新开始任务”。

轻任务中心的权限提示与恢复操作

建议用一张回归矩阵检查这条降级链路:

触发场景期望结果排查位置
快速连续点击打开球态只有一个浮层节点ComponentContent持有者
关闭页面后重新进入旧节点释放,新页面可重新创建aboutToDisappear与订阅管理
当前窗口浮层创建失败任务中心仍可操作SurfaceDecision
用户取消图片选择保留任务及已完成进度资源适配器
先失败的恢复请求迟到不覆盖后续成功查询epoch与revision
资源路径失效显示重新选择入口断点校验

还要区分“处理失败”与“显示失败”。真正的任务异常应由执行仓库决定重试、暂停或终止;显示层异常只改变用户的操作入口。这样任务即使经历前后台变化、窗口销毁或资源重新选择,也不会因为某一个UI控件失效而发生进度回退。

七、工程收束

异常降级的价值不在于隐藏所有错误,而是让错误转化为下一步可执行的操作。统一状态仓库维持任务事实,表面控制器决定球态或窗态是否可用,资源适配器负责用户选择的文件,恢复网关过滤迟到结果。对使用OverlayManager的应用内轻量入口,应遵守当前窗口的层级和生命周期;对媒体文件应选择与业务目的匹配的授权路径。下一篇将收束到高频状态刷新、批量任务投影与渲染开销,在保证状态正确的前提下优化交互平滑度。

相关文档:OverlayManager浮层管理、窗口生命周期、图片选择器开发示例。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐