前言

普通卡片能点播放、收藏、开始运动,但这些动作不应该直接写在卡片 UI 里。卡片只是桌面组件,真正的业务应该回到应用侧处理。

当前项目把这件事统一交给 CardActionHandler。这篇就带你把这条链路看明白。

效果图

音乐卡片里的播放、切歌、收藏这些动作,最后都应该被应用侧统一接住再处理。

音乐卡片动作分发效果

先看这几个文件

  • entry/src/main/ets/utils/ActionUtils.ets
  • entry/src/main/ets/utils/CardActionHandler.ets
  • entry/src/main/ets/entryability/EntryAbility.ets
  • entry/src/main/ets/model/common/FormCardConstant.ets

这四个文件连起来就是:卡片发动作,应用接动作,业务分发。

动作类型先统一定义

FormCardConstant.ets 里定义了:

export enum CardActionType {
  PLAY_ACTION = 'playAction',
  COLLECT_ACTION = 'collectAction',
  REQUEST_UPDATE = 'requestUpdate',
  EXERCISE_ACTION = 'exerciseAction',
}

小白可以这样理解:

  • PLAY_ACTION:音乐播放、暂停、上一首、下一首。
  • COLLECT_ACTION:音乐收藏或取消收藏。
  • REQUEST_UPDATE:新卡片主动请求当前数据。
  • EXERCISE_ACTION:运动卡片开始、结束、重置。

把动作写成枚举的好处是:不容易写错字符串,也方便全项目搜索。

卡片侧怎么发 CALL

ActionUtils.ets 里有音乐播放动作:

public playByAction(component: object, type: PlayActionType, formId: string): void {
  postCardAction(component, {
    action: FormCarAction.CALL,
    abilityName: ENTRY_ABILITY,
    params: {
      method: 'cardAction',
      actionType: CardActionType.PLAY_ACTION,
      playActionType: type,
      formId: formId,
    },
  });
}

这里的重点是 CALL

CALL 表示“我要调用应用侧能力”。目标 Ability 是:

abilityName: ENTRY_ABILITY

也就是项目里的 LiveCardAbility

EntryAbility 负责注册回调

EntryAbility.ets 里有:

CardActionHandler.setContext(this.context);
this.callee.on('cardAction', CardActionHandler.getHandler());

这两行很关键。

第一行把 context 交给 CardActionHandler,后面它才能访问数据库、文件、事件总线等能力。

第二行注册 cardAction 回调。卡片侧发来的 method: 'cardAction',最终就会走到这里。

应用销毁时还要解绑:

Technical framework diagram. Clean lines, structur

this.callee.off('cardAction');

CardActionHandler 怎么分发

核心代码是:

private cardActionCall = (data: rpc.MessageSequence): null => {
  let params: Record<string, string> = JSON.parse(data.readString());

  switch (params.actionType) {
    case CardActionType.PLAY_ACTION:
      this.handlePlayAction(params);
      break;
    case CardActionType.COLLECT_ACTION:
      this.handleCollectAction(params);
      break;
    case CardActionType.REQUEST_UPDATE:
      this.handleRequestUpdate(params);
      break;
    case CardActionType.EXERCISE_ACTION:
      this.handleExerciseAction(params);
      break;
  }
  return null;
};

A hand-drawn doodle illustration on pure white pap

它的职责很清楚:解析参数,然后根据 actionType 分发。

不要把大量业务逻辑都塞进 cardActionCall()。项目里做得不错,每个动作都有独立方法。

以播放动作为例

private handlePlayAction(params: Record<string, string>): void {
  if (params.playActionType) {
    let playActionType: PlayActionType = params.playActionType as PlayActionType;
    MediaService.getInstance().initAndPlayByAction(playActionType);
  }
}

这段代码没有自己操作播放器,而是交给 MediaService

这就是合理分层:

CardActionHandler:分发动作
MediaService:处理音乐业务
FormUtils:刷新桌面卡片
SongRdbHelper:保存歌曲数据

新手加动作怎么做

假设你要加一个天气刷新动作,可以按这个顺序:

  1. CardActionType 增加:
WEATHER_REFRESH = 'weatherRefresh'
  1. ActionUtils 增加发送方法:
public refreshWeather(component: object, formId: string): void {
  postCardAction(component, {
    action: FormCarAction.CALL,
    abilityName: ENTRY_ABILITY,
    params: {
      method: 'cardAction',
      actionType: CardActionType.WEATHER_REFRESH,
      formId
    }
  });
}
  1. CardActionHandler 里加分支:
case CardActionType.WEATHER_REFRESH:
  this.handleWeatherRefresh(params);
  break;

排查清单

动作没反应时这样查:

  1. 卡片点击事件有没有执行。
  2. postCardActionmethod 是否是 cardAction
  3. EntryAbility 是否注册了 this.callee.on('cardAction', ...)
  4. CardActionHandler 是否收到 actionType
  5. switch 里有没有对应分支。
  6. 真正业务方法有没有报错。

写在最后

CardActionHandler 的价值是“收口”。卡片越多,动作越多,如果没有统一入口,项目很快会乱。

小白记住:卡片发动作,Handler 分发动作,业务服务处理动作。

Logo

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

更多推荐