Administrator
发布于 2026-09-03 / 8,867 阅读
0
0

鸿蒙百篇系列(86/100):原子化服务进阶——卡片通信、分包与无状态架构

系列:鸿蒙开发从入门到专家 · 第 86 篇

原子化服务(Atomic Service)是鸿蒙生态里很独特的一种应用形态:免安装、即点即用、以卡片为入口。第 2 批我们已经讲过基础概念,本篇进入进阶话题——卡片与主体的双向通信、分包策略、以及无状态架构下的生命周期管理。这些都是把元服务从 demo 做成可上架产品的关键。

一、元服务的核心结构再回顾

一个元服务通常包含:

  • EntryAbility:服务入口,用户点击后进入的完整页面。

  • FormExtensionAbility:卡片扩展能力,负责桌面、负一屏等位置的卡片内容。

  • ServiceExtensionAbility:后台服务能力,处理不需要 UI 的长时任务或数据同步。

和普通应用相比,元服务最大的不同是"安装 Free"和"生命周期更短"。系统可能随时回收元服务进程,开发者不能假设它一直活着。

特性

传统应用

原子化服务

安装

必须下载安装

免安装

入口

桌面图标

卡片、搜索、语音、分享

包体限制

无严格限制

首包 + 依赖不超过 2MB/包

生命周期

可后台保活

即用即走,易被回收

状态保存

可内存常驻

应尽量无状态

二、卡片与主体的双向通信

卡片本身不能执行复杂业务逻辑,它需要和元服务主体通信。通信方式主要有两种:

2.1 卡片点击拉起主体

卡片点击时可以通过 routerEvent 或 messageEvent 触发对应动作。例如点击天气卡片打开详情页:

// WidgetCard.ets
@Entry
@Component
struct WidgetCard {
  @StorageProp('formId') formId: string = '';
  @StorageProp('cityName') cityName: string = '北京';

  build() {
    Column() {
      Text(`${this.cityName} 天气`)
        .fontSize(14)
        .fontColor('#666')

      Button('查看详情')
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility',
            params: { formId: this.formId, city: this.cityName }
          });
        })
    }
    .width('100%')
    .height('100%')
    .padding(12)
  }
}

2.2 主体更新卡片

当后端数据变化时,主体可以主动更新卡片:

import { formProvider, formBindingData } from '@kit.FormKit';

async function updateWeatherCard(formId: string, weather: object) {
  const formData = formBindingData.createFormBindingData({
    cityName: weather.city,
    temperature: `${weather.temp}°C`,
    weatherDesc: weather.desc,
    updateTime: new Date().toLocaleTimeString()
  });
  await formProvider.updateForm(formId, formData);
}

2.3 卡片触发主体后台处理

如果卡片需要刷新数据但不想拉起完整页面,可以用 messageEvent:

Button('刷新')
  .onClick(() => {
    postCardAction(this, {
      action: 'message',
      abilityName: 'EntryAbility',
      params: { action: 'refresh', formId: this.formId }
    });
  })

主体在 onFormEvent 中接收并处理:

import { FormExtensionAbility, formBindingData } from '@kit.FormKit';

export default class WeatherWidgetAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string) {
    const params = JSON.parse(message);
    if (params.action === 'refresh') {
      // 请求网络,更新卡片
    }
  }
}

三、分包策略

元服务对单个包大小有限制:单个 HAP(含依赖的 HSP)不能超过 2MB。超过就需要分包。

3.1 分包原则

  • 首包:Entry HAP,包含首页和启动必需资源。

  • 功能分包:按功能模块拆分,用户用到时再加载。

  • HSP 共享包:公共代码和资源抽成 HSP,减少重复。

3.2 module.json5 中的关键字段

{
  "module": {
    "name": "entry",
    "type": "atomicService",
    "installationFree": true,
    "deliveryWithInstall": true,
    "dependencies": [
      { "moduleName": "library-common", "bundleName": "com.example.service" }
    ]
  }
}

分包时要注意依赖关系不能形成循环,否则会导致加载失败。建议在项目初期就把公共模块独立出来,而不是等到包体积告警再重构。

四、无状态架构

元服务被系统回收后,内存中的状态会丢失。设计时要避免把重要状态放在全局变量或单例中。

4.1 状态保存到持久化存储

import { preferences } from '@kit.ArkData';

const store = preferences.getPreferencesSync(getContext(), { name: 'service_cache' });

function saveState(key: string, value: object) {
  store.putSync(key, JSON.stringify(value));
  store.flush();
}

function loadState<T>(key: string): T | undefined {
  const raw = store.getSync(key, '') as string;
  return raw ? JSON.parse(raw) : undefined;
}

4.2 启动时恢复状态

EntryAbility 的 onCreate 或 onNewWant 中,从持久化存储恢复必要状态,不要依赖内存中的缓存。

import { UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want) {
    const lastCity = loadState<string>('lastCity') || '北京';
    this.processLaunchParams(want, lastCity);
  }
}

4.3 避免全局单例

元服务中尽量不要使用全局单例保存业务数据。如果必须共享,使用 AppStorage 或 LocalStorage,并做好空值处理。

五、Want 机制与跨服务调用

元服务之间可以通过 Want 互相拉起,也可以用 openAtomicService 打开其他元服务。

import { common, AtomicServiceOptions, wantConstant } from '@kit.AbilityKit';

async function openPartnerService(context: common.UIAbilityContext, appId: string) {
  const options: AtomicServiceOptions = {
    flags: wantConstant.Flags.FLAG_INSTALL_ON_DEMAND,
    parameters: { source: 'my_service', productId: '12345' }
  };
  try {
    const result = await context.openAtomicService(appId, options);
    console.info('open success');
  } catch (err) {
    console.error(`open failed: ${err.message}`);
  }
}

跨服务调用时要注意参数大小限制,建议只传必要 ID,详细数据通过后端或分布式数据服务共享。

六、实战:一个电商秒杀元服务

以"限时秒杀提醒"元服务为例:

  1. 用户从桌面卡片看到秒杀倒计时和商品图。

  2. 卡片定时向主体请求最新价格和库存。

  3. 秒杀开始前 5 分钟,系统通过事件推荐弹出提醒。

  4. 用户点击卡片进入 EntryAbility,完成下单或加购。

  5. 下单完成后,主体更新卡片状态为"已预约"或"已售罄"。

这个场景里,卡片是流量入口,主体是交易闭环,后台服务负责定时刷新和消息触达。三者边界清楚,才能做出稳定的元服务。

七、小结

原子化服务的进阶重点不是更复杂的 UI,而是"轻、快、稳":轻量包体、快速启动、稳定恢复。卡片与主体的通信、分包策略、无状态架构是三个必须掌握的能力。元服务被回收不可怕,可怕的是回收后用户再进来发现状态丢失、流程断裂。

下一篇我们讨论元服务的商业化路径,看看如何从"有意思"变成"有收入"。

参考阅读

  • 华为开发者联盟:原子化服务开发指南

  • 华为开发者联盟:Form Kit 开发指南

  • 华为开发者联盟:Ability Kit 开发指南


评论