ExtensionAbility 入门:服务、卡片、分享与后台能力边界

ExtensionAbility 入门:服务、卡片、分享与后台能力边界

前两篇我们已经把 Stage 工程目录、首页加载、资源管理和页面跳转讲清楚了。从这一篇开始,重点放到 Stage 模型本身:哪些代码应该放在 AbilityStage,哪些代码应该放在 UIAbility,哪些场景应该使用 ExtensionAbility,后台任务又应该如何设计。

本文的目标很明确:让读者看完后能把概念落到工程里,而不是只记住几个类名。

1. 本章先解决什么问题

ExtensionAbility 面向非页面扩展场景。理解它的边界,才能避免把后台服务、卡片和分享入口都塞进 UIAbility。

如果你正在写 HarmonyOS 应用,可以先带着三个问题读本文:

  1. 这段代码应该放在入口、页面、服务层还是扩展能力中?
  2. 出问题时应该先检查配置、生命周期、页面路由还是后台任务?
  3. 这个能力是否真的需要后台运行,还是可以交给前台页面或系统调度?

补充流程图:
#mermaid-svg-EgOwDWROvmRceOcJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EgOwDWROvmRceOcJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EgOwDWROvmRceOcJ .error-icon{fill:#552222;}#mermaid-svg-EgOwDWROvmRceOcJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EgOwDWROvmRceOcJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EgOwDWROvmRceOcJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EgOwDWROvmRceOcJ .marker.cross{stroke:#333333;}#mermaid-svg-EgOwDWROvmRceOcJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EgOwDWROvmRceOcJ p{margin:0;}#mermaid-svg-EgOwDWROvmRceOcJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EgOwDWROvmRceOcJ .cluster-label text{fill:#333;}#mermaid-svg-EgOwDWROvmRceOcJ .cluster-label span{color:#333;}#mermaid-svg-EgOwDWROvmRceOcJ .cluster-label span p{background-color:transparent;}#mermaid-svg-EgOwDWROvmRceOcJ .label text,#mermaid-svg-EgOwDWROvmRceOcJ span{fill:#333;color:#333;}#mermaid-svg-EgOwDWROvmRceOcJ .node rect,#mermaid-svg-EgOwDWROvmRceOcJ .node circle,#mermaid-svg-EgOwDWROvmRceOcJ .node ellipse,#mermaid-svg-EgOwDWROvmRceOcJ .node polygon,#mermaid-svg-EgOwDWROvmRceOcJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EgOwDWROvmRceOcJ .rough-node .label text,#mermaid-svg-EgOwDWROvmRceOcJ .node .label text,#mermaid-svg-EgOwDWROvmRceOcJ .image-shape .label,#mermaid-svg-EgOwDWROvmRceOcJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-EgOwDWROvmRceOcJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EgOwDWROvmRceOcJ .rough-node .label,#mermaid-svg-EgOwDWROvmRceOcJ .node .label,#mermaid-svg-EgOwDWROvmRceOcJ .image-shape .label,#mermaid-svg-EgOwDWROvmRceOcJ .icon-shape .label{text-align:center;}#mermaid-svg-EgOwDWROvmRceOcJ .node.clickable{cursor:pointer;}#mermaid-svg-EgOwDWROvmRceOcJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EgOwDWROvmRceOcJ .arrowheadPath{fill:#333333;}#mermaid-svg-EgOwDWROvmRceOcJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EgOwDWROvmRceOcJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EgOwDWROvmRceOcJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EgOwDWROvmRceOcJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EgOwDWROvmRceOcJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EgOwDWROvmRceOcJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EgOwDWROvmRceOcJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EgOwDWROvmRceOcJ .cluster text{fill:#333;}#mermaid-svg-EgOwDWROvmRceOcJ .cluster span{color:#333;}#mermaid-svg-EgOwDWROvmRceOcJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EgOwDWROvmRceOcJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EgOwDWROvmRceOcJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-EgOwDWROvmRceOcJ .icon-shape,#mermaid-svg-EgOwDWROvmRceOcJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EgOwDWROvmRceOcJ .icon-shape p,#mermaid-svg-EgOwDWROvmRceOcJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EgOwDWROvmRceOcJ .icon-shape .label rect,#mermaid-svg-EgOwDWROvmRceOcJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EgOwDWROvmRceOcJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EgOwDWROvmRceOcJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EgOwDWROvmRceOcJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:本章先解决什么问题
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 本章先解决什么问题ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,读者不会只看到一段抽象描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码把不同扩展入口先统一成请求对象,再按 type 分发。这样可以避免把服务、卡片、分享逻辑全部写在一个生命周期回调里。

2. ExtensionAbility 解决什么问题

应用不只有前台页面,还可能需要服务化能力、卡片能力、分享入口等能力。这些场景通常不是普通页面,而是系统的扩展入口。

补充流程图:
#mermaid-svg-McrTX7Hiw4wBIlbO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-McrTX7Hiw4wBIlbO .error-icon{fill:#552222;}#mermaid-svg-McrTX7Hiw4wBIlbO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-McrTX7Hiw4wBIlbO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-McrTX7Hiw4wBIlbO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-McrTX7Hiw4wBIlbO .marker.cross{stroke:#333333;}#mermaid-svg-McrTX7Hiw4wBIlbO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-McrTX7Hiw4wBIlbO p{margin:0;}#mermaid-svg-McrTX7Hiw4wBIlbO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster-label text{fill:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster-label span{color:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster-label span p{background-color:transparent;}#mermaid-svg-McrTX7Hiw4wBIlbO .label text,#mermaid-svg-McrTX7Hiw4wBIlbO span{fill:#333;color:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO .node rect,#mermaid-svg-McrTX7Hiw4wBIlbO .node circle,#mermaid-svg-McrTX7Hiw4wBIlbO .node ellipse,#mermaid-svg-McrTX7Hiw4wBIlbO .node polygon,#mermaid-svg-McrTX7Hiw4wBIlbO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-McrTX7Hiw4wBIlbO .rough-node .label text,#mermaid-svg-McrTX7Hiw4wBIlbO .node .label text,#mermaid-svg-McrTX7Hiw4wBIlbO .image-shape .label,#mermaid-svg-McrTX7Hiw4wBIlbO .icon-shape .label{text-anchor:middle;}#mermaid-svg-McrTX7Hiw4wBIlbO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-McrTX7Hiw4wBIlbO .rough-node .label,#mermaid-svg-McrTX7Hiw4wBIlbO .node .label,#mermaid-svg-McrTX7Hiw4wBIlbO .image-shape .label,#mermaid-svg-McrTX7Hiw4wBIlbO .icon-shape .label{text-align:center;}#mermaid-svg-McrTX7Hiw4wBIlbO .node.clickable{cursor:pointer;}#mermaid-svg-McrTX7Hiw4wBIlbO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-McrTX7Hiw4wBIlbO .arrowheadPath{fill:#333333;}#mermaid-svg-McrTX7Hiw4wBIlbO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-McrTX7Hiw4wBIlbO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-McrTX7Hiw4wBIlbO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-McrTX7Hiw4wBIlbO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-McrTX7Hiw4wBIlbO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-McrTX7Hiw4wBIlbO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster text{fill:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO .cluster span{color:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-McrTX7Hiw4wBIlbO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-McrTX7Hiw4wBIlbO rect.text{fill:none;stroke-width:0;}#mermaid-svg-McrTX7Hiw4wBIlbO .icon-shape,#mermaid-svg-McrTX7Hiw4wBIlbO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-McrTX7Hiw4wBIlbO .icon-shape p,#mermaid-svg-McrTX7Hiw4wBIlbO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-McrTX7Hiw4wBIlbO .icon-shape .label rect,#mermaid-svg-McrTX7Hiw4wBIlbO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-McrTX7Hiw4wBIlbO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-McrTX7Hiw4wBIlbO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-McrTX7Hiw4wBIlbO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:ExtensionAbility 解决什么问题
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class ExtensionAbilityRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现、验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码把不同扩展入口先统一成请求对象,再按 type 分发。这样可以避免把服务、卡片、分享逻辑全部写在一个生命周期回调里。

3. ServiceExtensionAbility 的定位

服务扩展适合处理与服务相关的交互,但不能将其视为无限的后台线程。其后台能力受系统策略和场景约束。

补充流程图:
#mermaid-svg-cAit883uzleZqXjt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cAit883uzleZqXjt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cAit883uzleZqXjt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cAit883uzleZqXjt .error-icon{fill:#552222;}#mermaid-svg-cAit883uzleZqXjt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cAit883uzleZqXjt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cAit883uzleZqXjt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cAit883uzleZqXjt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cAit883uzleZqXjt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cAit883uzleZqXjt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cAit883uzleZqXjt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cAit883uzleZqXjt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cAit883uzleZqXjt .marker.cross{stroke:#333333;}#mermaid-svg-cAit883uzleZqXjt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cAit883uzleZqXjt p{margin:0;}#mermaid-svg-cAit883uzleZqXjt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cAit883uzleZqXjt .cluster-label text{fill:#333;}#mermaid-svg-cAit883uzleZqXjt .cluster-label span{color:#333;}#mermaid-svg-cAit883uzleZqXjt .cluster-label span p{background-color:transparent;}#mermaid-svg-cAit883uzleZqXjt .label text,#mermaid-svg-cAit883uzleZqXjt span{fill:#333;color:#333;}#mermaid-svg-cAit883uzleZqXjt .node rect,#mermaid-svg-cAit883uzleZqXjt .node circle,#mermaid-svg-cAit883uzleZqXjt .node ellipse,#mermaid-svg-cAit883uzleZqXjt .node polygon,#mermaid-svg-cAit883uzleZqXjt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cAit883uzleZqXjt .rough-node .label text,#mermaid-svg-cAit883uzleZqXjt .node .label text,#mermaid-svg-cAit883uzleZqXjt .image-shape .label,#mermaid-svg-cAit883uzleZqXjt .icon-shape .label{text-anchor:middle;}#mermaid-svg-cAit883uzleZqXjt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cAit883uzleZqXjt .rough-node .label,#mermaid-svg-cAit883uzleZqXjt .node .label,#mermaid-svg-cAit883uzleZqXjt .image-shape .label,#mermaid-svg-cAit883uzleZqXjt .icon-shape .label{text-align:center;}#mermaid-svg-cAit883uzleZqXjt .node.clickable{cursor:pointer;}#mermaid-svg-cAit883uzleZqXjt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cAit883uzleZqXjt .arrowheadPath{fill:#333333;}#mermaid-svg-cAit883uzleZqXjt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cAit883uzleZqXjt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cAit883uzleZqXjt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cAit883uzleZqXjt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cAit883uzleZqXjt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cAit883uzleZqXjt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cAit883uzleZqXjt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cAit883uzleZqXjt .cluster text{fill:#333;}#mermaid-svg-cAit883uzleZqXjt .cluster span{color:#333;}#mermaid-svg-cAit883uzleZqXjt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cAit883uzleZqXjt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cAit883uzleZqXjt rect.text{fill:none;stroke-width:0;}#mermaid-svg-cAit883uzleZqXjt .icon-shape,#mermaid-svg-cAit883uzleZqXjt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cAit883uzleZqXjt .icon-shape p,#mermaid-svg-cAit883uzleZqXjt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cAit883uzleZqXjt .icon-shape .label rect,#mermaid-svg-cAit883uzleZqXjt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cAit883uzleZqXjt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cAit883uzleZqXjt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cAit883uzleZqXjt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:ServiceExtensionAbility 的定位
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class ServiceExtensionAbExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码提供了一个可迁移到 Stage 工程中的最小化实现。
  3. 这段代码将不同的扩展入口统一为请求对象,再按 type 进行分发。这样可以避免把服务、卡片、分享逻辑全部写在一个生命周期回调里。

4. FormExtensionAbility 的定位

卡片能力面向桌面或系统卡片展示,重点是轻量展示和刷新,不适合承载复杂页面逻辑。

补充流程图:
#mermaid-svg-ArAlod5cdlvSQx6T{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ArAlod5cdlvSQx6T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ArAlod5cdlvSQx6T .error-icon{fill:#552222;}#mermaid-svg-ArAlod5cdlvSQx6T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ArAlod5cdlvSQx6T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ArAlod5cdlvSQx6T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ArAlod5cdlvSQx6T .marker.cross{stroke:#333333;}#mermaid-svg-ArAlod5cdlvSQx6T svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ArAlod5cdlvSQx6T p{margin:0;}#mermaid-svg-ArAlod5cdlvSQx6T .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ArAlod5cdlvSQx6T .cluster-label text{fill:#333;}#mermaid-svg-ArAlod5cdlvSQx6T .cluster-label span{color:#333;}#mermaid-svg-ArAlod5cdlvSQx6T .cluster-label span p{background-color:transparent;}#mermaid-svg-ArAlod5cdlvSQx6T .label text,#mermaid-svg-ArAlod5cdlvSQx6T span{fill:#333;color:#333;}#mermaid-svg-ArAlod5cdlvSQx6T .node rect,#mermaid-svg-ArAlod5cdlvSQx6T .node circle,#mermaid-svg-ArAlod5cdlvSQx6T .node ellipse,#mermaid-svg-ArAlod5cdlvSQx6T .node polygon,#mermaid-svg-ArAlod5cdlvSQx6T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ArAlod5cdlvSQx6T .rough-node .label text,#mermaid-svg-ArAlod5cdlvSQx6T .node .label text,#mermaid-svg-ArAlod5cdlvSQx6T .image-shape .label,#mermaid-svg-ArAlod5cdlvSQx6T .icon-shape .label{text-anchor:middle;}#mermaid-svg-ArAlod5cdlvSQx6T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ArAlod5cdlvSQx6T .rough-node .label,#mermaid-svg-ArAlod5cdlvSQx6T .node .label,#mermaid-svg-ArAlod5cdlvSQx6T .image-shape .label,#mermaid-svg-ArAlod5cdlvSQx6T .icon-shape .label{text-align:center;}#mermaid-svg-ArAlod5cdlvSQx6T .node.clickable{cursor:pointer;}#mermaid-svg-ArAlod5cdlvSQx6T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ArAlod5cdlvSQx6T .arrowheadPath{fill:#333333;}#mermaid-svg-ArAlod5cdlvSQx6T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ArAlod5cdlvSQx6T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ArAlod5cdlvSQx6T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArAlod5cdlvSQx6T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ArAlod5cdlvSQx6T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArAlod5cdlvSQx6T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ArAlod5cdlvSQx6T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ArAlod5cdlvSQx6T .cluster text{fill:#333;}#mermaid-svg-ArAlod5cdlvSQx6T .cluster span{color:#333;}#mermaid-svg-ArAlod5cdlvSQx6T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ArAlod5cdlvSQx6T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ArAlod5cdlvSQx6T rect.text{fill:none;stroke-width:0;}#mermaid-svg-ArAlod5cdlvSQx6T .icon-shape,#mermaid-svg-ArAlod5cdlvSQx6T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArAlod5cdlvSQx6T .icon-shape p,#mermaid-svg-ArAlod5cdlvSQx6T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ArAlod5cdlvSQx6T .icon-shape .label rect,#mermaid-svg-ArAlod5cdlvSQx6T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArAlod5cdlvSQx6T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ArAlod5cdlvSQx6T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ArAlod5cdlvSQx6T :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:FormExtensionAbility 的定位
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class FormExtensionAbilityRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码说明:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,避免读者只看到一段抽象描述。
  2. 示例代码提供了一个可迁移到 Stage 模型工程中的最小化写法。
  3. 这段代码将不同的扩展入口统一为请求对象,再根据 type 进行分发。这样可以避免将服务、卡片、分享的逻辑全部写在一个生命周期回调中。

5. ShareExtensionAbility 的定位

分享扩展适用于接收外部分享内容,并将内容转交给应用内部处理流程。

补充流程图:
#mermaid-svg-t18dtS7txX4qRuSm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-t18dtS7txX4qRuSm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-t18dtS7txX4qRuSm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-t18dtS7txX4qRuSm .error-icon{fill:#552222;}#mermaid-svg-t18dtS7txX4qRuSm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-t18dtS7txX4qRuSm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-t18dtS7txX4qRuSm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-t18dtS7txX4qRuSm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-t18dtS7txX4qRuSm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-t18dtS7txX4qRuSm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-t18dtS7txX4qRuSm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-t18dtS7txX4qRuSm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-t18dtS7txX4qRuSm .marker.cross{stroke:#333333;}#mermaid-svg-t18dtS7txX4qRuSm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-t18dtS7txX4qRuSm p{margin:0;}#mermaid-svg-t18dtS7txX4qRuSm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-t18dtS7txX4qRuSm .cluster-label text{fill:#333;}#mermaid-svg-t18dtS7txX4qRuSm .cluster-label span{color:#333;}#mermaid-svg-t18dtS7txX4qRuSm .cluster-label span p{background-color:transparent;}#mermaid-svg-t18dtS7txX4qRuSm .label text,#mermaid-svg-t18dtS7txX4qRuSm span{fill:#333;color:#333;}#mermaid-svg-t18dtS7txX4qRuSm .node rect,#mermaid-svg-t18dtS7txX4qRuSm .node circle,#mermaid-svg-t18dtS7txX4qRuSm .node ellipse,#mermaid-svg-t18dtS7txX4qRuSm .node polygon,#mermaid-svg-t18dtS7txX4qRuSm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-t18dtS7txX4qRuSm .rough-node .label text,#mermaid-svg-t18dtS7txX4qRuSm .node .label text,#mermaid-svg-t18dtS7txX4qRuSm .image-shape .label,#mermaid-svg-t18dtS7txX4qRuSm .icon-shape .label{text-anchor:middle;}#mermaid-svg-t18dtS7txX4qRuSm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-t18dtS7txX4qRuSm .rough-node .label,#mermaid-svg-t18dtS7txX4qRuSm .node .label,#mermaid-svg-t18dtS7txX4qRuSm .image-shape .label,#mermaid-svg-t18dtS7txX4qRuSm .icon-shape .label{text-align:center;}#mermaid-svg-t18dtS7txX4qRuSm .node.clickable{cursor:pointer;}#mermaid-svg-t18dtS7txX4qRuSm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-t18dtS7txX4qRuSm .arrowheadPath{fill:#333333;}#mermaid-svg-t18dtS7txX4qRuSm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-t18dtS7txX4qRuSm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-t18dtS7txX4qRuSm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t18dtS7txX4qRuSm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-t18dtS7txX4qRuSm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t18dtS7txX4qRuSm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-t18dtS7txX4qRuSm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-t18dtS7txX4qRuSm .cluster text{fill:#333;}#mermaid-svg-t18dtS7txX4qRuSm .cluster span{color:#333;}#mermaid-svg-t18dtS7txX4qRuSm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-t18dtS7txX4qRuSm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-t18dtS7txX4qRuSm rect.text{fill:none;stroke-width:0;}#mermaid-svg-t18dtS7txX4qRuSm .icon-shape,#mermaid-svg-t18dtS7txX4qRuSm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t18dtS7txX4qRuSm .icon-shape p,#mermaid-svg-t18dtS7txX4qRuSm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-t18dtS7txX4qRuSm .icon-shape .label rect,#mermaid-svg-t18dtS7txX4qRuSm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t18dtS7txX4qRuSm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-t18dtS7txX4qRuSm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-t18dtS7txX4qRuSm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:ShareExtensionAbility 的定位
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class ShareExtensionAbilExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现、验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码将不同扩展入口先统一为请求对象,再按 type 分发。这样可以避免将服务、卡片、分享逻辑全部写在一个生命周期回调里。

6. 先判断场景,再选择能力

如果用户正在浏览页面,使用 UIAbility;如果是系统扩展入口,则考虑使用对应的 ExtensionAbility。

补充流程图:
#mermaid-svg-g3JP8IA5nHpR8ffw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g3JP8IA5nHpR8ffw .error-icon{fill:#552222;}#mermaid-svg-g3JP8IA5nHpR8ffw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g3JP8IA5nHpR8ffw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g3JP8IA5nHpR8ffw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g3JP8IA5nHpR8ffw .marker.cross{stroke:#333333;}#mermaid-svg-g3JP8IA5nHpR8ffw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g3JP8IA5nHpR8ffw p{margin:0;}#mermaid-svg-g3JP8IA5nHpR8ffw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster-label text{fill:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster-label span{color:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster-label span p{background-color:transparent;}#mermaid-svg-g3JP8IA5nHpR8ffw .label text,#mermaid-svg-g3JP8IA5nHpR8ffw span{fill:#333;color:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw .node rect,#mermaid-svg-g3JP8IA5nHpR8ffw .node circle,#mermaid-svg-g3JP8IA5nHpR8ffw .node ellipse,#mermaid-svg-g3JP8IA5nHpR8ffw .node polygon,#mermaid-svg-g3JP8IA5nHpR8ffw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g3JP8IA5nHpR8ffw .rough-node .label text,#mermaid-svg-g3JP8IA5nHpR8ffw .node .label text,#mermaid-svg-g3JP8IA5nHpR8ffw .image-shape .label,#mermaid-svg-g3JP8IA5nHpR8ffw .icon-shape .label{text-anchor:middle;}#mermaid-svg-g3JP8IA5nHpR8ffw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g3JP8IA5nHpR8ffw .rough-node .label,#mermaid-svg-g3JP8IA5nHpR8ffw .node .label,#mermaid-svg-g3JP8IA5nHpR8ffw .image-shape .label,#mermaid-svg-g3JP8IA5nHpR8ffw .icon-shape .label{text-align:center;}#mermaid-svg-g3JP8IA5nHpR8ffw .node.clickable{cursor:pointer;}#mermaid-svg-g3JP8IA5nHpR8ffw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g3JP8IA5nHpR8ffw .arrowheadPath{fill:#333333;}#mermaid-svg-g3JP8IA5nHpR8ffw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g3JP8IA5nHpR8ffw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g3JP8IA5nHpR8ffw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g3JP8IA5nHpR8ffw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g3JP8IA5nHpR8ffw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g3JP8IA5nHpR8ffw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster text{fill:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw .cluster span{color:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g3JP8IA5nHpR8ffw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g3JP8IA5nHpR8ffw rect.text{fill:none;stroke-width:0;}#mermaid-svg-g3JP8IA5nHpR8ffw .icon-shape,#mermaid-svg-g3JP8IA5nHpR8ffw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g3JP8IA5nHpR8ffw .icon-shape p,#mermaid-svg-g3JP8IA5nHpR8ffw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g3JP8IA5nHpR8ffw .icon-shape .label rect,#mermaid-svg-g3JP8IA5nHpR8ffw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g3JP8IA5nHpR8ffw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g3JP8IA5nHpR8ffw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g3JP8IA5nHpR8ffw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:先判断场景,再选择能力
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 先判断场景_再选择能力ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码提供了一个可以迁移到 Stage 模型工程中的最小化写法。
  3. 这段代码将不同的扩展入口统一为请求对象,再根据 type 进行分发。这样可以避免将服务、卡片、分享等逻辑全部写在一个生命周期回调中。

7. 工程结构建议

建议把 Stage 相关代码按职责分层,不要把所有逻辑写在一个文件里。

text 复制代码
entry/src/main/ets
├── entryability
├── entryabilitystage
├── extensionability
├── pages
├── components
├── services
├── models
└── utils

目录解释:

  1. entryability 放 UIAbility 入口。
  2. entryabilitystage 放模块级初始化。
  3. extensionability 放服务、卡片、分享等扩展能力。
  4. pages 放 ArkUI 页面。
  5. services 放可复用业务服务。

8. 从 Demo 到项目时怎么拆

官方示例通常为了让读者快速跑通,会把代码写得比较集中。真实项目不能一直停留在 Demo 写法,否则页面一多就会出现三个问题:入口文件越来越大、页面事件处理越来越重、后台和前台逻辑互相影响。

更推荐的拆法是:

  1. AbilityStage 只处理模块级初始化,例如日志、配置、轻量服务注册。
  2. UIAbility 只处理界面入口,例如生命周期、窗口创建、首页加载、Want 参数接收。
  3. ExtensionAbility 只处理系统扩展入口,例如服务、卡片、分享等非普通页面场景。
  4. pages 只写页面状态和交互,不直接堆复杂业务。
  5. services 承担可复用业务,例如任务调度、缓存、配置读取、数据请求。

一个实用的判断标准是:如果这段代码离开当前页面后仍然有价值,就不要写死在页面里;如果这段代码只和启动入口有关,就不要放进组件里;如果这段代码需要系统以扩展方式调用,就不要强行放进 UIAbility

补充流程图:
#mermaid-svg-kO78yCeauIy63W80{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kO78yCeauIy63W80 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kO78yCeauIy63W80 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kO78yCeauIy63W80 .error-icon{fill:#552222;}#mermaid-svg-kO78yCeauIy63W80 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kO78yCeauIy63W80 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kO78yCeauIy63W80 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kO78yCeauIy63W80 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kO78yCeauIy63W80 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kO78yCeauIy63W80 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kO78yCeauIy63W80 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kO78yCeauIy63W80 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kO78yCeauIy63W80 .marker.cross{stroke:#333333;}#mermaid-svg-kO78yCeauIy63W80 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kO78yCeauIy63W80 p{margin:0;}#mermaid-svg-kO78yCeauIy63W80 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kO78yCeauIy63W80 .cluster-label text{fill:#333;}#mermaid-svg-kO78yCeauIy63W80 .cluster-label span{color:#333;}#mermaid-svg-kO78yCeauIy63W80 .cluster-label span p{background-color:transparent;}#mermaid-svg-kO78yCeauIy63W80 .label text,#mermaid-svg-kO78yCeauIy63W80 span{fill:#333;color:#333;}#mermaid-svg-kO78yCeauIy63W80 .node rect,#mermaid-svg-kO78yCeauIy63W80 .node circle,#mermaid-svg-kO78yCeauIy63W80 .node ellipse,#mermaid-svg-kO78yCeauIy63W80 .node polygon,#mermaid-svg-kO78yCeauIy63W80 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kO78yCeauIy63W80 .rough-node .label text,#mermaid-svg-kO78yCeauIy63W80 .node .label text,#mermaid-svg-kO78yCeauIy63W80 .image-shape .label,#mermaid-svg-kO78yCeauIy63W80 .icon-shape .label{text-anchor:middle;}#mermaid-svg-kO78yCeauIy63W80 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kO78yCeauIy63W80 .rough-node .label,#mermaid-svg-kO78yCeauIy63W80 .node .label,#mermaid-svg-kO78yCeauIy63W80 .image-shape .label,#mermaid-svg-kO78yCeauIy63W80 .icon-shape .label{text-align:center;}#mermaid-svg-kO78yCeauIy63W80 .node.clickable{cursor:pointer;}#mermaid-svg-kO78yCeauIy63W80 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kO78yCeauIy63W80 .arrowheadPath{fill:#333333;}#mermaid-svg-kO78yCeauIy63W80 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kO78yCeauIy63W80 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kO78yCeauIy63W80 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kO78yCeauIy63W80 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kO78yCeauIy63W80 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kO78yCeauIy63W80 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kO78yCeauIy63W80 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kO78yCeauIy63W80 .cluster text{fill:#333;}#mermaid-svg-kO78yCeauIy63W80 .cluster span{color:#333;}#mermaid-svg-kO78yCeauIy63W80 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kO78yCeauIy63W80 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kO78yCeauIy63W80 rect.text{fill:none;stroke-width:0;}#mermaid-svg-kO78yCeauIy63W80 .icon-shape,#mermaid-svg-kO78yCeauIy63W80 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kO78yCeauIy63W80 .icon-shape p,#mermaid-svg-kO78yCeauIy63W80 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kO78yCeauIy63W80 .icon-shape .label rect,#mermaid-svg-kO78yCeauIy63W80 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kO78yCeauIy63W80 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kO78yCeauIy63W80 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kO78yCeauIy63W80 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:从 Demo 到项目时怎么拆
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 从_Demo_到项目时怎么拆ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,避免读者只看到一段抽象描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码把不同扩展入口先统一成请求对象,再按 type 分发。这样可以避免把服务、卡片、分享逻辑全部写在一个生命周期回调里。

9. 实战代码

ServiceExtensionAbility 示例

ts 复制代码
import { ServiceExtensionAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class DemoServiceExtension extends ServiceExtensionAbility {
  onCreate(want: Want): void {
    hilog.info(0x0000, 'DemoService', 'service extension create');
  }

  onRequest(want: Want, startId: number): void {
    const taskId = want.parameters?.taskId as string ?? '';
    hilog.info(0x0000, 'DemoService', 'taskId=%{public}s startId=%{public}d', taskId, startId);
  }

  onDestroy(): void {
    hilog.info(0x0000, 'DemoService', 'service extension destroy');
  }
}

代码解释:

  1. 这段代码对应本节的最小可运行示例。
  2. 重点不是复制粘贴,而是理解它放在 Stage 工程中的位置。
  3. 扩展能力需要关注生命周期和请求参数,不要假设它会一直运行。

module.json5 声明扩展能力

json5 复制代码
{
  "extensionAbilities": [
    {
      "name": "DemoServiceExtension",
      "srcEntry": "./ets/extensionability/DemoServiceExtension.ets",
      "type": "service",
      "exported": false
    }
  ]
}

代码解释:

  1. 这段代码对应本节的最小可运行示例。
  2. 重点不是复制粘贴,而是理解它放在 Stage 工程中的位置。
  3. 扩展能力需要在模块配置中声明,type 要和实际场景对应。

10. 实战案例:做一个学习任务入口

下面用一个小案例把本章主题落到工程实践中。假设应用首页有一个"开始学习"按钮,点击后进入某个学习任务。页面只负责触发动作,任务参数和状态由服务层维护。

ts 复制代码
export interface StudyTask {
  id: string;
  title: string;
  source: string;
  createdAt: number;
}

export class StudyTaskService {
  private static currentTask?: StudyTask;

  static create(title: string, source: string): StudyTask {
    const task: StudyTask = {
      id: `${Date.now()}`,
      title,
      source,
      createdAt: Date.now()
    };
    StudyTaskService.currentTask = task;
    return task;
  }

  static getCurrent(): StudyTask | undefined {
    return StudyTaskService.currentTask;
  }
}

代码解释:

  1. 页面不直接构造业务对象,而是调用服务层创建任务。
  2. StudyTask 明确了任务字段,后续传参、缓存和日志都会更清晰。
  3. 服务层可扩展为持久化版本,不影响页面结构。

页面使用方式如下:

ts 复制代码
@Entry
@Component
struct Index {
  @State latestTitle: string = '暂无任务';

  build() {
    Column({ space: 16 }) {
      Text('Stage 模型学习任务')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)

      Text(this.latestTitle)
        .fontSize(16)
        .fontColor('#5A6B7B')

      Button('开始学习')
        .height(48)
        .onClick(() => {
          const task = StudyTaskService.create('Stage 模型实战', 'home');
          this.latestTitle = task.title;
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

这段页面代码只做三件事:显示标题、响应点击、更新 UI。真正的任务对象由服务层创建,这就是从 Demo 走向工程化的第一步。

11. 常见问题

11.1 代码写了但没有生效

先检查配置文件是否声明了对应入口,再检查路径大小写是否一致。Stage 工程里很多问题不是代码逻辑错误,而是配置没有把类和系统入口连接起来。

11.2 页面和 Ability 职责混在一起

页面负责展示和交互,Ability 负责入口和生命周期。业务逻辑如果多个页面都要使用,应该下沉到 services 层。

11.3 后台能力被系统限制

后台任务不能按照桌面端思路设计。要先判断任务是否必须立即执行、用户是否能感知、是否可以延迟,再选择对应方案。

11.4 生命周期日志看不懂

建议为每个入口统一设置 tag,例如 EntryAbilityAbilityStageTaskService。调试时按 tag 过滤日志,先看入口是否执行,再看页面是否加载,最后看业务服务是否被调用。

11.5 页面能打开但状态不对

优先检查状态是否应该放在页面中。如果状态需要跨页面共享,就放进服务层或持久化存储;如果状态只影响当前页面,再使用 @State 管理。

补充流程图:
#mermaid-svg-KCoMgii4fIFruNfJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KCoMgii4fIFruNfJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KCoMgii4fIFruNfJ .error-icon{fill:#552222;}#mermaid-svg-KCoMgii4fIFruNfJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KCoMgii4fIFruNfJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KCoMgii4fIFruNfJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KCoMgii4fIFruNfJ .marker.cross{stroke:#333333;}#mermaid-svg-KCoMgii4fIFruNfJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KCoMgii4fIFruNfJ p{margin:0;}#mermaid-svg-KCoMgii4fIFruNfJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KCoMgii4fIFruNfJ .cluster-label text{fill:#333;}#mermaid-svg-KCoMgii4fIFruNfJ .cluster-label span{color:#333;}#mermaid-svg-KCoMgii4fIFruNfJ .cluster-label span p{background-color:transparent;}#mermaid-svg-KCoMgii4fIFruNfJ .label text,#mermaid-svg-KCoMgii4fIFruNfJ span{fill:#333;color:#333;}#mermaid-svg-KCoMgii4fIFruNfJ .node rect,#mermaid-svg-KCoMgii4fIFruNfJ .node circle,#mermaid-svg-KCoMgii4fIFruNfJ .node ellipse,#mermaid-svg-KCoMgii4fIFruNfJ .node polygon,#mermaid-svg-KCoMgii4fIFruNfJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KCoMgii4fIFruNfJ .rough-node .label text,#mermaid-svg-KCoMgii4fIFruNfJ .node .label text,#mermaid-svg-KCoMgii4fIFruNfJ .image-shape .label,#mermaid-svg-KCoMgii4fIFruNfJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-KCoMgii4fIFruNfJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KCoMgii4fIFruNfJ .rough-node .label,#mermaid-svg-KCoMgii4fIFruNfJ .node .label,#mermaid-svg-KCoMgii4fIFruNfJ .image-shape .label,#mermaid-svg-KCoMgii4fIFruNfJ .icon-shape .label{text-align:center;}#mermaid-svg-KCoMgii4fIFruNfJ .node.clickable{cursor:pointer;}#mermaid-svg-KCoMgii4fIFruNfJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KCoMgii4fIFruNfJ .arrowheadPath{fill:#333333;}#mermaid-svg-KCoMgii4fIFruNfJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KCoMgii4fIFruNfJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KCoMgii4fIFruNfJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KCoMgii4fIFruNfJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KCoMgii4fIFruNfJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KCoMgii4fIFruNfJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KCoMgii4fIFruNfJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KCoMgii4fIFruNfJ .cluster text{fill:#333;}#mermaid-svg-KCoMgii4fIFruNfJ .cluster span{color:#333;}#mermaid-svg-KCoMgii4fIFruNfJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KCoMgii4fIFruNfJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KCoMgii4fIFruNfJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-KCoMgii4fIFruNfJ .icon-shape,#mermaid-svg-KCoMgii4fIFruNfJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KCoMgii4fIFruNfJ .icon-shape p,#mermaid-svg-KCoMgii4fIFruNfJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KCoMgii4fIFruNfJ .icon-shape .label rect,#mermaid-svg-KCoMgii4fIFruNfJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KCoMgii4fIFruNfJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KCoMgii4fIFruNfJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KCoMgii4fIFruNfJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:常见问题
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 常见问题ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,避免读者只看到一段抽象描述。
  2. 示例代码提供了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码将不同的扩展入口统一为请求对象,再按 type 进行分发。这样可以避免将服务、卡片、分享逻辑全部写在一个生命周期回调中。

12. 运行验证清单

  1. 修改 module.json5 后重新构建安装。
  2. 给关键生命周期加 hilog
  3. 页面路径统一使用 pages/页面名,不要写 .ets 后缀。
  4. 新增页面后同步更新 main_pages.json
  5. 后台任务应记录任务状态,避免中断后无法恢复。
  6. 将可复用逻辑放入 services,避免散落在页面事件中。

补充流程图:
#mermaid-svg-FndXqh0zXN9odQGf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FndXqh0zXN9odQGf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FndXqh0zXN9odQGf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FndXqh0zXN9odQGf .error-icon{fill:#552222;}#mermaid-svg-FndXqh0zXN9odQGf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FndXqh0zXN9odQGf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FndXqh0zXN9odQGf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FndXqh0zXN9odQGf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FndXqh0zXN9odQGf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FndXqh0zXN9odQGf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FndXqh0zXN9odQGf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FndXqh0zXN9odQGf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FndXqh0zXN9odQGf .marker.cross{stroke:#333333;}#mermaid-svg-FndXqh0zXN9odQGf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FndXqh0zXN9odQGf p{margin:0;}#mermaid-svg-FndXqh0zXN9odQGf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FndXqh0zXN9odQGf .cluster-label text{fill:#333;}#mermaid-svg-FndXqh0zXN9odQGf .cluster-label span{color:#333;}#mermaid-svg-FndXqh0zXN9odQGf .cluster-label span p{background-color:transparent;}#mermaid-svg-FndXqh0zXN9odQGf .label text,#mermaid-svg-FndXqh0zXN9odQGf span{fill:#333;color:#333;}#mermaid-svg-FndXqh0zXN9odQGf .node rect,#mermaid-svg-FndXqh0zXN9odQGf .node circle,#mermaid-svg-FndXqh0zXN9odQGf .node ellipse,#mermaid-svg-FndXqh0zXN9odQGf .node polygon,#mermaid-svg-FndXqh0zXN9odQGf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FndXqh0zXN9odQGf .rough-node .label text,#mermaid-svg-FndXqh0zXN9odQGf .node .label text,#mermaid-svg-FndXqh0zXN9odQGf .image-shape .label,#mermaid-svg-FndXqh0zXN9odQGf .icon-shape .label{text-anchor:middle;}#mermaid-svg-FndXqh0zXN9odQGf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FndXqh0zXN9odQGf .rough-node .label,#mermaid-svg-FndXqh0zXN9odQGf .node .label,#mermaid-svg-FndXqh0zXN9odQGf .image-shape .label,#mermaid-svg-FndXqh0zXN9odQGf .icon-shape .label{text-align:center;}#mermaid-svg-FndXqh0zXN9odQGf .node.clickable{cursor:pointer;}#mermaid-svg-FndXqh0zXN9odQGf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FndXqh0zXN9odQGf .arrowheadPath{fill:#333333;}#mermaid-svg-FndXqh0zXN9odQGf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FndXqh0zXN9odQGf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FndXqh0zXN9odQGf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FndXqh0zXN9odQGf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FndXqh0zXN9odQGf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FndXqh0zXN9odQGf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FndXqh0zXN9odQGf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FndXqh0zXN9odQGf .cluster text{fill:#333;}#mermaid-svg-FndXqh0zXN9odQGf .cluster span{color:#333;}#mermaid-svg-FndXqh0zXN9odQGf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FndXqh0zXN9odQGf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FndXqh0zXN9odQGf rect.text{fill:none;stroke-width:0;}#mermaid-svg-FndXqh0zXN9odQGf .icon-shape,#mermaid-svg-FndXqh0zXN9odQGf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FndXqh0zXN9odQGf .icon-shape p,#mermaid-svg-FndXqh0zXN9odQGf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FndXqh0zXN9odQGf .icon-shape .label rect,#mermaid-svg-FndXqh0zXN9odQGf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FndXqh0zXN9odQGf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FndXqh0zXN9odQGf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FndXqh0zXN9odQGf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:运行验证清单
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 运行验证清单ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码提供了一个可迁移到 Stage 工程中的最小实现。
  3. 流程图将本小节从概念、代码位置、实现到验证串联起来,使读者不会只看到一段抽象描述。

13. 读者练习

建议你按下面步骤自己做一遍:

  1. 在现有 Stage 工程里新建一个 services 目录。
  2. 把页面里的任务创建逻辑移动到 StudyTaskService
  3. 在 Ability 生命周期里加入日志记录。
  4. 新增一个页面,验证页面跳转和服务层状态是否正常。
  5. 把可复用文字移动到 string.json
  6. 把可复用颜色移动到 color.json
  7. 重新运行应用,确认入口、页面、服务层职责清晰。

如果这 7 步都能顺利完成,说明你已经不是只会修改默认模板,而是开始按照工程化的方式组织 Stage 应用了。

补充流程图:
#mermaid-svg-FtUTU2GLgnNzkmgv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FtUTU2GLgnNzkmgv .error-icon{fill:#552222;}#mermaid-svg-FtUTU2GLgnNzkmgv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FtUTU2GLgnNzkmgv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FtUTU2GLgnNzkmgv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FtUTU2GLgnNzkmgv .marker.cross{stroke:#333333;}#mermaid-svg-FtUTU2GLgnNzkmgv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FtUTU2GLgnNzkmgv p{margin:0;}#mermaid-svg-FtUTU2GLgnNzkmgv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster-label text{fill:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster-label span{color:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster-label span p{background-color:transparent;}#mermaid-svg-FtUTU2GLgnNzkmgv .label text,#mermaid-svg-FtUTU2GLgnNzkmgv span{fill:#333;color:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv .node rect,#mermaid-svg-FtUTU2GLgnNzkmgv .node circle,#mermaid-svg-FtUTU2GLgnNzkmgv .node ellipse,#mermaid-svg-FtUTU2GLgnNzkmgv .node polygon,#mermaid-svg-FtUTU2GLgnNzkmgv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FtUTU2GLgnNzkmgv .rough-node .label text,#mermaid-svg-FtUTU2GLgnNzkmgv .node .label text,#mermaid-svg-FtUTU2GLgnNzkmgv .image-shape .label,#mermaid-svg-FtUTU2GLgnNzkmgv .icon-shape .label{text-anchor:middle;}#mermaid-svg-FtUTU2GLgnNzkmgv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FtUTU2GLgnNzkmgv .rough-node .label,#mermaid-svg-FtUTU2GLgnNzkmgv .node .label,#mermaid-svg-FtUTU2GLgnNzkmgv .image-shape .label,#mermaid-svg-FtUTU2GLgnNzkmgv .icon-shape .label{text-align:center;}#mermaid-svg-FtUTU2GLgnNzkmgv .node.clickable{cursor:pointer;}#mermaid-svg-FtUTU2GLgnNzkmgv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FtUTU2GLgnNzkmgv .arrowheadPath{fill:#333333;}#mermaid-svg-FtUTU2GLgnNzkmgv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FtUTU2GLgnNzkmgv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FtUTU2GLgnNzkmgv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FtUTU2GLgnNzkmgv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FtUTU2GLgnNzkmgv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FtUTU2GLgnNzkmgv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster text{fill:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv .cluster span{color:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FtUTU2GLgnNzkmgv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FtUTU2GLgnNzkmgv rect.text{fill:none;stroke-width:0;}#mermaid-svg-FtUTU2GLgnNzkmgv .icon-shape,#mermaid-svg-FtUTU2GLgnNzkmgv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FtUTU2GLgnNzkmgv .icon-shape p,#mermaid-svg-FtUTU2GLgnNzkmgv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FtUTU2GLgnNzkmgv .icon-shape .label rect,#mermaid-svg-FtUTU2GLgnNzkmgv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FtUTU2GLgnNzkmgv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FtUTU2GLgnNzkmgv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FtUTU2GLgnNzkmgv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:读者练习
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 读者练习ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码定位、实现到验证串联起来,使读者不会只看到一段抽象的描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现示例。
  3. 这段代码将不同的扩展入口统一为请求对象,再根据类型进行分发。这样可以避免将服务、卡片、分享等逻辑全部写在一个生命周期回调函数中。

14. 本章总结

ExtensionAbility 面向非页面扩展场景。理解它的边界,才能避免把后台服务、卡片和分享入口都塞进 UIAbility。真正写项目时,不要先问"这个 API 怎么调",而要先问"这个能力应该属于哪一层"。职责边界清晰,Stage 工程才会越写越稳。

参考资料

  1. 华为开发者文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/stage-model-development-overview
  2. 华为开发者文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-background-tasks-1
  3. 华为开发者文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/start-with-ets-stage
    补充流程图:

#mermaid-svg-brymAIGRRakJUhdT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-brymAIGRRakJUhdT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-brymAIGRRakJUhdT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-brymAIGRRakJUhdT .error-icon{fill:#552222;}#mermaid-svg-brymAIGRRakJUhdT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-brymAIGRRakJUhdT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-brymAIGRRakJUhdT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-brymAIGRRakJUhdT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-brymAIGRRakJUhdT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-brymAIGRRakJUhdT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-brymAIGRRakJUhdT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-brymAIGRRakJUhdT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-brymAIGRRakJUhdT .marker.cross{stroke:#333333;}#mermaid-svg-brymAIGRRakJUhdT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-brymAIGRRakJUhdT p{margin:0;}#mermaid-svg-brymAIGRRakJUhdT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-brymAIGRRakJUhdT .cluster-label text{fill:#333;}#mermaid-svg-brymAIGRRakJUhdT .cluster-label span{color:#333;}#mermaid-svg-brymAIGRRakJUhdT .cluster-label span p{background-color:transparent;}#mermaid-svg-brymAIGRRakJUhdT .label text,#mermaid-svg-brymAIGRRakJUhdT span{fill:#333;color:#333;}#mermaid-svg-brymAIGRRakJUhdT .node rect,#mermaid-svg-brymAIGRRakJUhdT .node circle,#mermaid-svg-brymAIGRRakJUhdT .node ellipse,#mermaid-svg-brymAIGRRakJUhdT .node polygon,#mermaid-svg-brymAIGRRakJUhdT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-brymAIGRRakJUhdT .rough-node .label text,#mermaid-svg-brymAIGRRakJUhdT .node .label text,#mermaid-svg-brymAIGRRakJUhdT .image-shape .label,#mermaid-svg-brymAIGRRakJUhdT .icon-shape .label{text-anchor:middle;}#mermaid-svg-brymAIGRRakJUhdT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-brymAIGRRakJUhdT .rough-node .label,#mermaid-svg-brymAIGRRakJUhdT .node .label,#mermaid-svg-brymAIGRRakJUhdT .image-shape .label,#mermaid-svg-brymAIGRRakJUhdT .icon-shape .label{text-align:center;}#mermaid-svg-brymAIGRRakJUhdT .node.clickable{cursor:pointer;}#mermaid-svg-brymAIGRRakJUhdT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-brymAIGRRakJUhdT .arrowheadPath{fill:#333333;}#mermaid-svg-brymAIGRRakJUhdT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-brymAIGRRakJUhdT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-brymAIGRRakJUhdT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-brymAIGRRakJUhdT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-brymAIGRRakJUhdT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-brymAIGRRakJUhdT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-brymAIGRRakJUhdT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-brymAIGRRakJUhdT .cluster text{fill:#333;}#mermaid-svg-brymAIGRRakJUhdT .cluster span{color:#333;}#mermaid-svg-brymAIGRRakJUhdT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-brymAIGRRakJUhdT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-brymAIGRRakJUhdT rect.text{fill:none;stroke-width:0;}#mermaid-svg-brymAIGRRakJUhdT .icon-shape,#mermaid-svg-brymAIGRRakJUhdT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-brymAIGRRakJUhdT .icon-shape p,#mermaid-svg-brymAIGRRakJUhdT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-brymAIGRRakJUhdT .icon-shape .label rect,#mermaid-svg-brymAIGRRakJUhdT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-brymAIGRRakJUhdT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-brymAIGRRakJUhdT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-brymAIGRRakJUhdT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理解:本章总结
确定代码位置
编写最小示例
运行验证
沉淀到工程结构

补充代码:

ts 复制代码
interface ExtensionRequest {
  type: 'service' | 'form' | 'share';
  payload: Record<string, string>;
}

export class 本章总结ExtensionRouter {
  static route(request: ExtensionRequest): string {
    if (request.type === 'service') {
      return 'handle service request';
    }
    if (request.type === 'form') {
      return 'update form content';
    }
    return 'handle share content';
  }
}

代码解释:

  1. 流程图将本小节从概念、代码位置、实现到验证串联起来,使读者不会只看到一段抽象描述。
  2. 示例代码给出了一个可以迁移到 Stage 工程中的最小实现。
  3. 这段代码将不同的扩展入口统一为请求对象,再按 type 进行分发。这样可以避免将服务、卡片、分享逻辑全部写在一个生命周期回调中。