👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
   我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
  
  🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
  🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
  💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
  
   如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀

先问你一个关键问题(你回一句我就能把权限点/接口细节对齐)🙂

你现在做的是 HarmonyOS NEXT(华为开发者文档体系) 还是 OpenHarmony(开源发行版)?另外你要调用的是 可信设备(已认证) 还是要支持“周边发现但未认证”的设备?

1)RPC 调用流程:别背概念,按“链路”把它走一遍🚶‍♂️

官方的 IPC/RPC 开发指导把流程讲得很直白:RPC 本质是客户端 Proxy ↔ 服务端 Stub 一一对应,客户端先连接服务拿到 Proxy,再通过 Proxy 发起请求,服务端 Stub 处理并 reply。

1.1 跨设备 RPC 的关键差异:Want 里要带目标设备 NetworkId

在跨设备场景下,连接服务时除了包名/组件名,还需要目标设备的 NetworkId(一般来自 distributedDeviceManager 的设备列表)。

1.2 一条“标准链路”(建议你在团队里画成时序图贴墙上🤣)

  1. 设备发现/认证 → 拿到目标设备 NetworkId(可信设备优先)
  2. 客户端 connectServiceExtensionAbility(want)(want 里带 networkId)拿到 remote
  3. remote.sendMessageRequest(code, data, reply, option) 发请求
  4. 服务端 Stub onRemoteMessageRequest 解包 → 处理 → 写 reply
  5. 客户端读 reply → 断开连接/复用连接(按你的策略)

2)接口定义规范:跨设备最怕“协议漂移”,你得把协议当合同📜

跨设备 RPC 的接口设计,我强烈建议你按“协议化”来做,而不是“随手写几个 code”。

2.1 你至少要定清楚这 6 件事

① Descriptor(接口描述符)

  • Stub 构造时的 descriptor 统一用一个常量,避免多端写岔。

② Message Code(请求码枚举)

  • code 一旦发版就别乱改;要扩展就新增 code,旧 code 保留兼容。

③ Parcel 数据结构(字段顺序就是协议)

  • writeInt/writeString 的顺序要和 readInt/readString 完全一致。
  • 我建议每个请求都带 version + requestId,否则排查会痛苦到想辞职😵‍💫

④ 返回结构(成功/失败都要能表达)

  • 别只返回一个 int;建议统一:statusCode + message + payload

⑤ 幂等性(弱网重试时救命)

  • 如果客户端重试,同一个 requestId 到服务端要能识别并避免重复扣款/重复写入。

⑥ 大小限制与能力边界
OpenHarmony 文档明确提到:单设备跨进程传输最大 200KB,超过建议走匿名共享内存等机制;并且 RPC 有一些能力限制(例如远端 Proxy 不能在本设备内二次跨进程传递等)。

你可以把它理解成:RPC 适合“命令/参数/小结果”,不适合“搬家”。要搬大件,换车(共享内存/文件/对象存储等)。

2.2 一个“协议常量文件”模板(团队强烈建议这么搞)

// rpc_protocol.ets
export const RPC_DESCRIPTOR = 'com.myteam.demo.rpc.v1'

export enum RpcCode {
  PING = 1,
  GET_SUMMARY = 2,
}

export const RPC_VERSION = 1

export enum RpcStatus {
  OK = 0,
  BAD_REQUEST = 400,
  UNAUTHORIZED = 401,
  SERVER_ERROR = 500,
  RETRY_LATER = 503,
}

3)跨设备 RPC 实战代码:从 networkId → connect → sendMessageRequest 跑通全链路🛠️

下面的整体结构与关键点(Proxy/Stub、跨设备 want 要带 networkId、连接回调、sendMessageRequest)与官方指导一致。

3.1 服务端:ServiceExtensionAbility + Stub(RemoteObject)

// ServerServiceExtAbility.ets
import { ServiceExtensionAbility, Want } from '@kit.AbilityKit'
import { rpc } from '@kit.IPCKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { RPC_DESCRIPTOR, RpcCode, RPC_VERSION, RpcStatus } from './rpc_protocol'

const TAG = 'RPC_SERVER'

class ServerStub extends rpc.RemoteObject {
  constructor() {
    super(RPC_DESCRIPTOR)
  }

  onRemoteMessageRequest(code: number, data: rpc.MessageParcel, reply: rpc.MessageParcel): boolean {
    try {
      const version = data.readInt()
      const requestId = data.readString()

      if (version !== RPC_VERSION) {
        reply.writeInt(RpcStatus.BAD_REQUEST)
        reply.writeString(`version mismatch, reqId=${requestId}`)
        return true
      }

      switch (code) {
        case RpcCode.PING: {
          const msg = data.readString()
          reply.writeInt(RpcStatus.OK)
          reply.writeString(`pong(${requestId}): ${msg}`)
          return true
        }

        case RpcCode.GET_SUMMARY: {
          const userId = data.readString()
          // ✅ 这里写你的业务:查询、计算、聚合……
          reply.writeInt(RpcStatus.OK)
          reply.writeString(`summary for ${userId} (reqId=${requestId})`)
          return true
        }

        default:
          reply.writeInt(RpcStatus.BAD_REQUEST)
          reply.writeString(`unknown code=${code}, reqId=${requestId}`)
          return true
      }
    } catch (e) {
      reply.writeInt(RpcStatus.SERVER_ERROR)
      reply.writeString(`server exception: ${JSON.stringify(e)}`)
      return true
    }
  }
}

export default class ServerServiceExtAbility extends ServiceExtensionAbility {
  private stub: ServerStub = new ServerStub()

  onConnect(want: Want): rpc.RemoteObject {
    hilog.info(0x0000, TAG, `onConnect: ${JSON.stringify(want)}`)
    return this.stub
  }
}

3.2 客户端:带 NetworkId 的 Want 连接远端服务,然后 sendMessageRequest

// rpc_client.ets
import { Want, UIAbility } from '@kit.AbilityKit'
import { rpc } from '@kit.IPCKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { RPC_VERSION, RpcCode, RpcStatus } from './rpc_protocol'

const TAG = 'RPC_CLIENT'

function newReqId(): string {
  return `${Date.now()}_${Math.random().toString(16).slice(2)}`
}

export async function callRemotePing(ability: UIAbility, targetNetworkId: string) {
  const want: Want = {
    deviceId: targetNetworkId, // ✅ 跨设备关键:目标设备 NetworkId :contentReference[oaicite:4]{index=4}
    bundleName: 'com.example.rpcserver',
    abilityName: 'ServerServiceExtAbility'
  }

  const connId = await ability.context.connectServiceExtensionAbility(want, {
    onConnect: async (_element, remote) => {
      const data = rpc.MessageParcel.create()
      const reply = rpc.MessageParcel.create()
      const option = new rpc.MessageOption()

      const reqId = newReqId()

      data.writeInt(RPC_VERSION)
      data.writeString(reqId)
      data.writeString('hello from client')

      await remote.sendMessageRequest(RpcCode.PING, data, reply, option)

      const status = reply.readInt()
      const msg = reply.readString()

      if (status === RpcStatus.OK) {
        hilog.info(0x0000, TAG, `OK: ${msg}`)
      } else {
        hilog.error(0x0000, TAG, `ERR status=${status}, msg=${msg}`)
      }

      data.reclaim()
      reply.reclaim()

      ability.context.disconnectServiceExtensionAbility(connId)
    },
    onDisconnect: () => hilog.info(0x0000, TAG, 'disconnected'),
    onFailed: (code) => hilog.error(0x0000, TAG, `connect failed: ${code}`)
  })
}

4)网络异常处理:跨设备不是“会失败”,是“一定会失败”,区别只在于你怎么兜底😅

你要把异常分三类,不同类用不同策略:

4.1 连接阶段失败(connect failed)

常见原因:设备离线/组网断开/服务没启动/权限不够。
策略建议:

  • 快速失败 + 提示用户(“对方设备不在线/请确认同一网络”)
  • 短周期重试(带退避):比如 1s、2s、4s,最多 3 次
  • 不要无限重试:否则你会制造“电量刺客”🔋

4.2 调用阶段失败(sendMessageRequest 抛错/超时)

策略建议:

  • 给每个请求设 超时(别等到天荒地老)
  • 业务幂等:用 requestId 做去重(尤其写入/扣费)
  • 熔断:连续 N 次失败,对该设备暂停一段时间再试(用户体验会好很多)

4.3 弱网下的“数据量陷阱”

RPC 的通信更适合“轻量调用”;一旦你试图传大对象,不仅有大小约束,还会引入序列化/拷贝/重试成本。OpenHarmony 的 IPC/RPC 约束里也明确提了传输大小与替代方案方向。
我的建议非常粗暴但有效:

  • 结果大 → 返回一个 token/uri,让客户端再走文件/对象下载通道
  • 或者直接让两端去同一个“云端/局域网服务”拿数据(别把 RPC 当网盘)

5)安全与权限控制:别把 RPC 当成“内部通道”,它照样是攻击面🧯

5.1 只对“可信设备”开放关键服务

RPC 走软总线做跨设备通信,但“能发现”不等于“可信可调用”。OpenHarmony 文档对 RPC 定位就是跨设备跨进程能力的一部分。
工程上建议:

  • 列表只展示/只允许选择 已认证的可信设备
  • 对“周边发现设备”只给“申请认证/引导配对”,别直接开放业务调用

5.2 权限:客户端要申请,服务端还要二次校验(别偷懒)

鸿蒙提供 abilityAccessCtrl 做权限校验与授权管理,包含 checkAccessToken 等接口。
建议你做到两点:

  • 客户端:在发起跨设备调用前确认权限已授权(必要时拉起授权)
  • 服务端:对敏感接口做二次校验(别只信“调用方说自己有权限”)

哪些算“敏感接口”?只要涉及:设备控制、隐私数据、跨端写入、支付类动作……都算。

5.3 参数与协议安全:别相信对方发来的任何东西

服务端必须做:

  • version 校验(不兼容就拒绝)
  • 参数长度/范围校验(防止 OOM、注入式 payload)
  • 速率限制(同一调用方短时间刷爆你)

结尾:跨设备 RPC 写得好,用户觉得“真协同”;写不好,用户觉得“真玄学”😮‍💨

我最后送你一句挺“刺耳但实用”的话:
跨设备不是加功能,是加风险。
你把协议定好、异常兜好、安全做严,RPC 才会从“偶尔能用”变成“稳定可依赖”。

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 移动端 老兵
📅 日期:2025-11-05
🧵 本文原创,转载请注明出处。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐