经验分享

在这里插入图片描述

哈喽,各位 51CTO 的博友们,大家好!我是陈杨,可能有部分朋友对我有点印象,但更多新朋友还不认识我,今天正好借这个机会,和大家好好认识一下,顺便聊聊我从前端工程师成功转型鸿蒙开发的那些事儿。

算下来,我已经有八年前端开发经验,同时坚持写技术文章也有六年了,这些经历在我的博客里都能查到。曾经我以为,只要把前端技术学深学透,就能稳稳守住自己的 “饭碗”。但这两年,这种想法被越来越强烈的危机感打破 —— 总担心自己哪天就跟不上前端的迭代节奏,而且手里没有能独立运营的项目,被行业淘汰似乎只是时间问题。

也正因这份居安思危的心态,三年前我下定决心转型,当时的目标很明确:开发属于自己的产品,做好宣传、搭建用户社群,最终实现更自主的职业价值。

转型之初,我也踩过不少技术选型的坑。iOS 开发、安卓开发、各类跨端框架,我都逐一了解过,但最终都放弃了,原因有三点:

  1. 学习成本太高:眼看就要到三十岁,时间和精力都耗不起,没法投入大量成本去啃全新的技术栈;
  2. 生态市场饱和:iOS 和安卓的市场格局早已定型,新人想做出点成绩太难,很容易打击积极性;
  3. 兴趣驱动不足:说起来也奇怪,我对安卓和 iOS 开发就是提不起兴趣,哈哈哈。

排除了这些方向,我也考虑过继续深耕 Windows 或网页开发,但实在是提不起劲。就在我纠结之际,鸿蒙 API9 的发布,彻底点亮了我的转型之路。说实话,在 API9 之前,鸿蒙用的还是 Java 语法,我当时瞅都不瞅 —— 毕竟在我看来,那和安卓没什么两样(这话可能有点直接,大家轻喷)。

直到 ArkTS 框架的出现,我一眼就来了精神!这语法简直和 VUE/React+TS 如出一辙,对我这个老前端来说,这不就是信手拈来的事儿吗?我甚至没有进行系统性学习,就对着官方文档啃了三天,直接跑通了我的第一个鸿蒙 APP。那一刻,我真的觉得天时地利人和全凑齐了:

  1. 学习门槛低:鸿蒙新框架和前端技术无缝衔接,几乎没花多少时间成本就上手了;
  2. 市场机会大:当时鸿蒙生态才刚刚起步,整个市场都是蓝海,对我们这种转型开发者来说,处处都是机遇。

从 2023 年正式入局鸿蒙开发,到现在已经快三年了。这三年里,我实实在在做了五件事,也拿到了一些成果:

  1. 封装开源组件库:打造了鸿蒙生态下的开源图表组件库 ——莓创图表,这也是我在鸿蒙社区的第一个开源作品,借此结识了一大批志同道合的鸿蒙开发者;
  2. 参赛验证实力:通过参加各类鸿蒙开发赛事检验学习成果,不仅拿到了不少奖项,还一路闯进了创新赛和极客挑战赛的总决赛;
  3. 写文巩固影响力:坚持输出鸿蒙技术文章,既能巩固自己的知识体系,也能扩大个人影响力,反过来又倒逼自己不断学习、持续进步;
  4. 深耕开发者社区:积极参与社区活动,主动分享技术、解答问题,为鸿蒙生态添砖加瓦,也有幸成为了一名鸿蒙极客
  5. 开发上架多款 APP:去年一年,我和团队陆续开发了多款鸿蒙应用,目前已经成功上架十一款,也靠着这些产品赚到了转型后的第一桶金。感兴趣的朋友可以去体验一下,比如 JLPTREFLEX PRO国潮纸刻Wss 直连ZenithDocs Pro圣诞相册CSS 特效这些

以上就是我转型鸿蒙开发的一些经验。这三年的经历,让我找回了刚毕业时那种全身心投入做一件事的冲劲。今年我还有不少重要规划,接下来的日子里,也会持续在平台分享鸿蒙相关的技术干货,不管是想转型鸿蒙的朋友,还是刚入门的萌新,都可以来找我交流,咱们一起学习、一起进步!

当然,今天的重点还是技术分享。其他的话题大家感兴趣的话可以私聊我,话不多说,咱们正式进入主题 —— 今天我会以我们团队的主力 APP 指令魔方 为例,给大家拆解鸿蒙的各类特性 API,讲讲这些 API 到底该怎么用、使用过程中会踩哪些坑,还有哪些可以快速落地的实战案例。

马上开始!

鸿蒙卡片的开发-全流程开发

鸿蒙卡片是指令魔方中最重要的一个环节,他可以实现用户在桌面就可以操作某个指令,来快速实现某个功能。大大提升了我们使用手机的效率,而且还可以美化桌面。同时ArkTS卡片也是HarmonyOS生态中轻量级的交互组件,能在宿主应用(如桌面)直接展示核心信息并支持轻量化交互,无需启动完整应用。

这次我们基于 HarmonyOS 官方文档,从概念、创建、配置、生命周期到进程模型,带大家完整掌握 ArkTS 卡片开发,包含实操代码和关键原理图示。

一、ArkTS卡片核心概述

1.1 核心亮点

ArkTS卡片相比传统JS卡片,在开发效率和能力上有显著提升:

  • 统一开发范式:卡片与应用页面共享ArkTS UI布局逻辑,比如应用中用的Column、Row布局可直接复用到卡片,无需单独适配,大幅减少重复开发。
  • 能力增强:新增三大关键能力——支持属性动画/显式动画(让交互更流畅)、开放Canvas画布(自定义绘制复杂图形)、允许运行逻辑代码(业务逻辑可在卡片内闭环,比如本地数据处理)。
1.2 实现原理

ArkTS卡片的运行依赖4个核心角色,交互流程如图所示:

  • 卡片使用方:显示卡片的宿主应用(目前仅系统应用,如桌面),控制卡片展示位置。
  • 卡片提供方:开发卡片的应用,负责卡片的UI布局、数据内容和点击事件逻辑。
  • 卡片管理服务:系统级常驻服务,提供formProvider接口,管理卡片生命周期(如创建、刷新、删除)和周期性刷新(定时/定点)。
  • 卡片渲染服务:管理所有卡片的渲染实例,每个宿主卡片组件对应一个渲染实例;实例运行在ArkTS虚拟机中,不同应用的渲染实例隔离(避免资源冲突),同一应用的实例共享globalThis对象。

关键区别:JS卡片不支持内置逻辑代码,而ArkTS卡片通过“渲染服务+虚拟机隔离”,既支持逻辑运行,又不影响宿主应用稳定性。

1.3 卡片类型对比

ArkTS卡片分动态和静态两种,实际开发需根据业务场景选择,核心差异如下:

卡片类型 支持能力 适用场景 优缺点
静态卡片 仅UI组件+布局,仅支持FormLink组件跳转 展示固定信息(如天气实况、日期) 内存开销小,功耗低;但无交互能力,频繁刷新会导致资源反复创建销毁
动态卡片 UI组件+布局+通用事件(点击、动画)+自定义动效 需交互/刷新场景(如音乐卡片切歌、新闻卡片刷新内容) 功能丰富,支持复杂交互;但内存开销比静态卡片大

动态卡片的事件交互依赖postCardAction接口,支持3种事件:

  • router:跳转至应用内UIAbility(非系统应用仅支持跳自己的页面);
  • call:拉起UIAbility到后台(如音乐卡片后台播放);
  • message:触发FormExtensionAbility的onFormEvent回调,更新卡片内容(如点击按钮刷新数据)。
1.4 约束与限制

开发时需注意以下限制,避免卡片异常:

  • 开发层面:仅支持ArkUI声明式范式,不支持跨平台开发;不支持Native语言(如C/C++)和加载Native so库。
  • 资源层面:仅支持导入“标注支持卡片”的模块(接口文档会标“卡片能力”),仅支持HAR静态共享包,不支持HSP动态共享包。
  • 调试层面:不支持极速预览、断点调试和Hot Reload热重载,开发时需通过日志(如hilog)排查问题。
  • 运行层面:不支持setTimeOut;若宿主应用支持左右滑动(如桌面分页),卡片内避免用左右滑动组件(防止手势冲突)。

二、创建ArkTS卡片

2.1 两种工程入口

在DevEco Studio(API 10及以上Stage模型)中,有两种创建卡片的前置工程:

  1. 新建Application工程:创建后右键工程根目录 → 选择“New” → “Service Widget”;
  2. 新建Atomic Service(元服务)工程:同上,后续步骤一致。

注意:不同DevEco Studio版本界面可能有差异,以实际界面为准。

2.2 新建卡片步骤
  1. 右键工程 → “New” → “Service Widget”,选择“动态卡片”或“静态卡片”(后续可通过配置修改);
  2. 选择卡片模板(如“2x2宫格”“4x4宫格”),开发语言选“ArkTS”,输入卡片名称(建议按业务命名,如“MusicWidget”);
  3. 点击“Finish”后,工程会自动生成3个核心文件:
  • EntryFormAbility.ets:卡片生命周期管理文件(处理创建、刷新、删除等逻辑);
  • WidgetCard.ets:卡片UI页面文件(定义布局和交互控件);
  • form_config.json:卡片配置文件(定义尺寸、刷新策略、卡片类型等)。

2.3 卡片类型修改

若创建时选错类型,可通过form_config.jsonisDynamic字段修改:

  • isDynamic: true 或字段置空 → 动态卡片;
  • isDynamic: false → 静态卡片。

三、卡片配置文件详解

ArkTS卡片需配置两个核心文件:module.json5(注册FormExtensionAbility)和form_config.json(卡片具体参数)。

3.1 module.json5配置(注册FormExtensionAbility)

FormExtensionAbility是卡片的“生命周期载体”,需在module.json5extensionAbilities标签中注册,示例代码如下:

{
  "module": {
    "package": "com.example.arktswidget",
    "name": ".MyModule",
    "mainAbility": "com.example.arktswidget.EntryAbility",
    // 卡片相关配置
    "extensionAbilities": [
      {
        "name": "EntryFormAbility", // 生命周期类名,需与EntryFormAbility.ets一致
        "srcEntry": "./ets/entryformability/EntryFormAbility.ets", // 生命周期文件路径
        "label": "$string:EntryFormAbility_label", // 卡片标签(多语言支持)
        "description": "$string:EntryFormAbility_desc", // 卡片描述
        "type": "form", // 类型固定为form
        "metadata": [
          {
            "name": "ohos.extension.form", // 固定键名
            "resource": "$profile:form_config" // 指向form_config.json配置文件
          }
        ]
      }
    ]
  }
}
3.2 form_config.json配置(卡片参数)

该文件位于resources/base/profile/目录,定义卡片的尺寸、刷新策略、主题等核心参数,完整示例如下(含关键注释):

{
  "forms": [
    {
      "name": "MusicWidget", // 卡片名称(最大127字节)
      "displayName": "$string:widget_display_name", // 卡片显示名(多语言,1-30字节)
      "description": "$string:widget_desc", // 描述(可选,最大255字节)
      "src": "./ets/widget/pages/WidgetCard.ets", // ArkTS卡片页面路径(需带.ets后缀)
      "uiSyntax": "arkts", // 类型:arkts(ArkTS卡片)/hml(JS卡片),默认hml
      // 窗口配置(可选,控制UI缩放)
      "window": {
        "designWidth": 720, // 设计基准宽度(默认720px)
        "autoDesignWidth": true // 自动计算基准宽度(true时忽略designWidth)
      },
      "colorMode": "auto", // 主题:auto(跟随系统)/dark/light,默认auto
      "isDefault": true, // 是否为默认卡片(每个UIAbility仅1个默认卡片)
      "updateEnabled": true, // 是否支持周期性刷新(true=支持)
      "scheduledUpdateTime": "10:30", // 定点刷新时间(24小时制,如10:30,可选)
      "updateDuration": 1, // 定时刷新周期(单位30分钟,1=30分钟,0=不生效,优先级高于定点)
      "defaultDimension": "2*2", // 默认尺寸(需在supportDimensions中)
      "supportDimensions": ["2*2", "4*4"], // 支持的尺寸(1*1圆形/1*2/2*2/2*4/4*4/6*4)
      "formConfigAbility": "ability://com.example.arktswidget.EntryAbility", // 配置跳转链接(URI格式,可选)
      "dataProxyEnabled": false, // 是否支持代理刷新(true时定时刷新失效,可选)
      "isDynamic": true, // 是否为动态卡片(仅ArkTS卡片生效,默认true)
      "fontScaleFollowSystem": true, // 字体是否跟随系统缩放(默认true)
      "supportShapes": "rect", // 卡片形状:rect(方形)/circle(圆形),默认rect
      "metadata": [] // 自定义元信息(可选)
    }
  ]
}

关键标签说明:

  • isDynamic:仅ArkTS卡片生效,决定卡片类型(动态/静态);
  • updateEnabled:开启后支持“定时刷新”(updateDuration)或“定点刷新”(scheduledUpdateTime),两者同时配置时定时优先;
  • supportDimensions:需根据宿主应用支持的尺寸选择(如桌面通常支持22_、24_)。

四、卡片生命周期管理

ArkTS卡片的生命周期由FormExtensionAbility接口控制,需在EntryFormAbility.ets中实现核心方法,处理卡片的创建、刷新、删除等逻辑。

4.1 完整生命周期代码
// 导入必要的工具包
import { 
  formBindingData, 
  FormExtensionAbility, 
  formInfo, 
  formProvider 
} from '@kit.FormKit';
import { Configuration, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

// 日志配置(方便调试)
const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00; // 日志域(自定义)

// 实现FormExtensionAbility
export default class EntryFormAbility extends FormExtensionAbility {
  /**
   * 1. 卡片创建时触发(宿主应用添加卡片)
   * @param want 包含卡片参数(如卡片名称、尺寸)
   * @return 卡片初始数据(FormBindingData)
   */
  onAddForm(want: Want): formBindingData.FormBindingData {
    hilog.info(DOMAIN_NUMBER, TAG, '[onAddForm] 卡片开始创建');
    // 获取卡片名称(从want参数中提取)
    const formName = want.parameters?.[formInfo.FormParam.NAME_KEY] as string;
    hilog.info(DOMAIN_NUMBER, TAG, `当前创建的卡片:${formName}`);
    
    // 构造卡片初始数据(键名需与WidgetCard.ets中的UI绑定一致)
    const initData = {
      'title': '音乐卡片',
      'currentSong': '晴天',
      'singer': '周杰伦'
    };
    // 返回绑定数据(UI会自动渲染这些数据)
    return formBindingData.createFormBindingData(initData);
  }

  /**
   * 2. 临时卡片转常态卡片时触发(目前手机端暂未使用临时卡片)
   * @param formId 卡片唯一ID
   */
  onCastToNormalForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onCastToNormalForm] 临时卡片转常态:${formId}`);
  }

  /**
   * 3. 卡片刷新时触发(定时/定点刷新、宿主主动刷新)
   * @param formId 卡片唯一ID
   */
  onUpdateForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onUpdateForm] 卡片开始刷新:${formId}`);
    
    // 构造刷新后的数据(如更新当前播放歌曲)
    const updateData = {
      'currentSong': '七里香',
      'singer': '周杰伦'
    };
    const formData = formBindingData.createFormBindingData(updateData);
    
    // 调用接口更新卡片(需处理异常)
    formProvider.updateForm(formId, formData)
      .catch((error: BusinessError) => {
        hilog.error(DOMAIN_NUMBER, TAG, `刷新失败:${JSON.stringify(error)}`);
      });
  }

  /**
   * 4. 卡片可见性变化时触发(仅系统应用生效)
   * @param newStatus 可见性状态(键:formId,值:可见性)
   */
  onChangeFormVisibility(newStatus: Record<string, number>): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onChangeFormVisibility] 可见性变化:${JSON.stringify(newStatus)}`);
  }

  /**
   * 5. 卡片触发事件时触发(如动态卡片点击按钮)
   * @param formId 卡片唯一ID
   * @param message 事件消息(自定义格式)
   */
  onFormEvent(formId: string, message: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onFormEvent] 卡片触发事件:${formId},消息:${message}`);
    // 示例:收到“切歌”消息后,刷新卡片数据
    if (message === 'next_song') {
      this.onUpdateForm(formId);
    }
  }

  /**
   * 6. 卡片删除时触发(宿主应用移除卡片)
   * @param formId 卡片唯一ID
   */
  onRemoveForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onRemoveForm] 卡片开始删除:${formId}`);
    // 清理卡片相关数据(如本地缓存的播放记录)
  }

  /**
   * 7. 系统配置更新时触发(如字体大小、主题变化)
   * @param config 新的系统配置
   */
  onConfigurationUpdate(config: Configuration) {
    hilog.info(DOMAIN_NUMBER, TAG, `[onConfigurationUpdate] 系统配置变化:${JSON.stringify(config)}`);
  }

  /**
   * 8. 查询卡片状态时触发(默认返回就绪状态)
   * @param want 包含卡片参数
   * @return 卡片状态(READY/UNREADY/INVALID)
   */
  onAcquireFormState(want: Want) {
    hilog.info(DOMAIN_NUMBER, TAG, `[onAcquireFormState] 查询卡片状态`);
    return formInfo.FormState.READY; // 默认为就绪状态
  }
}
4.2 生命周期关键注意点
  • FormExtensionAbility进程不常驻:生命周期方法执行完成后,进程会保留10秒;若10秒内无新操作,进程自动退出。若需处理耗时任务(如网络请求),建议拉起应用主进程处理,完成后调用formProvider.updateForm刷新卡片。
  • formId是关键标识:每个卡片实例有唯一formId,刷新、删除、事件处理都需通过formId定位。
  • 数据绑定FormBindingData中的键名需与WidgetCard.ets的UI组件绑定一致(如Text(${this.title})对应数据中的title键)。

五、ArkTS卡片进程模型

ArkTS卡片的运行依赖4个独立进程,各进程职责和隔离机制如图所示:

进程名称 核心职责 关键特性
卡片使用方进程 宿主应用进程(如桌面),展示卡片UI 仅系统应用可作为使用方
卡片渲染服务进程 统一渲染所有卡片的UI,管理渲染实例 所有应用的卡片共享该进程,但通过ArkTS虚拟机隔离(不同应用的实例无资源冲突)
卡片管理服务进程 系统级SA服务,管理卡片生命周期(创建/刷新/删除)、调度渲染服务和提供方 常驻系统,是卡片运行的“中枢”
卡片提供方进程 包含应用主进程(UIAbility)和FormExtensionAbility进程 两个进程内存隔离(避免互相影响),但共享同一文件沙箱(可共享本地文件)
进程交互逻辑
  1. 用户在桌面添加卡片 → 卡片使用方进程向管理服务进程发起创建请求;
  2. 管理服务进程通知提供方进程执行onAddForm,生成初始数据;
  3. 管理服务进程调度渲染服务进程,加载卡片UI代码(WidgetCard.ets)并渲染;
  4. 渲染完成后,渲染服务进程将UI数据发送给使用方进程,最终展示在桌面。

六、开发注意事项

  1. UI组件限制:仅支持ArkUI声明式的部分组件(如Column、Row、Text、Button),不支持List、Grid等复杂滚动组件(易引发性能问题);
  2. 资源控制:动态卡片的内存开销较大,避免在卡片中加载大图片或复杂动画;
  3. 手势冲突:若宿主应用支持左右滑动(如桌面分页),卡片内不要用SwipeGesture等左右滑动手势;
  4. 调试技巧:因不支持断点调试,建议通过hilog打印关键流程日志(如创建、刷新、事件触发),在DevEco Studio的“Log”面板查看;
  5. 兼容性form_config.json中的supportDimensions需选择主流尺寸(如2_2、4_4),避免使用小众尺寸(如6*4)导致部分设备不支持。

通过以上内容,我们已覆盖ArkTS卡片开发的全流程。实际开发中,建议先根据业务场景确定卡片类型(动态/静态),再按“创建工程→配置文件→实现生命周期→调试”的步骤推进,遇到问题可以在社区官网提问题,也可以私聊我,我一般看到都会回信息的,有必须要的情况,我也可以创建一些交流群,大家一起互相沟通

Logo

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

更多推荐