普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月14日首页

鸿蒙应用的“任意门”:Deep Linking 与 App Linking 的相爱相杀

作者 SameX
2026年1月14日 13:50

写在前面:本文基于 HarmonyOS Next 的摸爬滚打经验总结。技术这东西更新快,如果哪里说得不对,或者你有更骚的操作,欢迎在评论区拍砖交流。转载请注明出处,谢啦。

做移动端开发,最烦的是什么?是应用像一个个孤岛,互相都不通气。

用户在微信里点个链接,想跳到你的 App 里看详情,结果要么没反应,要么跳出一堆甚至都没听说过的 App 让你选。这就很尴尬了。为了解决这个问题,鸿蒙系统给咱们提供了两把钥匙:一把叫 Deep Linking,一把叫 App Linking

很多兄弟容易搞混,觉得这俩不是一回事吗?确实,目的都是为了“跳转”,但手段和段位可大不一样。今天咱们就来扒一扒这两者的底裤。


一、 Deep Linking:简单粗暴的“土法炼钢”

Deep Linking 说白了,就是利用自定义协议(Scheme)来实现跳转。这招在移动开发界属于“老兵”了。

它是怎么工作的?

你想让别人通过暗号找到你,你就得先起个暗号。比如你做个地图 App,你可以跟系统喊一嗓子:“以后只要有人喊 geo:// 开头的,都归我管!”

这就是 Deep Linking 的核心:自定义 Scheme

  • 优点:门槛低,随便定义。my-super-app://,想怎么写怎么写。
  • 缺点:太随意了。万一隔壁老王也定义了 geo:// 怎么办?这时候系统就懵圈了,只能弹个窗让用户自己选。这一选,用户体验就断档了。而且这玩意儿不安全,谁都能冒充。

怎么配置?

在鸿蒙里,你得在 module.json5 里通过 skills 标签去“抢注”这个暗号。

// module.json5
{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "skills": [
          {
            "uris": [
              {
                "scheme": "mychat", // 你的暗号
                "host": "talk.com", // 具体的接头地点
                "path": "room"      // 具体的房间号
              }
            ]
          }
        ]
      }
    ]
  }
}

这一下,只要有链接是 mychat://talk.com/room,系统就会把目光投向你。


二、 App Linking:持证上岗的“正规军”

华为现在大力推的是 App Linking。为啥?因为它正规、安全、体验好。

它强在哪?

App Linking 不再用那些乱七八糟的自定义协议,而是直接用标准的 HTTPS 链接(比如 https://www.example.com)。

这里有个核心逻辑:域名校验。 系统会去验证:“你这个 App 到底是不是这个域名的亲儿子?”。

  • 如果验证通过:用户点击链接,直接拉起 App,没有任何弹窗干扰,丝般顺滑。
  • 如果没安装 App:既然是 HTTPS,那就直接用浏览器打开网页。这就叫“进可攻退可守”,用户永远不会看到 404 或者无响应。

这就特别适合做社交分享、广告引流,或者短信召回老用户。

怎么配置?(重点来了,这里稍微繁琐点)

这玩意儿需要“双向奔赴”:App 端要认领域名,服务器端要认领 App。

  1. 服务器端搞个“介绍信”: 你得在你的网站服务器根目录下,创建一个 .well-known/applinking.json 文件。这文件里写啥?写你 App 的身份证号(APP ID)。 这是为了告诉全天下:这个 App 是我罩着的。
  2. App 端开启“雷达”: 在 AGC 控制台开通服务后,你得在 module.json5 里也配上 skills,不过这次 scheme 必须是 https注意:在 AGC 后台(增长 > App Linking)记得把“域名校验”的开关打开,不然系统懒得去查。

三、 实战:到底怎么跳?

配置好了,怎么触发跳转呢?咱们看代码。

场景 A:我想拉起别人(发起方)

鸿蒙提供了 openLink 接口,这比传统的 startAbility 更适合处理链接跳转。

import { common } from '@ohos.app.ability.common';

// 比如在一个按钮点击事件里
function jumpToTarget(context: common.UIAbilityContext) {
  // 目标链接
  const targetLink = "https://www.example.com/programs?action=showall"; 
  
  const options: common.OpenLinkOptions = {
    // 重点!这里有个开关
    // true: 只要 App Linking(没安装App就可能没反应或者走浏览器逻辑,看系统实现)
    // false: 兼容 Deep Linking 模式,哪怕没校验过域名的 scheme 也能试着跳
    appLinkingOnly: false 
  };

  try {
    context.openLink(targetLink, options).then(() => {
      console.info('跳转成功,走你!');
    }).catch((err) => {
      console.error(`跳转翻车了: ${JSON.stringify(err)}`);
    });
  } catch (paramError) {
    console.error(`参数都有问题: ${JSON.stringify(paramError)}`);
  }
}

如果你非要用 Deep Linking 的那种 geo: 协议,用 startAbility 也是可以的,构建一个 Want 对象就行,但这在 API 12 里显得有点“复古”了。

场景 B:别人拉起我(接收方)

不管是 Deep Linking 还是 App Linking,进了你的门,处理逻辑是一样的。都是在 AbilityonCreate 或者 onNewWant 里接客。

import { UIAbility, Want, AbilityConstant } from '@ohos.app.ability.common';
import { url } from '@ohos.arkts';

export default class EntryAbility extends UIAbility {
  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.handleLink(want);
  }

  // 如果 App 已经在后台活着,会走这里
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.handleLink(want);
  }

  handleLink(want: Want) {
    const uri = want?.uri;
    if (!uri) return; // 没带链接?那是误触吧

    console.info(`收到链接请求: ${uri}`);

    // 解析 URL,这就跟前端解析 location.href 一个德行
    try {
      const urlObject = url.URL.parseURL(uri);
      const action = urlObject.params.get('action');
      
      if (action === "showall") {
        // 路由跳转逻辑:带大伙去“所有节目”页面
        // router.pushUrl(...) 
      }
    } catch (e) {
      console.error("这链接格式不对啊,老铁");
    }
  }
}


四、 总结:该选哪一个?

说了一大堆,最后给兄弟们来个“防纠结指南”:

特性 Deep Linking (土法) App Linking (正规军)
链接长相 myapp://detail https://www.myapp.com/detail
安全性 低 (谁都能用) 高 (域名校验,防伪冒)
没安装App时 报错或无响应 自动打开浏览器网页,体验无缝衔接
唯一性 不保证 (可能弹窗选App) 保证 (唯一归属,一键直达)
适用场景 App 内部页面互跳、非公网环境 外部引流、营销短信、二维码、社交分享

血泪建议: 如果是做对外推广、H5 唤醒 App,无脑上 App Linking。它是未来的主流,而且不用担心“应用未安装”的尴尬。 如果是 App 内部自己跳自己,或者公司内部几个 App 互通,不想搞服务器域名那一套,那 Deep Linking 依然是个轻量级的好选择。

行了,关于鸿蒙的“连接艺术”今天就聊到这。代码写完了记得多测测,别到时候用户点开链接一脸懵逼,那就尴尬了。

不懂鸿蒙权限?看这篇就够了(鸿蒙权限获取最佳实践,附完整代码)

2026年1月14日 09:15
鸿蒙权限管理不踩坑指南:做个“懂分寸”的合规好应用 在鸿蒙(HarmonyOS)的世界里,权限管理就像应用的“社交礼仪”——懂分寸、不越界,才能赢得用户好感和系统“青睐”;要是乱要权限、硬闯隐私,轻则
昨天 — 2026年1月13日首页

HarmonyOS 多模块项目中的公共库治理与最佳实践

作者 90后晨仔
2026年1月13日 20:43

鸿蒙(HarmonyOS)多模块项目 中,如果你希望 避免在每个模块(Module)中重复集成同一个三方库或公共库,可以将该库 提升到项目级别(Project-level)进行统一管理。以下是标准做法,适用于 Stage 模型 + ArkTS + DevEco Studio 的工程结构。


✅ 目标

将公共库(如 @ohos/utils、自研工具库、第三方 npm 包等)只声明一次,供多个模块(entry、feature、service 等)共享使用


📁 鸿蒙项目结构回顾

MyHarmonyProject/
├── build-profile.json5        ← 项目级构建配置
├── oh-package.json5           ← 项目级依赖(关键!)
├── modules/
│   ├── entry/                 ← 主模块
│   ├── feature_news/          ← 功能模块1
│   └── feature_ebook/         ← 功能模块2
└── libs/                      ← (可选)本地 aar/har 公共库

✅ 正确做法:在 项目根目录的 oh-package.json5 中声明依赖

步骤 1:在项目根目录的 oh-package.json5 中添加依赖

{
  "devDependencies": {
    // 开发依赖(如 types)
  },
  "dependencies": {
    // 👇 把公共库放在这里(项目级)
    "@ohos/utils": "1.0.0",
    "some-third-party-lib": "^2.3.0"
  }
}

✅ 这样,所有子模块都可以继承使用这些依赖,无需在每个 module/xxx/oh-package.json5 中重复声明。


步骤 2:删除各子模块中的重复依赖

确保 modules/entry/oh-package.json5modules/feature_news/oh-package.json5不再包含 已提升到项目级的依赖。

例如,不要entry/oh-package.json5 中再写:

{
  "dependencies": {
    "@ohos/utils": "1.0.0"  // ❌ 删除这行!
  }
}

步骤 3:在子模块代码中正常 import 使用

// 在 entry 或 feature_news 模块中
import { ZGJYBAppearanceColorUtil } from '@ohos/utils';

// ✅ 可以正常使用,因为依赖已由项目级提供

⚠️ 注意事项

1. 仅适用于 npm 类型的包(通过 ohpm 安装)

  • 如果你是通过 ohpm install @ohos/utils 安装的库,它会被记录在 oh-package.json5
  • 这种方式支持 依赖提升(hoisting) ,类似 npm/yarn 的 workspace。

2. 本地 .har.hap 库不能这样共享

  • 如果你的“库”是一个 本地开发的 .har(HarmonyOS Archive)模块,则需要:

    • 将其放在 libs/ 目录下;
    • 每个需要使用的模块module.json5 中声明 deps 引用;
    • 或者将其发布为私有 ohpm 包,再通过 oh-package.json5 引入。

示例:引用本地 har(仍需逐模块配置)

// modules/entry/module.json5
{
  "deps": [
    "../libs/my-common-utils.har"
  ]
}

❌ 这种情况无法完全避免重复声明,但你可以通过脚本或模板减少工作量。


3. 确保 DevEco Studio 同步了依赖

  • 修改 oh-package.json5 后,点击 “Sync Now” 或运行:

    ohpm install
    

    在项目根目录执行,会安装所有模块共享的依赖。


✅ 最佳实践总结

场景 推荐方案
公共 npm/ohpm 库(如 @ohos/utils ✅ 在 项目根目录 oh-package.json5 中声明一次
自研公共逻辑(TS 工具函数) ✅ 创建一个 shared 模块,发布为 ohpm 私有包,再在项目级引入
本地 .har ⚠️ 需在每个模块的 module.json5 中引用,但可统一放在 libs/ 目录管理
避免重复代码 ✅ 抽象公共组件/工具到独立模块,通过依赖注入使用

🔧 附加建议:创建 shared 模块(高级)

  1. 新建模块:File > New > Module > Static Library (HAR)

    • 命名为 shared
  2. 在其中放置公共工具类、常量、网络封装等

  3. shared/oh-package.json5 中定义包名:

    { "name": "@myorg/shared", "version": "1.0.0" }
    
  4. 在项目根目录运行:

    ohpm install ./modules/shared --save
    
  5. 然后在 oh-package.json5 中就会出现:

    "dependencies": {
      "@myorg/shared": "file:./modules/shared"
    }
    
  6. 所有模块即可通过 import { xxx } from '@myorg/shared' 使用。

✅ 这是最接近“项目级公共库”的鸿蒙官方推荐方案。


✅ 结论

把公共库写在项目根目录的 oh-package.json5dependencies 中,即可实现“一次集成,多模块共享”

只要你的库是通过 ohpm 管理的(包括本地 file: 引用),就支持这种共享机制。这是 HarmonyOS 多模块项目的标准依赖管理方式。

昨天以前首页

告别素材焦虑!用 AI 一键生成鸿蒙项目图片素材

作者 万少
2026年1月12日 16:37

告别素材焦虑!用 AI 一键生成鸿蒙项目图片素材

万少:华为HDE、鸿蒙极客

个人主页:blog.zbztb.cn/

2025年参与孵化了20+鸿蒙应用、技术文章300+、鸿蒙知识库用户500+、鸿蒙免费课程2套。

如果你也喜欢交流AI和鸿蒙技术,欢迎扣我。

最近我在B站上进行不定期的免费鸿蒙技术直播,欢迎关注:space.bilibili.com/414874315?s…

程序员找素材,到底有多难?

做项目开发时,我们经常需要各种图片素材。但获取素材这件事,不同角色的体验天差地别:

企业开发:有专业 UI 设计师,直接找设计师要就完事了。

个人开发者:就只能自己想办法:

  • 手动去素材网站搜索、挑选、下载
  • 把素材引入工程
  • 在代码中使用

这一套流程走下来,没个十几二十分钟根本搞不定。更糟心的是,花半天找的素材还不一定满意。

我就一直在想:图片素材能不能像普通文本一样,直接让 AI 生成,然后插到工程里?

鸿蒙工程里怎么用图片?

在鸿蒙(HarmonyOS)开发中,使用图片主要分两步:

  1. 存放图片:把图片放到 resources 目录或 rawfile 目录
  2. 代码引用:在 .ets 文件中引入并使用

这个流程本身很简单,但问题卡在第一步——图片从哪来?

我的解决方案:AI 生成图片脚本

既然 AI 能写代码,那生成图片当然也不在话下。

我的做法是写一个脚本,通过 AI 图像生成接口来获取素材。成本很低,市面上主流的 AI 绘画服务(百度、阿里、火山等)生成一张图片大约 1~3 分钱

脚本的核心功能:

  • 接收一个参数:图片描述(文本字符串)
  • 调用 AI 图片生成接口
  • 返回图片文件流
  • 自动保存到指定位置

实现思路:

  1. 从各 AI 平台官网复制对应语言的 SDK 代码(比如 Python、Node.js 等)
  2. 把自己的 API Key 写入脚本
  3. 封装一个函数,传入图片描述,返回图片文件

实战:免费好用的图片生成服务

推荐一个我经常用的——智谱 AI 的图片生成服务:

🔗 bigmodel.cn/

免费额度对个人开发者来说完全够用,生成效果也很不错。

智谱 AI 平台

将生成图片的脚本直接放在鸿蒙工程内

image-20260112130734993

将脚本包装成可以使用终端调用的文件

image-20260112130828662

AI编辑器中直接对话生成

这里用上架应用-流蓝卡片 为例:

image-20260112131139741

然后执行程序,得到结果

image-20260112131456935


实际效果:

PixPin_2026-01-12_13-17-11

历史文章

  1. AI 玩转鸿蒙 (1):选择合适的AI开发工具

    mp.weixin.qq.com/s/HXbT60vzJ…

下期预告

用 AI 生成鸿蒙代码难免会有小语法错误。

下篇文章我来分享:如何让 AI 自动修复自己的代码错误,实现「生成即可用」的无缝体验。

前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP--ArkTS 卡片刷新机制

作者 陈_杨
2026年1月12日 05:35

大家好,我是陈杨,一名有着8 年前端开发经验、6 年技术写作积淀的鸿蒙开发者,也是鸿蒙生态里的一名极客。

曾因前端行业的危机感居安思危,果断放弃饱和的 iOS、安卓赛道,在鸿蒙 API9 发布时,凭着前端技术底子,三天吃透 ArkTS 框架,快速上手鸿蒙开发。三年深耕,我不仅打造了鸿蒙开源图表组件库「莓创图表」,闯进过创新赛、极客挑战赛总决赛,更带着团队实实在在做出了成果 —— 目前已成功上架11 款鸿蒙应用,涵盖工具、效率、创意等多个品类,包括JLPT、REFLEX PRO、国潮纸刻、Wss 直连、ZenithDocs Pro、圣诞相册、CSS 特效等,靠这些自研产品赚到了转型后的第一桶金。

从前端转型到鸿蒙掘金,靠的不是运气,而是选对赛道的眼光和快速落地的执行力。今天这篇文章,就接着上一篇的内容,和大家聊聊 [ArkTS 卡片刷新机制],这篇很重要,认真听。

在 ArkTS 卡片开发中,“内容刷新” 是核心功能之一 —— 无论是实时数据更新(如天气、新闻)、图片替换,还是基于状态的内容切换,都依赖高效的刷新机制。本文基于 HarmonyOS 官方文档,系统梳理卡片刷新的两种核心模式(主动刷新、被动刷新)、特殊场景实现(图片刷新、状态关联刷新)及关键约束,结合完整代码示例,帮助开发者精准落地各类刷新需求。

一、刷新机制核心概述

ArkTS 卡片的刷新能力由系统框架提供,核心依赖两套接口和配置体系:

  • 主动刷新:由卡片提供方(应用)或使用方(如桌面)主动触发,适用于 “按需更新” 场景(如用户点击刷新按钮、应用数据变化);
  • 被动刷新:由系统根据预设规则自动触发,适用于 “周期性更新” 场景(如每天固定时间刷新、每隔 30 分钟更新);
  • 数据传递:刷新时的数据通过formBindingData封装,卡片页面通过@LocalStorageProp接收(数据自动转为 string 类型),确保数据同步的一致性。

关键接口说明:

接口名 调用方 核心作用 约束
updateForm 卡片提供方 主动刷新自身卡片内容 仅能刷新当前应用的卡片,无法操作其他应用卡片
requestForm 卡片使用方(系统应用) 主动请求刷新已添加的卡片 仅能刷新当前宿主中的卡片
setFormNextRefreshTime 卡片提供方 设置下次刷新时间 最短刷新间隔 5 分钟

二、主动刷新:按需触发的精准更新

主动刷新是开发者最常使用的模式,核心是通过updateForm接口手动触发,可搭配用户交互、应用数据变化等场景使用。

2.1 核心实现:提供方主动刷新

卡片提供方(应用)通过formProvider.updateForm接口触发刷新,需传入目标卡片的formId(唯一标识)和更新后的数据。通常与onFormEvent(用户交互触发)、onUpdateForm(生命周期回调)搭配使用。

完整代码示例:用户点击按钮触发刷新

// 1. 卡片页面(WidgetCard.ets):提供刷新按钮,通过postCardAction传递事件
let storage = new LocalStorage();
@Entry(storage)
@Component
struct RefreshButtonCard {
  // 接收FormExtensionAbility传递的formId和数据
  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('currentData') currentData: string = '初始数据';

  build() {
    Column({ space: 20 })
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .padding(15) {

      Text(`当前内容:${this.currentData}`)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      // 点击按钮触发刷新事件
      Button("手动刷新")
        .width("80%")
        .height(50)
        .backgroundColor("#5A5FFF")
        .fontColor("#FFFFFF")
        .onClick(() => {
          // 发送message事件给FormExtensionAbility,携带formId
          postCardAction(this, {
            action: "message",
            params: {
              formId: this.formId,
              refreshType: "manual"
            }
          });
        })
    }
  }
}

// 2. FormExtensionAbility(EntryFormAbility.ets):接收事件并执行刷新
import { 
  formBindingData, 
  FormExtensionAbility, 
  formProvider, 
  formInfo 
} from '@kit.FormKit';
import { Want, BusinessError } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;

export default class EntryFormAbility extends FormExtensionAbility {
  // 卡片创建时,将formId存入LocalStorage(供页面使用)
  onAddForm(want: Want): formBindingData.FormBindingData {
    hilog.info(DOMAIN_NUMBER, TAG, '[onAddForm] 卡片创建');
    // 获取卡片唯一ID
    const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string;
    // 初始化数据(包含formId)
    const initData = {
      formId: formId,
      currentData: '初始数据'
    };
    return formBindingData.createFormBindingData(initData);
  }

  // 接收卡片页面的message事件,执行刷新
  async onFormEvent(formId: string, message: string): Promise<void> {
    const params = JSON.parse(message);
    hilog.info(DOMAIN_NUMBER, TAG, `[onFormEvent] 触发手动刷新,formId: ${formId}`);

    // 模拟获取新数据(实际场景可替换为接口请求、数据库查询等)
    const newData = {
      currentData: `刷新于 ${new Date().toLocaleTimeString()}`
    };

    // 封装刷新数据并调用updateForm
    const formInfo = formBindingData.createFormBindingData(newData);
    try {
      await formProvider.updateForm(formId, formInfo);
      hilog.info(DOMAIN_NUMBER, TAG, `[onFormEvent] 刷新成功`);
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `[onFormEvent] 刷新失败:code=${err.code}, msg=${err.message}`);
    }
  }
}

2.2 关键注意点

  • formId是核心标识:每个卡片实例的formId唯一,需通过want.parameters[formInfo.FormParam.IDENTITY_KEY]获取(卡片创建时);
  • 异常处理必须:updateForm可能因网络异常、权限不足失败,需通过try/catch捕获BusinessError
  • 数据格式限制:传递的数据需为 JSON 可序列化类型(字符串、数字、对象),复杂类型需手动转换。

三、被动刷新:系统驱动的自动更新

被动刷新无需手动触发,由系统根据配置自动执行,核心分为 “定时刷新”“定点刷新”“下次刷新” 三类,适用于周期性数据更新场景。

3.1 定时刷新:固定间隔自动更新

定时刷新通过form_config.jsonupdateDuration字段配置,单位为 “30 分钟”,需配合updateEnabled: true启用。

配置示例(form_config.json)

{
  "forms": [
    {
      "name": "WeatherWidget",
      "src": "./ets/widget/pages/WeatherCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true, // 启用周期性刷新
      "updateDuration": 2, // 刷新周期:2 * 30分钟 = 1小时
      "defaultDimension": "2*2",
      "supportDimensions": ["2*2"],
      "colorMode": "auto",
      "isDefault": true
    }
  ]
}

代码实现(EntryFormAbility.ets)

系统触发定时刷新时,会回调onUpdateForm方法,需在此方法中实现数据更新逻辑:

export default class EntryFormAbility extends FormExtensionAbility {
  // 定时刷新触发时执行
  async onUpdateForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onUpdateForm] 定时刷新触发,formId: ${formId}`);

    // 模拟请求天气数据(实际场景替换为真实接口)
    const newWeatherData = {
      city: "北京",
      temperature: "25℃",
      updateTime: new Date().toLocaleTimeString()
    };

    const formInfo = formBindingData.createFormBindingData(newWeatherData);
    try {
      await formProvider.updateForm(formId, formInfo);
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `定时刷新失败:${err.message}`);
    }
  }
}

定时刷新核心约束

  • 刷新周期规则:updateDuration为自然数,0 表示不生效;API 11 及以上版本,若应用市场配置了刷新周期,取 “配置值” 和 “应用配置值” 的较大者(如应用配置 1 小时,市场配置 2 小时,则按 2 小时刷新);
  • 配额限制:每张卡片每天最多定时刷新 50 次(包含updateDurationsetFormNextRefreshTime两种方式),0 点重置;
  • 可见性影响:卡片不可见时,系统仅记录刷新动作,待卡片可见后统一刷新布局。

3.2 定点刷新:指定时间自动更新

定点刷新支持 “单时间点” 和 “多时间点” 配置,通过form_config.jsonscheduledUpdateTime(单时间点)或multiScheduledUpdateTime(多时间点)字段设置,需关闭定时刷新(updateDuration: 0`)。

配置示例(多时间点刷新)

{
  "forms": [
    {
      "name": "NewsWidget",
      "src": "./ets/widget/pages/NewsCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true,
      "updateDuration": 0, // 关闭定时刷新,优先定点刷新
      "scheduledUpdateTime": "10:30", // 单时间点(兼容旧版本)
      "multiScheduledUpdateTime": "08:00,12:00,18:00", // 多时间点(最多24个)
      "defaultDimension": "2*4",
      "supportDimensions": ["2*4"],
      "isDefault": true
    }
  ]
}

关键说明

  • 优先级:多时间点配置(multiScheduledUpdateTime)优先级高于单时间点(scheduledUpdateTime),两者同时配置时仅多时间点生效;
  • 时间格式:采用 24 小时制,精确到分钟(如08:0016:30),多时间点用英文逗号分隔;
  • 触发逻辑:系统在指定时间点回调onUpdateForm方法,数据更新逻辑与定时刷新一致。

3.3 下次刷新:自定义延迟更新

通过formProvider.setFormNextRefreshTime接口设置下次刷新时间,适用于 “延迟更新” 场景(如用户操作后 5 分钟刷新),最短间隔为 5 分钟。

代码示例

export default class EntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string): void {
    const params = JSON.parse(message);
    if (params.action === "setNextRefresh") {
      // 设置5分钟后刷新(参数单位:分钟)
      const delayMinutes = 5;
      formProvider.setFormNextRefreshTime(formId, delayMinutes, (err: BusinessError) => {
        if (err) {
          hilog.error(DOMAIN_NUMBER, TAG, `设置下次刷新失败:${err.message}`);
          return;
        }
        hilog.info(DOMAIN_NUMBER, TAG, `已设置5分钟后刷新`);
      });
    }
  }

  // 下次刷新时间到后,触发onUpdateForm
  async onUpdateForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onUpdateForm] 下次刷新触发`);
    // 执行数据更新逻辑...
  }
}

四、特殊场景刷新:图片与状态关联

除基础文本数据刷新外,卡片常见的复杂场景包括 “图片刷新”(本地 / 网络图片)和 “状态关联刷新”(根据卡片配置刷新不同内容),需针对性处理。

4.1 图片刷新:本地与网络图片更新

卡片展示图片需通过formImages字段传递文件描述符(fd),页面通过memory://fileName协议加载,支持本地图片和网络图片两种场景。

4.1.1 本地图片刷新(卡片创建时加载)

// EntryFormAbility.ets:onAddForm中加载本地图片
import { fileIo } from '@kit.CoreFileKit';
import { Want, BusinessError } from '@kit.AbilityKit';

export default class EntryFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    // 获取应用临时目录(存放本地图片)
    const tempDir = this.context.getApplicationContext().tempDir;
    const imgMap: Record<string, number> = {};

    try {
      // 打开本地图片(假设tempDir下有head.png)
      const file = fileIo.openSync(`${tempDir}/head.png`);
      imgMap['avatar'] = file.fd; // fd为文件描述符,作为图片标识
    } catch (e) {
      const err = e as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `打开本地图片失败:${err.message}`);
    }

    // 封装图片数据(formImages为固定字段,不可改名)
    class FormData {
      text: string = "我的头像";
      imgName: string = "avatar"; // 与formImages的key一致
      formImages: Record<string, number> = imgMap; // 存储fd
    }

    return formBindingData.createFormBindingData(new FormData());
  }
}

// 卡片页面(WidgetCard.ets):加载本地图片
let storage = new LocalStorage();
@Entry(storage)
@Component
struct LocalImageCard {
  @LocalStorageProp('text') text: string = "加载中...";
  @LocalStorageProp('imgName') imgName: string = "";

  build() {
    Column()
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center) {

      // 通过memory://协议加载图片(imgName对应formImages的key)
      Image(`memory://${this.imgName}`)
        .width(100)
        .height(100)
        .borderRadius(50)
        .objectFit(ImageFit.Cover)

      Text(this.text)
        .margin({ top: 10 })
        .fontSize(14)
    }
  }
}

4.1.2 网络图片刷新(用户点击触发)

网络图片需先下载到本地临时目录,再通过 fd 传递,需申请ohos.permission.INTERNET权限。

// 1. 配置权限(module.json5)
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "用于下载网络图片",
        "usedScene": { "abilities": ["EntryFormAbility"], "when": "always" }
      }
    ]
  }
}

// 2. EntryFormAbility.ets:onFormEvent中下载并刷新图片
import { http } from '@kit.NetworkKit';

export default class EntryFormAbility extends FormExtensionAbility {
  async onFormEvent(formId: string, message: string): Promise<void> {
    // 先更新状态为“刷新中”
    let loadingData = { text: "刷新中..." };
    await formProvider.updateForm(formId, formBindingData.createFormBindingData(loadingData));

    // 网络图片地址(替换为真实链接)
    const imgUrl = "https://example.com/new-avatar.jpg";
    const tempDir = this.context.getApplicationContext().tempDir;
    const fileName = `img_${Date.now()}`; // 文件名唯一(确保图片刷新)
    const tempFile = `${tempDir}/${fileName}`;
    const imgMap: Record<string, number> = {};

    try {
      // 1. 下载网络图片
      const httpRequest = http.createHttp();
      const response = await httpRequest.request(imgUrl);
      if (response.responseCode !== http.ResponseCode.OK) {
        throw new Error(`下载失败:${response.responseCode}`);
      }

      // 2. 写入临时文件
      const imgFile = fileIo.openSync(tempFile, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      await fileIo.write(imgFile.fd, response.result as ArrayBuffer);
      imgMap[fileName] = imgFile.fd;

      // 3. 刷新卡片图片
      const formData = {
        text: "刷新成功",
        imgName: fileName,
        formImages: imgMap
      };
      await formProvider.updateForm(formId, formBindingData.createFormBindingData(formData));

      // 4. 关闭文件(需在刷新后执行)
      fileIo.closeSync(imgFile);
      httpRequest.destroy();
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `网络图片刷新失败:${err.message}`);
      // 刷新失败状态
      const failData = { text: "刷新失败" };
      await formProvider.updateForm(formId, formBindingData.createFormBindingData(failData));
    }
  }
}

图片刷新关键约束

  • 图片大小限制:展示的图片需控制在 2MB 以内,避免占用过多内存;
  • 文件名唯一性:每次刷新需使用不同的imgName(如加时间戳),否则卡片不会感知图片变化;
  • 下载超时限制:FormExtensionAbility后台仅存活 5 秒,需确保网络图片快速下载完成。

4.2 状态关联刷新:根据卡片配置更新内容

同一卡片可能存在多种状态(如两张天气卡片分别显示北京、上海天气),需通过持久化存储记录卡片状态,刷新时根据状态返回对应内容。

完整实现示例

// 1. form_config.json:配置定时刷新(每30分钟)
{
  "forms": [
    {
      "name": "CityWeatherWidget",
      "src": "./ets/widget/pages/WeatherCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true,
      "updateDuration": 1, // 30分钟刷新一次
      "defaultDimension": "2*2",
      "supportDimensions": ["2*2"],
      "isDefault": true
    }
  ]
}

// 2. 卡片页面(WeatherCard.ets):选择城市状态
let storage = new LocalStorage();
@Entry(storage)
@Component
struct CityWeatherCard {
  @LocalStorageProp('beijingWeather') beijingWeather: string = "待刷新";
  @LocalStorageProp('shanghaiWeather') shanghaiWeather: string = "待刷新";
  @State selectBeijing: boolean = false;
  @State selectShanghai: boolean = false;

  build() {
    Column({ space: 15 })
      .width('100%')
      .height('100%')
      .padding(15) {

      // 城市选择复选框
      Row({ space: 8 }) {
        Checkbox({ name: 'bj', group: 'cityGroup' })
          .onChange((value) => {
            this.selectBeijing = value;
            // 通知FormExtensionAbility更新状态
            this.notifyStateChange();
          });
        Text("北京")
          .fontSize(14);
      }

      Row({ space: 8 }) {
        Checkbox({ name: 'sh', group: 'cityGroup' })
          .onChange((value) => {
            this.selectShanghai = value;
            this.notifyStateChange();
          });
        Text("上海")
          .fontSize(14);
      }

      // 天气展示
      Text(`北京天气:${this.beijingWeather}`)
        .fontSize(14)
        .margin({ top: 10 });
      Text(`上海天气:${this.shanghaiWeather}`)
        .fontSize(14);
    }
  }

  // 发送状态变更事件
  private notifyStateChange() {
    postCardAction(this, {
      action: "message",
      params: {
        selectBeijing: JSON.stringify(this.selectBeijing),
        selectShanghai: JSON.stringify(this.selectShanghai)
      }
    });
  }
}

// 3. EntryFormAbility.ets:持久化状态并刷新对应内容
import { preferences } from '@kit.ArkData';

export default class EntryFormAbility extends FormExtensionAbility {
  // 卡片创建时初始化状态(存入preferences数据库)
  async onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string;
    const isTempCard = want.parameters?.[formInfo.FormParam.TEMPORARY_KEY] as boolean;

    // 仅常态卡片持久化状态
    if (!isTempCard) {
      const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
      await store.put(`bj_${formId}`, 'false'); // 北京状态:未选中
      await store.put(`sh_${formId}`, 'false'); // 上海状态:未选中
      await store.flush();
    }

    return formBindingData.createFormBindingData({});
  }

  // 接收状态变更事件,更新数据库
  async onFormEvent(formId: string, message: string): Promise<void> {
    const params = JSON.parse(message);
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');

    if (params.selectBeijing !== undefined) {
      await store.put(`bj_${formId}`, params.selectBeijing);
    }
    if (params.selectShanghai !== undefined) {
      await store.put(`sh_${formId}`, params.selectShanghai);
    }
    await store.flush();
  }

  // 定时刷新时,根据状态返回对应天气数据
  async onUpdateForm(formId: string): void {
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
    const selectBeijing = await store.get(`bj_${formId}`, 'false');
    const selectShanghai = await store.get(`sh_${formId}`, 'false');

    const updateData: Record<string, string> = {};
    // 模拟获取天气数据
    if (selectBeijing === 'true') {
      updateData.beijingWeather = `25℃ 晴(${new Date().toLocaleTimeString()})`;
    }
    if (selectShanghai === 'true') {
      updateData.shanghaiWeather = `28℃ 多云(${new Date().toLocaleTimeString()})`;
    }

    await formProvider.updateForm(formId, formBindingData.createFormBindingData(updateData));
  }

  // 卡片删除时,清理数据库数据
  async onRemoveForm(formId: string): void {
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
    await store.delete(`bj_${formId}`);
    await store.delete(`sh_${formId}`);
  }
}

五、刷新机制核心约束与最佳实践

5.1 关键约束(避坑重点)

  1. 刷新次数限制:定时刷新每天最多 50 次,超出后当天无法触发;
  2. 进程存活限制FormExtensionAbility后台仅存活 5 秒(触发回调后),耗时操作(如下载大文件)需拉起主应用处理;
  3. 权限限制:网络图片刷新需申请INTERNET权限,本地文件操作需确保路径合法;
  4. 资源限制:图片大小≤2MB,自定义字体总大小≤20MB,避免内存溢出。

5.2 最佳实践

  1. 选择合适的刷新方式
  • 实时交互场景(如点击刷新)→ 主动刷新;
  • 周期性数据(如天气、新闻)→ 被动刷新(定时 / 定点);
  1. 优化刷新性能
  • 减少刷新频率,非必要不使用高频定时刷新;
  • 刷新数据仅传递变更部分,避免全量更新;
  1. 异常处理兜底
  • 网络请求失败时,保留上次有效数据;
  • 图片加载失败时,显示默认占位图;
  1. 清理冗余数据:卡片删除时,同步清理数据库、临时文件等,避免存储冗余。

总结

ArkTS 卡片的刷新机制围绕 “主动按需” 和 “被动自动” 两大核心,覆盖了文本、图片、状态关联等各类场景。开发时需重点关注formId唯一性、数据传递格式、系统约束(次数、内存、进程存活),并根据业务场景选择合适的刷新方式。通过本文的代码示例和约束说明,可快速落地稳定、高效的卡片刷新功能,提升用户体验。

前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP--ArkTS 卡片开发完全指南

作者 陈_杨
2026年1月12日 05:25

经验分享

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

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

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

转型之初,我也踩过不少技术选型的坑。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卡片开发的全流程。实际开发中,建议先根据业务场景确定卡片类型(动态/静态),再按“创建工程→配置文件→实现生命周期→调试”的步骤推进,遇到问题可以在社区官网提问题,也可以私聊我,我一般看到都会回信息的,有必须要的情况,我也可以创建一些交流群,大家一起互相沟通

2026第一站:分享我在高德大赛现场学到的技术、产品与心得

2026年1月11日 20:06

一、引言

1月9日,我参加了高德空间智能开发者大赛决赛,因提交的作品获得了优胜奖,所以受到官方邀请来现场,可以与更多开发者面对面交流,开发者大会既是展示技术的舞台,也是交流碰撞的场所。

下面,我按照现场流程和主题,分享我的参会见闻、优秀项目亮点和一些技术心得,供大家参考。

image.png

二、参会经历

1、行前准备与抵达

我提前一天入住了大赛项目组给安排好的苏州市国际会议酒店,第二天到会场就能感受到主办方在流程和细节上的专业。大家签到有序,现场的问题咨询与临时需求都能及时处理,为开发者紧凑的一天提供了良好保障。

image.png

image.png

2、会场氛围与组织

会场把展示、答辩和交流区划分得很清楚,观众流线顺畅,方便评委和观众近距离互动。每支队伍都有固定演示时间,时间控制严格但合理,有助于突出技术亮点和应用场景。

打卡区:投个飞机

image.png

打卡区:姿势生成

image.png

打卡区:热印工坊

image.png

打卡区:抽个彩蛋

image.png

毫不夸张的说,亲眼目睹了现场好多同学中了华为手环

3、作品演示与答辩(核心看点)

本届比赛涌现了许多把 AI 能力和位置服务(LBS)深度结合的好点子,下面挑几个我认为比较有意思的做个简介:

1、AI+LBS 智能露营寻址助手

这个项目超越了传统的 POI 检索,结合地图与 AI 的图像/语义分析,能发现地图上未标注但适合露营的空地。强调可解释性:不仅给出推荐地点,还能说明理由(比如地形、遮蔽、距离等)。实现上融合了空间特征提取、语义分类和安全性判断。

image.png

image.png

2、“15分钟生活圈”与双层 AI 助手

方案把用户需求分为“生活类”和“商业类”两条链:生活类走轻量推荐以保证速度;商业类触发深度分析,做竞品检索、热度评估并输出选址报告。关键技术有意图识别、检索与生成的动态 RAG 策略,以及对 Token 和成本的优化。

image.png

image.png

3、智慧农业:一图管全局的“高高种地”

把农业管理功能做在一张可视化地图上,包含地块管理、气象、路径导航和农事记录。高精度地块依赖 LBS,图像识别用于作物和病虫害检测,目标是让农业管理更精细、可追溯,方便决策。

image.png

4、“爪爪足迹”智能遛狗小助手

针对不同用户(如精致养宠青年、新手主人、老年人养犬),系统提供个性化遛狗路线、宠物友好 POI、天气与健康提醒,并通过打卡与成就体系提升粘性。项目充分利用了 POI 搜索和路径规划,重视长期行为设计。

image.png

5、盲人导航的社会价值实现

这是技术与公益结合的典型案例:团队把导航从“算路线”扩展为“实时安全决策 + 语音陪伴”。思路是端到端多模态闭环(语音→意图理解→空间 API 调用→决策生成→语音反馈),并接入行走态的实时环境感知(如连续帧的 YOLOv8 检测),把视觉识别结果转成可听的风险提示。

image.png

6、云端旅游“星途旅人”

基于高德地图数据和 AI 虚拟分身,提供“身未动而心已远”的云端旅行体验。难点在于怎么呈现沉浸感和设计可交互的内容。

image.png

当然还有很多其他优秀作品,无法逐一列举,但都展示了 AI 与 LBS 结合的巨大潜力。

三、与行业大咖面对面

下午是颁奖和大咖分享环节,几位专家的演讲很有干货,我记了几点要点:

1、高德“时空智能百宝箱”——产品总监分享

高德的核心是解决“我在哪、要去哪、怎么去”的问题,分享聚焦定位 SDK、POI 检索、地理围栏和 AI Agent,强调把这些能力组件化、工具化,方便开发者在不同场景中快速复用。

image.png

image.png

image.png

image.png

2、AI 时代的地图与定位——定位系统首席专家

演讲讲了导航的演进:从基础地图到车道级定位,再到实时事件感知(比如急刹车检测)。地图正从静态数据向实时感知与决策能力转变。

image.png

image.png

image.png

image.png

3、高德地图与鸿蒙生态协同——华为鸿蒙生态负责人

双方在终端与云端协同、能力互通和开发者工具链上有深入合作,这对想覆盖鸿蒙和安卓的团队是重要机会。

image.png

image.png

image.png

image.png

四、颁奖环节

本次大赛给开发者设置了多个奖项,可以看到,非常丰厚。

  • 特等奖:奖励总额超 10 万元
  • 一等奖:奖励总额超 3 万元/组
  • 二等奖:奖励总额超 2 万元/组
  • 三等奖:奖励总额约 1 万元/组
  • 鸿蒙特别奖:奖励总额超 2 万元/组

奖项.png

2.png

虽然本次未获大奖,但收获颇丰,下次继续加油。

五、收获

个人收获可以概括为几点:

  • 技术层面:AI 与 LBS 的结合带来了更多落地场景,关键在意图识别、空间语义映射和 RAG 类检索的融合策略。
  • 产品层面:成功产品不是技术堆叠,而是将能力与用户真实需求对齐,先保证可用性和可靠性。
  • 工程实践:高质量的 POI 数据、持续标注和模型迭代是长期效果的保障;多模态融合要设计好降级策略以提高鲁棒性。
  • 生态与合作:开放能力与平台化能为创业团队加速,但也对架构和可扩展性提出更高要求。

六、总体体验

参赛感受:

这次大赛给我带来了充实且具体的收获——既有技术层面的启发,也有产品与工程实践的反思。与现场的团队和观众面对面交流,互相休息,让很多抽象的想法变得可讨论、可验证。

会场体验:

会场组织井然,展示与答辩区划分合理,现场有明确的打卡与咨询服务点,互动氛围很好。评委提问直接且具有针对性,能迅速把注意力拉回到产品价值与实现细节上,这对参赛者很有帮助。

行程体验:

高德官方为所有获奖开发者安排了住宿,并提供了全天餐饮,现场也有补给与礼品。大多数参赛者乘坐高铁或飞机到达,相关人员会提前沟通交通路线或报销信息,总体体验很棒。

最后特别感谢主办方与所有参赛开发者的辛勤付出。期待下一届能看到更多把 AI 与 LBS 更紧密结合、真正解决用户场景问题的项目。

image.png

🤯 为什么我的收银台在鸿蒙系统“第一次返回”死活拦不住?一次差点背锅的排查实录

作者 asing
2026年1月11日 11:36

结论先行:
不是你代码不行,也不是你方案不对,而是 纯血鸿蒙压根不给 H5 机会拦第一次返回
我也是查到第三天,才敢确定这不是我的锅。

一、事情是怎么开始的(背锅预警)

事情发生在一个看似平平无奇的需求里:

👉 收银台页面,用户点左上角返回时,弹一个“挽留弹窗”

老需求了吧?
我内心 OS: “这不就是 history + popstate 的事吗,闭着眼写。”

IMG_2422.PNG

结果上线后,测试同学一句话把我干懵了:

“纯血鸿蒙手机有问题,刚进收银台直接返回,弹窗不出来。”

我第一反应:

不可能!
Android、iOS、Web 全部 OK!
你是不是点太快了?

然后她给我录了个视频。

我沉默了。


二、这个 Bug 有多“阴间”?

先描述一下诡异现象

操作 是否拦截
进入收银台,啥也不点,直接点返回 ❌ 不拦
进入收银台,啥也不点,左滑返回 ❌ 不拦
随便点一下页面,再返回 ✅ 正常
正常浏览页面再返回 ✅ 正常

重点来了👇

“点一下屏幕,哪怕什么都没点中,问题就消失了。”

这时候你会怀疑什么?

  • history 写错了?
  • popstate 没监听?
  • 路由冲突?
  • 鸿蒙 WebView 有 Bug?

我也是这么想的。


三、我排查过的所有“错误方向”(踩坑合集)

为了证明不是我菜,我做过这些事:

  • ✅ 改成 hash 拦截
  • ✅ history.go(-1) / go(1) 各种组合
  • ✅ 监听 popstate、hashchange
  • ✅ React Router / 非 Router 对比
  • ✅ console.log 打满
  • ✅ 换三台鸿蒙手机

结果发现一个残酷事实:

JS 代码在“第一次返回”时,压根没执行。

不是执行了逻辑不对,
根本没机会执行


四、真相揭晓:鸿蒙的「用户激活机制」

最终答案来自一篇几乎没人看的 ArkWeb 说明 + 无数次验证。

核心结论一句话:

在纯血鸿蒙系统中,如果 H5 页面“未发生任何用户交互”,
返回事件不会分发给 JS。

换句话说:

页面刚打开
↓
用户没点、没滑、没触摸
↓
系统认为:这个页面还没“激活”
↓
返回事件 → Native 直接处理
↓
JS:?我还没醒

而一旦你:

  • 点了一下
  • 滑了一下
  • 随便触摸一下

页面就会进入 Activated 状态

JS:我醒了,我能拦了

这也完美解释了:

“为什么点一下就好了。”


五、所以问题到底是谁的?

我可以很负责任地说:

❌ 不是前端实现 Bug
❌ 不是产品逻辑问题
❌ 不是测试点刁钻

👉 这是鸿蒙 WebView(ArkWeb)的系统级策略。

你用再优雅的 H5 方案,也绕不过这一点。


六、那还能不能做?能,但得认清边界

先给现实结论(很重要)

没有 Native 桥的前提下

  • Web / Android / iOS:✅ 100% 可拦

  • 纯血鸿蒙:

    • 用户有过任何交互 → ✅ 可拦
    • 零操作直接返回 → ❌ H5 无法保证

这是能力上限,不是技术水平问题。


七、我们最后是怎么“体面解决”的?

方案一:产品侧(强烈推荐,性价比最高)

👉 让用户“自然产生一次交互”

比如:

  • Skeleton 骨架屏
  • “正在加载订单信息…”
  • 首次点击任意区域消失的提示

99% 的用户都会:

  • 等一下
  • 点一下
  • 滑一下

页面立刻进入激活态。

问题基本消失。


方案二:技术侧(终极解,但要 Native)

如果你能改 App:

由 Native 拦截返回,H5 只负责弹窗。

这是 100% 稳定解
也是鸿蒙下所有大厂最终都会走的路。


八、给后来人的一句忠告(血泪)

如果你在鸿蒙里遇到“第一次返回拦不住”:
先别怀疑人生,也别重构代码。
你大概率遇到的是系统 User Activation 限制。

认清边界,比写更多代码重要。


九、写在最后:为什么我要写这篇文章?

因为我当初在掘金、GitHub、博客:

👉 没搜到一篇把这个问题讲清楚的文章。

如果这篇文章能帮你:

  • 少查 2 天
  • 少背一次锅
  • 少怀疑一次自己

那我就赚到了。


👍 如果你觉得有用

  • 点个赞,让更多人少踩一个坑
  • 留个评论,说说你遇到过的“鸿蒙玄学”
  • 收藏一下,下次出事能拿出来对线

我们写代码已经够难了,
至少不该为“不是自己造成的问题”加班。

鸿蒙走向「好用」的背后:是 1000 万开发者愿意多走一步

作者 艾 梵
2025年12月30日 12:00

对于鸿蒙而言,2025 年没有旁观者——

因为参与这件事,从来只有两种结果:要么你见证了它的成功,要么你帮助它走向成功。

而事实上,这两者往往同时发生。

2025 年是真正意义上鸿蒙系统的大年,而如今判断一个操作系统成功与否,发布会上的技术参数已经不是唯一的答案。

它更显性地体现在我们的日常生活中——在「碰一碰」分享的便捷里,在跨设备处理的无缝流转里,在 AI 主动响应的默契里。

具体到这一年鸿蒙的变化上,趋势其实很容易发现:它正从「执行命令」转向「主动服务」。而这一趋势的背后,除了技术的迭代,更有一幅由无数开发者、伙伴与用户共同奔赴的图景在推动。

在今年,你能看到鸿蒙生态展现出一种长期积累后的反应力:用户和开发者的生态位不再是一成不变的。用户更积极地表达对鸿蒙应用体验的期待,而开发者更主动地倾听。

用户和开发者的生态位之间产生了流动性,大家都成为了鸿蒙的共建者,协力创造出更好的体验,而这种流动性释放出前所未有的创造力。

他们的需求被倾听,他们的创造被支持,他们的故事被看见。「被看见」成为一种可预期的机制,而不是碰运气。

于是,鸿蒙生态自成一片值得投入的蓝海:无论是成熟的开发团队,还是一台电脑就能创作的独立开发者,都有机会让自己的创意被看见、被放大,一位鸿蒙开发者告诉爱范儿:

鸿蒙是能让我们变成「第一个吃螃蟹的人」的平台。

截至今年,累计已经有超过 23000 款应用,参与了 70 多项鸿蒙系统级体验的联合打造。微信、京东、小红书等伙伴,都选择把关键能力在鸿蒙上首发。

与此同时,华为在开发者侧的资源投入,也给出了更加丰厚的资源:HDC 2025 发布了「鸿蒙星光计划」,宣布投入 1 亿元现金和资源支持开发者;也是在今年,华为还启动了面向鸿蒙 AI 生态的「天工计划」,拿出 10 亿元支持开发者参与生态创新,让更多 AI 创新功能落地——

在机遇和回报的双轮驱动下,鸿蒙生态得以吸引越来越多的开发者、生态伙伴和终端用户投身其中。 一个操作系统生态如此鲜活,我们已经许多年未曾见过了。

把「微笑曲线」写进日常

通勤时的便捷分享、会议里的资料流转、下班路上的内容接续——深度使用鸿蒙设备的这一年,我发现有些节奏已经悄然发生改变。

它的价值不再由「手机系统」或「电脑系统」来界定,更多显现在「通勤」「办公」「娱乐」等一个个真实的生活切面中。

这当然不是一帆风顺的,余承东在发布会上就将它比喻成「微笑曲线」:

第一次上手,流畅、新鲜,系统的各个方面都很讨喜;

用了一段时间,会逐渐发现一些细节的卡点,或者部分应用功能和体验的缺失;

但多用一段时间,会发现常用软件越来越多,功能逐渐完善,常用场景被一块块补齐——鸿蒙系统,越用越好用了。

这条曲线的背后是上千万开发者的共同努力。

今年我们和鸿蒙的开发者们走得越近了,甚至在将爱范儿 App 鸿蒙版提上日程之后,我们也成为了这 1000 万开发者之一。

要说鸿蒙开发者有哪些不同,我想其中重要的一点就是:听劝。

在鸿蒙生态里,用户的声音可以写进「心愿单」,进入到开发者的迭代清单里。官方数据显示,2025 年已经有 200 多万条心愿单被落地推进。

这就像一份持续生长的路线图,汇聚了每一个使用者对更好体验的期待,形成高频的反馈回路,让用户建议高效落实为版本更新。它既是系统洞察需求的窗口,也成为开发者获取灵感的源泉。

爱范儿了解到,不少开发者都从「心愿单」中找到了努力的方向——比如,一封来自孕期用户的长信,促使播客 App 小宇宙决定开发鸿蒙版,并且在短短三个月内就完成开发并上线;许多小红书用户爱用的圆周旅迹,在鸿蒙用户的强大呼声和技术专家的支持下,只用了短短一个半月时间就部署上线。

诸如此类的「反馈 – 迭代 – 焕新」故事并不稀奇;用户的心愿每被兑现一次,产品的体验就往前挪一步,鸿蒙的生态也更趋于完善,用户、系统和开发者之间的距离,也就更近了一点。

用户把期待写下来,开发者把它做出来,系统把它承接并放大——这就是鸿蒙生态增长的方程式。

当「适配」变成「共创」

过去一年里,鸿蒙开发者多走了一步,多做了很多工作:

重构工程、补充文档和示例、做实机验证——甚至基于鸿蒙平台做全新的功能创新和开源共建。

比如,小红书一直是鸿蒙生态的排头兵。

它在今年开源了和华为鸿蒙共建的自研播放器能力。这种反常规的行为,背后是实打实要花人、花钱、花时间的事。

但小红书还是这么做了。没有什么比这一步,更能体现生态伙伴对于鸿蒙系统的信心。

可以说,把应用上架鸿蒙平台只是开始而已,真正的共创是将应用在鸿蒙系统里长出新的可能——今天你把路修平,明天别人就能在这条路上跑出新的玩法。

千万开发者「多走一步」,构成了 2025 年鸿蒙生态最深刻的转变:开发者从「适配者」,进化为「共创者」,甚至生态的「定义者」。

微信的鸿蒙团队,就非常符合这个定义。微信团队曾坦言,适配鸿蒙「堪比重做」:

从 24 年 10 月内测开始,截至 25 年 10 月,一共发了 12 个大版本、109 个小版本,几乎两三天就有一次版本提交。

这种频率和认真程度,早已跳出了「适配」的范畴,事实上,这就是在做一个全新的产品。

更直观的信号,是「首发」和「独有体验」越来越集中出现在鸿蒙上。 比如在 6 月的华为开发者大会主会场,一个初创团队做的 3D 空间记忆 App「Remy」,获得了和腾讯、阿里同等量级的曝光。

再譬如首发登陆鸿蒙的独立游戏《太吾绘卷》,已经和《原神》《三角洲行动》坐一桌,被视为点亮鸿蒙游戏生态的「星光」。

鸿蒙在 2025 年做的另一件关键工作,是把很多「本来要应用自己搭」的能力,变成了开箱即用的底座能力:

一次开发,多端部署;分布式能力、碰一碰分享、跨设备剪贴板、应用接续……鸿蒙定义了这些标准能力,把开发门槛降了下去,也就把体验的上限抬了起来。

互联底座之外,鸿蒙今年把 AI 也做成了更标准化的「工程件」:

  • Harmony Intelligence 的能力正在持续开放;
  • 小艺智能体平台已经提供了 4 种开发模式与 50 余种组件;
  • 针对具体的 AI 领域,鸿蒙还提供了上百种细分的开发套件和标准意图等等……

随着这些能力到位,对于鸿蒙开发者而言,给自己的产品接入 AI 能力可以说是手到擒来,而一系列扶持计划的资金和资源,则让鸿蒙开发者的智能体探索,实现更大规模的落地。

过去这一年里,爱范儿也体验了许多鸿蒙生态产品,让我们印象最深刻的场景有两个:

一个是用手机轻贴电脑屏幕,图片就能直接落进 WPS 的 PPT,操作流转变得不费脑:手机里的图片贴近电脑就能进 PPT,文档在不同设备上接续,不用先想「存哪、传哪、再打开」。

另一个是,在阅读、写作这类场景,也实现了对碎片化生活的适配:换一台设备就能接着看、接着写,顺手就能拉起 AI 摘要或朗读——当生态互联与 AI 智能体相辅相成时,更多的精力就留给了内容与体验。

甚至,爱范儿自己的鸿蒙 App ,也是鸿蒙底座能力的一种体现:

手机上没看完的文章,换到平板、PC 继续阅读;语音播报、内容摘要、跨设备流转,这些原本不支持的功能,如今更是无需从零搭建方案,就能快速实现。

众多开发者,今年做鸿蒙开发的体验,大体和我们相同。

取舍变得更加简单,系统提供底层能力,产品团队只需聚焦在内容和体验上,再也不被各个硬件终端之间的差异拖住开发/适配进度。

这种轻松、敏捷的开发体验,让哪怕只有三五个人的小团队,也能把产品从手机延伸到平板、电脑、车机等更多场景。

这些中小开发者当中,最令我印象深刻的莫过于独立开发者李尚儒。法律专业出身的他自学编程,目前在鸿蒙上已经开发出近 20 款独家应用,而且精准切中了「咖啡因记录」「日出日落时间预测」等小众需求,最高的时候通过鸿蒙获得的月收入达到 7 万元。

「鸿蒙提供的功能,能让一个人调动一个系统的力量」,李尚儒说。

我想,这也是符合「超级个体」时代的系统级能力。

聚是一团火,散是满天星

鸿蒙生态 2025 年的故事,是一个关于「可能性」的良性循环:

系统为开发者打开「新体验的可能性」,开发者则反过来为系统填充「新场景的可能性」。

过往操作系统中心化的分配体系,在鸿蒙这里被打破——新树立起的是开放的共创平台。

正是这种对每一个共建者价值的认可,让「聚是一团火,散是满天星」成为鸿蒙生态日拱一卒、蓬勃生长的真实写照。

如今,鸿蒙5和鸿蒙6的终端设备数突破 3600 万,应用市场可搜索应用及元服务数量突破 35 万,注册开发者突破 1000 万……这些数字标志着鸿蒙穿越了「微笑曲线」,进入了持续加速进击的全新阶段。

鸿蒙生态 2025 年的叙事,最终是一部关于「看见」与「实现」的交响乐。

它让每一位开发者的微小创新被系统放大,也让每一个细微的体验痛点得到回应。

它验证了一条生态发展可持续的路线——垄断与捆绑是过时的逻辑,在开放舞台上的价值共鸣与相互成就,才是新时代的操作系统该有的样子。

而对每一个参与者来说,这场旅程没有输赢:你若投入,便不会空手而归。

因为鸿蒙的成长,终将成为你的一部分。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


鸿蒙生态这一年:成为「确定性」,创造「可能性」

作者 艾 梵
2025年12月23日 18:00

在鸿蒙设备全面铺开的这一年里,有个问题被反复追问:

我想换鸿蒙,App 够用吗?

无独有偶,36氪和后浪近日联合发布的《加速进展的鸿蒙6:从尝鲜,到常用|年轻人 App 使用白皮书》,通过对年轻用户的深度调研,用数据量化了这种关注:

鸿蒙用户最看重应用的哪些体验?功能完备度(82.1%)、整体流畅度(78.6%)和安全性(71.4%)——这三项关注度排名极高。

这意味着,选择鸿蒙的人,对应用的需求已经不局限于「能用」,而是有着更高的期待。

好在,白皮书也让我们看到了鸿蒙交出的答卷:超 57% 的用户认为,鸿蒙应用在隐私安全方面表现更优,50% 的用户认可鸿蒙应用整体的操作流畅度。

当被问及当下鸿蒙6对比鸿蒙5的生态表现时,超 7 成用户认为鸿蒙6生态有明显进步。

在这一反馈背后,是一个个真实可感的使用场景——淘宝下单直接享受国补,微信读书能随手写下想法,手机、平板、大屏直接无缝续播抖音视频。

所有细节的进化,都指向了同一个结论:

今天的鸿蒙应用生态,绝对值得一试。

适配度超 95%,鸿蒙仍在加速

超过 95%。

这是鸿蒙生态交出的应用适配成绩单。

更具体地说,超 35 万应用及元服务正在加速开发和更新,覆盖诸多垂域;微信、抖音、支付宝等等头部应用基本实现了全面适配,中长尾应用的适配也在全力推进。

就此而言,我们已经拥有了把鸿蒙设备作为主力机使用的底气。那些数亿人在用的 App 都开发了鸿蒙版,为生活的不同场景提供了坚实支撑。

在今天的鸿蒙6上可以看到,腾讯系有超 60 款应用上架,阿里旗下淘宝、闲鱼、1688 等应用基本实现全适配,字节系的抖音、番茄小说、红果短剧等应用也在快速迭代,越来越好用。

聊微信、刷抖音、逛淘宝、点外卖、打滴滴……这些日常的高频操作,在鸿蒙6上可以说是毫无阻碍。

除了应用覆盖的广度,更重要的是加速度。

加速度,意味着鸿蒙的应用适配不仅是快,而且是「又好又快」。

2024 年 6 月 TOP5000 应用全面启动鸿蒙版开发,到 2024 年 10 月,已有超 15000 个鸿蒙应用和元服务上架,而这个数字,到今年 10 月,已经又翻了一倍,超 30000 款应用和元服务上架鸿蒙生态,具体到应用内部的功能,也在不断迭代和完善。

以微信为例,对比年初的鸿蒙版微信 1.0 版本,如今的最新版鸿蒙微信堪称脱胎换骨。

早期鸿蒙版微信不能抢红包、没有长按翻译,甚至连深色模式都不支持,而历经三百多天、一百多个版本的更新,鸿蒙微信已经补齐了绝大多数功能,连朋友圈发实况照片这样的新能力,最近也已经上线支持。

▲ 截至 11 月的微信适配功能概览。图片来自:哔哩哔哩@请不要叫我测评君

微信开发团队曾坦言,适配鸿蒙堪比重做——过去十几年积累的功能无法直接迁移,必须重写。

但正是这种从零构建,反而让鸿蒙微信获得了许多独特体验:通话中可以听语音消息、碰一碰快速登录、直接调用系统相机拍摄高质量照片和视频,等等。

用微信开发团队自己的话讲:

我们的终极目标,就是要对齐鸿蒙微信与 Android 和 iOS 的体验,甚至有所超越。

这种「适配即进化」的现象, 正在鸿蒙生态的各个应用类别中上演。

比如,作为年轻人最常用的社交类应用代表,小红书基于鸿蒙用户习惯的指关节动作,首发了「圈选搜笔记」功能,指关节圈选屏幕即可搜到相关笔记,一经推出就大受欢迎。

而与我们工作息息相关的 WPS ,更充分利用鸿蒙分布式技术进行创新:用鸿蒙手机拍照,碰一碰就能将照片自动插入电脑、平板正在编辑的文档中;针对 MateBook Fold 折叠屏电脑首创的「双屏掌控模式」,更能将副屏变为 PPT 控制台,实时调取提词和备注。

诸如此类的定制功能开发,表明开发者不再把鸿蒙适配视为简单的「移植」,而是一次重新思考用户体验、创造独特价值的机会。

愿意在创新中投入精力,正是国民应用们对鸿蒙生态的最大认可。

数据也证实了这一点:23000+ 应用参与创新,带来 70+ 系统级创新体验,2000+ 日均新增功能,8800万+ 用户日均下载/更新应用。

这样的数字背后,是鸿蒙生态正在形成的良性循环——用户因系统及生态选择鸿蒙,而生态也因系统升级和用户增长而加速适配。

1000 万开发者,为「确定性」和「可能性」而来

如今超 1000 万注册开发者,已经是鸿蒙生态得以蓬勃发展的中坚力量。

要知道在几年前,「鸿蒙开发者」还是稀有品种,这其中从无到有的「追及」绝非易事。

鸿蒙生态有着截然不同的技术框架和编程语言,强如微信团队,也「小步快跑」了一整年。自 2024 年 10 月鸿蒙微信开始内测以来,截至 2025 年 10 月 13 日,共发布了 12 个大版本、 109 个小版本,平均每 2.29 个工作日即有一个版本提交鸿蒙应用市场发布,终于做到了与其他系统版本体验平齐。

如此快速迭代,本质上是大公司在用「投入」换「时间」。

因为鸿蒙不可忽视——通过软硬件协同开发,鸿蒙在硬件产品形态海量创新的同时,也在持续夯实基础体验,让鸿蒙终端设备的数量从日增一万到日增十万,短短一年时间,已经突破了一个又一个千万。

从世界范围来看,也未见过增长如此迅猛的生态。

对所有大公司而言,错过鸿蒙生态,意味着错过数千万的新增用户,更意味着错过未来十年覆盖度最广的硬件入口——毫无疑问,所有大厂都应该倾力投入。

那么,对于资源不甚充裕的中小企业、个人团队呢?

鸿蒙直接给机会。

在鸿蒙全面铺开后,华为每年投入超过 60 亿元支持开发者创新,今年 9 月宣布的「天工计划」,又加码 10 亿元促进 AI 生态创新。

在 6 月的 HDC 开发者大会上,3D 空间记忆 App「Remy」迎来首秀:它能扫描现实空间,将其建模变为可回放的记忆。

在主会场,鸿蒙愿意把稀缺的曝光交给这个初创团队,让全球数千万鸿蒙用户看见这款来自中国本土的创新应用。

▲ Remy 建模演示

这盏来自世界舞台中心的聚光灯,本身就具备极强的吸引力。

值得一提的是,过去一年里,鸿蒙生态的开发工具也在飞速迭代,连同鸿蒙统一的设计规范与多端框架,让许多中小团队原本「够不到」的体验变成了可实现的方案。

譬如爱范儿的鸿蒙版 App,只需要一次开发就能适配手机、电脑、折叠屏、智慧屏等不同规格的设备,并且得益于鸿蒙的 AI 能力,可以快速实现语音播报、AI 总结等原版不具备的新功能。

▲ ifanr App 内容流转演示

过去需要投入巨大开发成本才能实现的产品体验,在鸿蒙生态中变得触手可及,而这正是鸿蒙生态的魅力所在——

它既为巨头提供了通往未来的确定性,也为中小开发者提供了实现跃迁的可能性。

完美的生态不是等来的。

我们如今许多理所应当的事物,都是逐步演进而来的——最早,生物的双眼,仅仅是两个能感光的斑点,是在漫长的岁月中边试错边迭代,才进化至如此精密复杂。

先要有,才会好。

3200 万终端设备,彰显生态价值

3200 万,就在我们查阅报告撰稿的这一刻,鸿蒙终端设备的数量仍在飞速增长。

为什么大家喜欢鸿蒙?

前面提到,被问及鸿蒙的优势时,《白皮书》显示,用户的高频回答里除了「隐私安全感更强」,就是「流畅度更佳」和「稳定性更强」。

那么,鸿蒙到底有多「流畅」?

用鸿蒙版 WPS 打开一个超过 1GB 的 PPT 文件,几乎是秒开,上下拖动也毫无卡顿。极限场景下的从容,正是消弭焦虑感、提升体验确定性的关键。

▲ 图片来自:微博@墨鱼Pro

真正的流畅感,不局限于动画和加载速度,还在于情绪上的顺畅和效率提升。

在鸿蒙,你不需要「找」服务,因为服务会主动来找你。

在拍照时,AI 会自然浮现出构图建议;收到可疑信息时,它会自动弹出隐私保护提示;刷着视频时,打车的进度、外卖的状态可以通过「实况窗」主动悬浮展示,无需反复切换查看。

更主动的同时,服务也变得更轻量。无需打开 app,美团「骑车桌面卡片」、负一屏「本地生活」等元服务,让解锁骑车、生活缴费等日常服务触手可及。

这是鸿蒙「意图框架」的真正胜利:

通过打破应用围墙,鸿蒙系统极大地缩短了服务路径,让服务能够自由组合、识别意图,在我们最需要它的那一刻,即时出现。

此前,外界担心从零构建的应用,会成为鸿蒙生态的阻碍。但如今看来,重构反倒成了一个契机。

从系统到应用的贯通,让众多主流应用完成「瘦身」,不仅启动更快、广告更少,还能以一种更原子化、更轻盈的形态重新服务于人。

进入鸿蒙生态,应用变得更为简洁、透明,而我们的数字生活也在告别冗余之后,显得格外清爽、可控。

当超半数年轻人认为流畅度、稳定性与隐私安全是鸿蒙应用的优势时,数字生活的「掌控感」终于回归。

站在 2025 年末节点回望,所有信号都表明,鸿蒙已经跃过了「临界点」。

最艰难的「拓荒期」已成历史。

如果说此前投入鸿蒙生态开发是一场赌上运气的冒险,那么如今的鸿蒙6,已是一个基础设施完备、有千万级流量支撑的可靠入口,值得所有开发者奔赴其中。

而对于你我而言,当下就是感受鸿蒙生态的最佳时机。

从《白皮书》中可以看到,超过 40% 用户认为,鸿蒙6的应用更多、功能更完善,比之前的版本有所进步,更有 32.9% 的用户认为,鸿蒙6的生态体验有明显跃升,比其他版本更顺滑。

另外,38.4% 的年轻人表示愿意体验新技术、非常愿意尝试鸿蒙系统。

显然,鸿蒙生态的快速成长、不断完善,正在吸引越来越多用户加入。

应用适配已经到位,操作体验兼具安全流畅,硬件产品还有补贴——对鸿蒙的感受,正在逐渐变成一种享受。

鸿蒙生态这一年,「确定性」已然确立,而更多的「可能性」,正在不断被创造出来。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


❌
❌