《Web 到 HarmonyOS》-- 进程、线程与通信:谁能通、怎么通

写在前面

日常写页面,90% 的通信都发生在 主进程 · 主线程 的组件树上------和 Vue 的 props / Pinia / mitt 几乎同构。

只有卡 UI、跑卡片 Extension、嵌 Web、跨应用时,才碰到线程 / 进程边界。

本篇阅读顺序:

  1. 组件通信(状态怎么共享)
  2. 事件通知(EventHub / Emitter)
  3. 进程 / 线程是什么 ,以及 谁能通、怎么通
边界 能不能直接共享对象 典型手段
同进程 · 同线程(组件树) 可以 @State 系 / Storage / EventHub
同进程 · 跨 ArkTS 线程 否(要序列化) TaskPool / Worker / Emitter
跨进程 / 跨应用 否 CommonEvent / Want / IPC / Web 桥

一、组件通信:状态共享(同进程 · 同线程)

业务特征:改数据 → UI 自动刷。优先用状态,别一上来 EventBus。

1.1 按共享范围选型(对照 Vue)

共享范围 鸿蒙 Web
组件自己 @State ref / reactive
父子单向 @State + @Prop props
父子双向 @State + @Link v-model
嵌套对象深层字段 @Observed + @ObjectLink 深层 reactive
跨很多层、同棵树 @Provide / @Consume provide / inject
同 Ability 多页面 LocalStorage + @LocalStorageLink/Prop 页面级 store
应用全局(同进程) AppStorage + @StorageLink/Prop Pinia
重启还要在 PersistentStorage(经 AppStorage) persist 插件 / localStorage

官方优先级(范围能小则小):

@State+@Prop/@Link/@ObjectLink → @Provide/@Consume → LocalStorage → AppStorage

1.2 组件内:@State ≈ ref

ts 复制代码
@State isLoading: boolean = false
@State title: string = '首页'

只读、从不改、或不绑 UI 的字段,不要标成状态变量(有性能开销)。

ts 复制代码
// 父
@State count: number = 0
Child({ count: this.count, countLink: $count })

// 子:单向
@Prop count: number = 0

// 子:双向(父用 $ 传引用)
@Link countLink: number
@Prop @Link
绑定 单向(深拷贝) 双向(引用)
复杂对象 拷贝贵 更省
适合 编辑页「确认前不同步父」 滚动同步、实时联动

嵌套 class 的字段变化要用 @Observed + @ObjectLink,否则子属性改了 UI 可能不刷。

1.4 跨层:@Provide / @Consume ≈ provide / inject

ts 复制代码
// 祖先
@Provide('theme') themeColor: string = '#0A59F7'

// 任意后代,按名或别名匹配
@Consume('theme') themeColor: string

适合主题、导航栈、登录用户简要信息等「整棵树全局一点、又不想层层 props」的状态。

改动极频繁、结构巨大的,仍优先拆小或放到合适的 Storage。

1.5 页面级 / 应用级:LocalStorage / AppStorage

ts 复制代码
const pageStore = new LocalStorage({ tabIndex: 0 })
@Entry(pageStore)
@Component
struct Home {
  @LocalStorageLink('tabIndex') tabIndex: number = 0
}

@StorageLink('userName') userName: string = ''
LocalStorage AppStorage
范围 Ability / 页面级组件树 应用进程内全局
生命周期 无引用可回收 进程起创建、进程终回收
典型 当前 Tab、页内筛选 用户信息、语言、全局开关

持久化用 PersistentStorage.persistProp(经 AppStorage)。无 UI 同步的纯缓存更适合 Preferences。

AppStorage 不能跨进程,别当跨 Ability 进程的数据库。

1.6 Ability / 页面跳转带参

Want / 路由参数 = 启动时一次性信封(≈ router.push({ query }))。

跳转后还要持续共享 → 再用 LocalStorage / AppStorage。


二、事件通知:EventHub 与 Emitter

业务特征:组件树对不上,或不想用状态硬绑------只要「发一下、收一下」。

能状态驱动就别滥用事件 ;更不要用 @StorageLink 当事件总线(会误刷一堆 UI)。

2.1 怎么选

场景 手段 Web 对照
同一 UIAbility 内、主线程 UI ↔ 页面 / VM EventHub (context.eventHub) 页面内 mitt
同进程内可能跨线程(含 TaskPool / Worker) Emitter 可跨 Worker 的事件总线
想强类型 API Emitter 外包一层 EventBus 自封一层 mitt
EventHub Emitter
定位 Ability 上下文上的轻量发布订阅 带队列 / 优先级的事件机制
作用域 某个 context 同进程线程内 / 线程间
跨进程 ❌ ❌
另有 --- once、优先级
数据 较自由 EventData,有大小上限(常见约 16MB)

口诀:主线程 UI 里简单广播 → EventHub;涉及子线程或要跨线程 → Emitter。

2.2 EventHub(同 Ability · 主线程)

ts 复制代码
// 订阅(注意在 aboutToDisappear / onDestroy 里 off)
this.context.eventHub.on('loginSuccess', (userName: string) => {
  // ...
});

// 发送
this.context.eventHub.emit('loginSuccess', 'Alice');

EventHub 不是全局单例 ,跟具体 context 绑定;换 Ability / 跨进程都无效。

2.3 Emitter(同进程 · 可跨线程)

ts 复制代码
import { emitter } from '@kit.BasicServicesKit';

const EVENT_ID = 1;

emitter.on({ eventId: EVENT_ID }, (eventData: emitter.EventData) => {
  const value = eventData.data?.value;
});

emitter.emit(
  { eventId: EVENT_ID, priority: emitter.EventPriority.HIGH },
  { data: { value: 2 } }
);

emitter.off(EVENT_ID);

组件或任务结束务必 off。业务可包强类型 EventBus(序列化 + 泛型),见 掘金封装。


三、通信总原则(先看边界再选 API)

到这里,日常组件和事件已经够用。下面进入边界:线程 / 进程。

text 复制代码
同进程同线程? → 状态装饰器 / EventHub(第一、二节)
同进程跨线程? → TaskPool / Worker / Emitter
跨进程 / 跨应用? → CommonEvent / Want / IPC(第一节的 Storage、第二节的 EventHub/Emitter 都过不去)
手段 同线程 同进程跨线程 跨进程 跨应用
@State / @Prop / @Link / Provide ✅ ❌ ❌ ❌
LocalStorage / AppStorage ✅(主线程 UI) ❌ ❌ ❌
EventHub ✅ ❌ ❌ ❌
Emitter ✅ ✅ ❌ ❌
TaskPool 入参/返回值 --- ✅ ❌ ❌
Worker postMessage --- ✅ ❌ ❌
Want 启动传参 ✅ ✅ ✅(一次性) ✅(有权限)
CommonEvent ✅ ✅ ✅ ✅(注意权限)
IPC / RPC --- --- ✅ ✅
Web 桥接 --- --- ✅(主进程↔Render) ---
flowchart TD A{同一进程} A -->|否| B[CommonEvent Want IPC Web桥] A -->|是| C{同一 ArkTS 线程} C -->|否| D[TaskPool Worker Emitter] C -->|是| E{状态还是事件} E -->|状态| F[State 系或 Storage] E -->|事件| G[EventHub]

掘金粘贴注意:

  1. 代码块语言选 mermaid(不要选 text)
  2. 从本文件纯文本复制,勿带网页转义字符
  3. 节点文案避免 @、/、嵌套 subgraph(掘金易空白);特殊字符用双引号包起来,写法对齐本系列第 07 篇---

四、进程是什么(Stage 模型)

进程是系统分配资源的基本单位(独立地址空间)。进程 A 崩了,默认不拖死 B。

Web 类比:浏览器里标签 / 扩展 / 渲染进程各自隔离;鸿蒙把隔离做进应用的 Ability 组合里。

4.1 一个应用里常见进程

进程类型 谁跑在里面 Web 粗类比
主进程 默认所有 UIAbility;以及 Service / DataShare Extension SPA 主进程
Extension 进程 同类型 Extension(卡片、输入法等;不含上面两个例外) 插件 / Widget 进程
Render 进程 Web 的网页渲染 / JS Chromium renderer
子进程(可选) childProcessManager;跟随父进程生命周期 少见
模块独立 / 动态 / 静态指定进程(可选) 多 HAP、isolationMode / isolationProcess / process 大应用拆进程

ArkWeb 还有 GPU / 孵化等系统进程;业务主要关心 主进程 ↔ 渲染进程(走 Web 桥,不是自己 IPC)。

进程名字符串没有稳定业务语义,不要写死「某进程名 === 输入法」。

4.2 和 Ability 的关系

  • 一进程可有多个 AbilityStage / Ability;进程内 Ability 都退出后进程才销毁。
  • 多数页面通信 = 主进程内部(第一、二节)。
  • 卡片 Extension、独立进程 HAP、别的 App、Web 渲染 → 才跨进程。

本仓库:EntryAbility + 页面默认都在主进程主线程。

flowchart TD App[同一应用 Bundle] App --> Main[主进程] App --> Ext[Extension 进程] App --> Ren[Render 进程] Main --> UA[UIAbility] Main --> SE[Service与DataShare] Ext --> FE[FormExtension 等] Main -->|CommonEvent Want IPC| Ext Main -->|Web 组件桥接| Ren ```--- ## 五、线程是什么(ArkTS 并发) 进程起来后先有 **主线程**;Ability / UI 默认跑在主线程。 **线程是调度执行的单位。** OS 上线程可共享进程内存,但 **ArkTS 是内存隔离模型**:不同 ArkTS 线程不能像 Java 那样直接共享业务对象,要靠序列化消息(≈ Web Worker)。 ### 5.1 常见 ArkTS 线程 | 线程 | 职责 | Web 对照 | |------|------|----------| | **主线程** | UI、生命周期、改 `@State`、收子线程结果 | 浏览器主线程 | | **TaskPool 工作线程** | 系统管池;独立耗时任务 | 任务队列 | | **Worker 线程** | 你创建 / 销毁;常驻或强交互后台 | `new Worker()` | 系统还有 **I/O 线程池**------网络、文件异步不一定要你自己开线程。CPU 重活、会堵主线程的同步逻辑,才上 TaskPool / Worker。 ### 5.2 TaskPool vs Worker | | TaskPool | Worker | |--|----------|--------| | 生命周期 | 系统扩缩容 | 你自己管 | | 适合 | 短~中、独立、可取消 | 常驻、长期句柄、交互多 | | 通信主路径 | 入参 + `await` 返回值;也可 Emitter | `postMessage` / `onmessage`;也可 Emitter | | 限制 | 任务独立;不能碰 UI | 空闲也占内存;数量有上限 | **默认耗时用 TaskPool;必须常驻占线程再用 Worker。** --- ## 六、线程 ↔ 线程:能不能通、怎么通 范围:同一进程内主线程 / TaskPool / Worker。 ### 6.1 矩阵 | 从 \ 到 | 主线程 | TaskPool | Worker A | Worker B | |---------|:------:|:--------:|:--------:|:--------:| | **主线程** | ---(用第一节状态 / 第二节 EventHub) | ✅ 任务入参;过程中一般不再正向推送 | ✅ `postMessage` | ✅ `postMessage` | | **TaskPool** | ✅ 返回值 / `sendData`;或 Emitter | ⚠️ 任务间无直连;Emitter 或回宿主 | ✅ Emitter | ✅ Emitter | | **Worker A** | ✅ `postMessage`;可同步调宿主注册方法 | ✅ Emitter | --- | ✅ Emitter | | **Worker B** | 同上 | ✅ Emitter | ✅ Emitter | --- | | 方向 | 怎么通信 | 备注 | |------|----------|------| | 宿主 → TaskPool | 创建任务时传参;**执行中不支持再正向塞数据** | 一次投递为主 | | TaskPool → 宿主 | 返回值;或 `sendData` 异步回调 | 优先返回值 | | 宿主 ↔ Worker | `postMessage` / `onmessage` | 协议自定 | | Worker → 宿主 | 异步 postMessage;也可同步调宿主方法 | 同步路径别做重活 | | **任意 ↔ 任意** | **Emitter** | 跨 Worker / TaskPool 通用 | ### 6.2 主线程 ↔ TaskPool ```ts import { taskpool } from '@kit.ArkTS'; @Concurrent function heavyParse(raw: string): object { return JSON.parse(raw); } async function run(): Promise<void> { const task = new taskpool.Task(heavyParse, bigJsonString); const result = await taskpool.execute(task); // 仅在主线程改 @State }
  • 函数要 @Concurrent,逻辑自包含。
  • 传参 / 返回有序列化开销。
  • 子线程禁止改 @State、动 UI、用 AppStorage / 非线程安全模块。
  • 执行中要喊别的线程:Concurrent 里 emitter.emit。

6.3 主线程 ↔ Worker

ts 复制代码
this.worker.postMessage({ type: 'reverse', payload: 'abc' });
this.worker.onmessage = (e) => { /* e.data */ };

// worker.ets
workerPort.onmessage = (e) => {
  workerPort.postMessage(String(e.data.payload).split('').reverse().join(''));
};

和 Web Worker 同构;只有主线程更新 UI。


七、进程 ↔ 进程:能不能通、怎么通

7.1 矩阵

从 \ 到 主进程 本应用 Extension Render(Web) 子进程 其它应用
主进程 ---(见第一~六节) ✅ CommonEvent / Want / IPC ✅ Web 桥 ✅ 视创建 API ✅ CommonEvent / Want / IPC
Extension ✅ 同上 ✅ CES / IPC ⚠️ 多经主进程 ⚠️ 少见 ✅ CommonEvent 等
Render ✅ Web 桥 / 回调 ⚠️ 不直连业务 Extension --- ❌ ❌
其它应用 ✅ ✅ ❌ ❌ ✅

跨进程无效: Emitter、EventHub、@State / AppStorage / LocalStorage。

7.2 CommonEvent(跨进程首选通知)

ts 复制代码
import { commonEventManager } from '@kit.BasicServicesKit';

let subscriber: commonEventManager.CommonEventSubscriber;
commonEventManager.createSubscriber(
  { events: ['com.example.MY_EVENT'] },
  (err, sub) => {
    if (err) return;
    subscriber = sub;
    commonEventManager.subscribe(subscriber, (err2, data) => {
      // data.event / data.data
    });
  }
);

commonEventManager.publish('com.example.MY_EVENT', {
  data: JSON.stringify({ from: 'main' }),
}, (err) => {});

一对多、松耦合。可用权限 / bundleName 限制收发方。事件名要全局唯一;敏感信息别塞广播。

典型:主进程 ↔ 卡片 FormExtension。

7.3 Want / IPC / Web 桥

手段 用途
Want 拉起 Ability 时一次性带参,不是长连接
IPC / RPC 跨进程(或跨设备)方法调用;多经系统 Kit 封装
Web 桥 主进程 ↔ Render;JSON 过桥,见 插播 WebView

网页里 emitter.on 收不到主线程业务事件。


八、按业务再问一遍

text 复制代码
1. 驱动 UI 的状态? → @State 系 / Storage(范围尽量小)
2. 只是通知且树对不上? → EventHub(同 Ability)或 Emitter(可跨线程)
3. 会卡主线程? → TaskPool(默认)或 Worker(常驻)
4. 跨进程 / 跨应用? → CommonEvent 或 IPC

九、小结

  1. 先组件状态、再事件通知------主进程主线程上和 Vue 同构。
  2. EventHub 管 Ability 内广播;Emitter 管同进程(含跨线程);二者都 不能跨进程。
  3. 线程 :宿主↔TaskPool 靠任务参数/返回值;宿主↔Worker 靠 postMessage;任意线程靠 Emitter。
  4. 进程:跨进程用 CommonEvent / Want / IPC;AppStorage 不是跨进程存储。
  5. Web 渲染进程走专用桥,不是业务 Emitter。

先做对组件和事件,再按「同不同线程 / 同不同进程」升级通信手段。

相关推荐
叫我铁汁儿吧2 小时前
JS 中的this
前端
狂潮lister2 小时前
狂潮·元理论 | 第5篇:想为你的AI培养个性吗!哈哈哈……(6个测试,教你幻觉的正确玩法)
前端·人工智能
高晶2 小时前
2A3 单端甲类功放从零制作全攻略:电路设计、焊接调试到煲机维护一站式指南
前端·设计模式
deli0072 小时前
塔防游戏里怪物为什么绕远路?Canvas 实测 A* 寻路 20 波
前端
小万7422 小时前
WHO 心理自助指南深度解析:Step-by-Step 与 Doing What Matters in Times of Stress,用循证协议重构心理韧性
前端
代码猎人2 小时前
前端怎么优化用户体验
前端
deli0072 小时前
批量加水印还在用在线工具?30 张图 Canvas 本地铺满实测
前端
鸿蒙开发2 小时前
扫描 PDF 看得见却搜不到?用 Tesseract.js + PDF.js 生成可搜索 PDF
前端·react.js
小万7422 小时前
ECC(Everything Claude Code)深度解析:把编程 Agent 从“聊天写代码”拉进工程现场的 Harness 系统
前端