☰
OpenHarmony Flutter直拨插件适配:从MissingPluginException到一键外呼
2026/9/30 11:54:06 网站建设 项目流程

标题里的 OpenHaomony,我按 OpenHarmony 生态来理解。前阵子接了一个 OpenHarmony 设备上的 Flutter 工单,业务很明确:用户在主界面点一个按钮,电话必须直接拨出去,不能像 url_launcher 那样先跳到系统拨号盘、再等用户按一次确认。于是我把 flutter_phone_direct_caller 加进了 pubspec,编译通过,点击按钮,日志里甩给我一行MissingPluginException(No implementation found for method makePhoneCall on channel flutter_phone_direct_caller)。

这个库在 Android 生态里明明很好用,怎么到 OpenHarmony 上就成了空壳?后来我把它的源码拆开看了一遍,才意识到问题不在库本身,而在 Flutter 插件机制的平台实现分布。这篇文章就从这个问题聊起:为什么会这样、这个库的原理是什么、怎么给它在 ohos 平台补上原生实现、权限怎么申请、真机验证时有哪些坑。内容不涉及什么高深理论,但每一步都是我实际跑通过的,适合正在做 OpenHarmony Flutter 三方库适配、或者想把一个 Android 插件搬到 OpenHarmony 上用的开发者参考。

1. 为什么"直接拨号"在 OpenHarmony 上绕不开 flutter_phone_direct_caller 这条链路

1.1 直拨、拨号盘和拉起拨号应用是三种完全不同的能力

先说清楚"直接拨号"到底指什么。很多 App 里的"拨打电话"其实只是调起系统拨号盘,界面是系统的、号码可以改,用户自己决定按不按拨号键。这种需求用 url_launcher 的tel:scheme 就能满足,权限要求也不高。但我的场景是业务系统里的一键外呼:客服工单、应急呼叫、内部调度,用户点了按钮,通话必须立刻建立,再让用户多点一次就违背了产品逻辑。

flutter_phone_direct_caller 走的是后一条路:直接发起呼叫,不经用户确认。它的官方 Android 实现本质上是调用ACTION_CALL这个系统 Intent,配合CALL_PHONE权限来绕过确认环节。这里有个容易混淆的点:大多数 Android 开发者熟悉的ACTION_DIAL其实是打开拨号盘,而ACTION_CALL才是真的把电话拨出去。到了 OpenHarmony 上,这个"绕过用户确认"的能力同样涉及系统敏感权限,而且不同的厂商发行版对权限的管控尺度还不一样,这也是后面所有适配工作的根源。

1.2 官方库生态的现状:Android/iOS 有原生实现,OpenHarmony 是"野孩子"

到 pub.dev 上搜 direct call 相关库,常见的就是 flutter_phone_direct_caller 和 flutter_callkit_incoming。后者做的是通话管理面板,功能重、依赖多,和我的一键外呼需求对不上;前者代码量少、逻辑直白,几乎是最理想的移植参照对象。

但拆开这个包你会发现,它的工程结构里只有 android 和 ios 两个平台目录,没有 ohos 目录。这意味着在 OpenHarmony Flutter 工程里直接把它加进依赖,运行时必然找不到实现。这不是库作者偷懒,而是 Flutter 插件机制本身的设计:Dart 侧只管把消息发到一条通道上,至于谁去接这个消息,由各平台的原生代码决定。通道另一端没有任何平台注册,Dart 侧就只能得到 MissingPluginException。

我见过不少人把 OpenHarmony 当 Android 用,直接在工程里改点权限、抄一段 Intent 代码就想跑通,结果权限加了、代码看着也对,Flutter 侧依然报找不到方法。根子往往就在 Flutter 的插件注册机制没打通,而不是什么玄学问题。具体怎么排查,我在后面的实测复盘里会完整展开。

1.3 最终选型:为什么我没有从零自研一条通道

当时对比过三条路:用 url_launcher、自研 MethodChannel、fork flutter_phone_direct_caller。前两个各有各的问题。

url_launcher 的tel:方案我测过一版,它在 OpenHarmony 上能调起拨号盘,但无法做到直接外呼,产品验收直接不通过。自研 MethodChannel 也确实可行,但需要自己封装 Dart API、自己处理空安全、自己写权限引导流程,把这些工作量加起来比改现成插件还大。所以我最终选择 fork 这个库,理由有三个:

第一,它的 Dart API 非常收敛,整个对外暴露的核心方法就一个 callNumber,没有一堆状态回调,改造面很小。第二,它内部只使用一条 MethodChannel、一个方法名,参数是一个 map,这种极简的协议最容易在不同平台上保持一致。第三,这个库在 Android 生产环境已经有大量验证案例,逻辑本身是可靠的,我要做的只是把 Android 的Intent.ACTION_CALL换成 OpenHarmony 的startAbility调用。

方案直接拨号OpenHarmony 适配成本维护难度
url_launcher tel:不支持,只能调起拨号盘低,但满足不了需求低
自研 MethodChannel支持中高,需自己实现全部协议中
fork flutter_phone_direct_caller支持中,原生侧补一个 Plugin 即可低

2. flutter_phone_direct_caller 调用链拆解:一条通道、一个方法、一个参数

2.1 扒开 Dart 侧源码看它真正做了什么

这个库的 Dart 侧代码极短,核心逻辑大概是这样(我用的是当前 pub.dev 上的主版本,结构大同小异):

import 'package:flutter/services.dart'; const MethodChannel _channel = MethodChannel('flutter_phone_direct_caller'); static Future<bool?> callNumber(String number, {bool forceDial = false}) async { final bool? res = await _channel.invokeMethod('makePhoneCall', <String, dynamic>{ 'number': number, }); return res; }

这一小段代码解释了整件事的关键:channel 名字是flutter_phone_direct_caller,方法名是makePhoneCall,参数是一个 map,里面放了一个number字段。Dart 侧不管底下是 Android、iOS 还是 OpenHarmony,它只做一件事——把这条消息扔到通道上。

正因为如此,只要原生侧注册的 MethodChannel 名字和 Dart 侧完全一致、方法名一致、参数结构一致,原生侧到底是不是这个库自己的代码根本不重要。这个认知是后面所有适配操作的前提。

2.2 Android 侧实现与 OpenHarmony 侧的差异本质

Android 侧官方实现大致是在 MainActivity 里注册同名 MethodChannel,收到makePhoneCall后用Intent(Intent.ACTION_CALL)发起呼叫,号码塞进Uri.parse("tel:" + number),前提是已经在 AndroidManifest 里声明CALL_PHONE权限并动态申请通过。

OpenHarmony 上的对应关系是:权限名变成ohos.permission.PLACE_CALL,能力入口是 Ability 框架,通过startAbility携带一个 Want 对象来发起呼叫。Android 的 Intent 与 OpenHarmony 的 Want 在概念上很像,都是"我要做某件事"的描述,但字段名、权限模型、动态申请接口完全不同。如果你只把 AndroidManifest 里的权限名搬过来,那只是第一步,后面的运行时申请和呼叫动作都得重写。

2.3 MethodChannel 在 OpenHarmony Flutter 引擎里的映射

还有一个需要理解透的点:MethodChannel 在 OpenHarmony 的 Flutter 引擎里能不能用?答案是能。OpenHarmony 的 Flutter 分支用的是自己编译维护的 Flutter Engine,但上层对 Dart 暴露的 Binding 接口和官方保持一致,所以你在 Dart 侧import 'package:flutter/services.dart'写的 MethodChannel 天然可以跑到 ohos 平台上。

真正的工作量在原生侧。OpenHarmony Flutter 插件开发一般通过@ohos/flutter_plugin这个包提供 Plugin、FlutterPluginBinding、MethodChannel 等类型。插件类在onAttached回调里拿到 FlutterPluginBinding,就能拿到 binaryMessenger,从而创建 MethodChannel 并注册 handler。这一步和 Android 里registerWith(Registrar)的角色完全一样。

我把这个机制理解成一个约定:Dart 侧喊一嗓子,原生侧得有人在同一个频道上接话。移植 flutter_phone_direct_caller 到 OpenHarmony,本质就是写一个符合这个机制的插件类,让它和 Dart 侧的 channel 成功握手。

3. 环境准备:OpenHarmony Flutter SDK 与宿主工程的实际搭配

3.1 工具链四件套

适配开始前,工具链必须准备对。这里我先说结论,再讲坑。

  • Flutter SDK:建议直接用 OpenHarmony 社区维护的 flutter_flutter 仓库分支,不要用 Google 官方主分支编出来的二进制去跑 ohos 工程,产物路径和平台目录识别都会出问题。
  • Dart SDK:随该 Flutter 分支一起下载,一般捆绑版本,不用单独配。
  • DevEco Studio:用来编译 ohos 原生模块、打包 HAP,需要和你的 OpenHarmony SDK 版本匹配。
  • 命令行 Flutter:用于flutter pub get、flutter build hap等操作。

我当时用的版本组合是 OpenHarmony flutter_flutter 的稳定分支加 DevEco Studio 对应的 SDK。这里有一个很实际的坑:Flutter 官方最新版对三方插件平台目录的扫描逻辑和 OpenHarmony 分支并不完全同步,所以尽量用 OpenHarmony 社区发布时捆绑的那一版 Flutter 工具链,而不是自己从官方主分支切。版本差半代,排错方向可能就全变了。

3.2 Flutter 工程与 ohos 宿主目录的关系

一个支持 ohos 的 Flutter 工程,典型结构是工程根目录下存在一个 ohos 子目录,里面是完整的 DevEco 工程,entry 模块作为 Flutter 原生宿主的壳。Flutter 侧源码在 lib 目录,ohos 侧源码在 ohos/entry/src/main/ets 下。两者通过 Flutter Engine 连接,运行时由原生壳加载引擎,再渲染 Flutter 页面。

如果你是从 Android 迁移过来的老工程,没有 ohos 目录,可以参照 OpenHarmony Flutter 官方模板手动补,也可以先建一个新的空 ohos 工程再把 lib 代码拷过去。我实际用下来,后一种方式更省心,因为官方模板里已经处理好了 EntryAbility、Flutter 引擎初始化、插件注册入口等一堆细节,手动补容易漏。

3.3 最容易踩的坑:Flutter 工具链没有把插件登记到 ohos 平台

这一步是很多适配失败的隐藏根因。许多 Flutter 插件自带 android/ios 实现,当你把它加进 pubspec.yaml 后,Flutter 工具链会生成.flutter-plugins文件和插件注册清单,告诉原生侧有哪些插件要被加载。但 OpenHarmony 分支早期的 flutter_tool 对 plugins 的 ohos 平台扫描支持不完整,导致你把依赖写好了,生成的注册清单里却只有 android 和 ios 的路径。

遇到 MissingPluginException,第一件事不是改源码,而是去翻工程里的插件注册清单(不同版本名字不同,有的叫 GeneratedPluginRegistrant),看 flutter_phone_direct_caller 有没有被登记进去。没有的话,就说明工具链没有把它当成 ohos 平台插件处理,你需要手动在 EntryAbility 里注册插件类,或者像我做的那样,修改 pubspec 里 plugins 配置,显式声明 ohos 平台。具体实现放下一节说,这里先建立排查意识。

4. 给 flutter_phone_direct_caller 补上 ohos 原生实现:完整改造过程

4.1 规划目录结构:fork 出来,别给主工程添乱

我不建议直接在主工程源码里塞一堆原生拨号逻辑,那样以后插件升级、多业务复用都会很难受。我更推荐把 flutter_phone_direct_caller fork 到自己的私有仓库,或者做成本地 path 依赖,然后给它新增一个 ohos 平台目录,结构大致如下:

flutter_phone_direct_caller/ ├── lib/ # Dart 侧保持原样 ├── android/ # Android 原生保持原样 ├── ios/ # iOS 原生保持原样 ├── ohos/ # 新增 │ └── src/main/ets/ │ ├── PhoneDirectCallerPlugin.ets │ └── Index.ets └── pubspec.yaml

这样主工程只是通过 pubspec 的 path 依赖引用它,以后插件官方升级了,你单独拉新代码合入即可,业务工程不会被无关的原生逻辑污染。

4.2 实现 PhoneDirectCallerPlugin:插件骨架与拨号动作

在 ohos 侧实现一个插件类,核心是两件事:创建同名 MethodChannel、注册 makePhoneCall 的 handler。代码骨架类似下面这样(具体 API 名会随你使用的 SDK 版本变化,以你当前 flutter_flutter 分支里的插件模板为准):

import { Plugin, FlutterPluginBinding, MethodChannel, MethodCall, MethodResult } from '@ohos/flutter_plugin'; import { common, Want } from '@kit.AbilityKit'; export default class PhoneDirectCallerPlugin implements Plugin { private channel: MethodChannel | null = null; onAttached(binding: FlutterPluginBinding): void { this.channel = new MethodChannel(binding.getBinaryMessenger(), 'flutter_phone_direct_caller'); this.channel.setMethodCallHandler(this.handleMethodCall.bind(this)); } private async handleMethodCall(call: MethodCall, result: MethodResult): Promise<void> { if (call.method === 'makePhoneCall') { try { const args = call.arguments as Record<string, Object>; const number = (args['number'] as string) ?? ''; if (number.trim().length < 3) { result.error('INVALID_NUMBER', 'phone number is empty or too short', null); return; } const ctx: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; await this.placeCall(ctx, number.trim()); result.success(true); } catch (e) { result.error('CALL_FAILED', (e as Error).message, null); } } else { result.notImplemented(); } } private async placeCall(context: common.UIAbilityContext, number: string): Promise<void> { // 方案一:使用 uri 方式发起呼叫,这是最接近 Android tel: 的做法 const want: Want = { uri: `tel:${number}` }; await context.startAbility(want); } }

这段代码解决了什么?第一,它把 Dart 侧发来的参数取出来,做了基本判空;第二,它用startAbility发起呼叫,参数是一个tel:协议 URI;第三,它把执行结果通过 result 返回给 Dart 侧,让业务层能感知成功或失败。

关于placeCall里的细节,我会提醒一句:不同 OpenHarmony 发行版对tel:URI 的支持程度不完全一样。标准 OpenHarmony ROM 上这个方案通常能通,但某些厂商改动过的发行版会拦截非法协议。遇到这种情况,备选方案是换成显式指定bundleName和abilityName来调起系统通话应用,而不是只靠 URI。我在后面实测复盘里会再讲这个差异。

4.3 把插件注册到宿主工程

插件类写好了,还必须让 Flutter 运行时知道怎么找到它。在 OpenHarmony 的宿主能力类(通常继承自 FlutterAbility)里,一般会在 onCreate 中把插件注册进去。不同版本的 API 名称会有差异,早期版本可能叫 addPlugin,后来改成 pluginRegistry.register 一类。我用的版本是类似这样:

import { FlutterAbility } from '@ohos/flutter_ability'; import PhoneDirectCallerPlugin from 'flutter_phone_direct_caller'; export default class EntryAbility extends FlutterAbility { onCreate(want, launchParam) { super.onCreate(want, launchParam); if (this.pluginRegistry) { this.pluginRegistry.register(new PhoneDirectCallerPlugin()); } } }

如果你的 SDK 版本里宿主能力没有合适的注册入口,也可以在插件模块自己的 Index.ets 里导出插件类,再让 ohos 工程的模块依赖引入这个 HAR 包,由 DevEco 的模块初始化逻辑加载。两条路最终效果相同,选你手头模板里能看到的那条就好。

4.4 验证链路:怎么确认 channel 已经握手成功

改完不要急着点拨号按钮,建议按这个顺序逐级验证:

  1. 看日志:在onAttached里打一行日志,没有输出说明插件根本没被装载。
  2. 在原生 handler 里打日志:如果收到 makePhoneCall,说明 Dart 到原生的通道已经通了一半。
  3. 在 Dart 侧调用后打印返回值:返回 true 表示方法正常执行完毕,返回 false 或捕获到异常,再去查原生侧哪一步抛了错。
  4. 最后在真机上验证电话是否真的拨出。模拟器上没有 telephony 服务,通常拨不出去,不能拿它当判断标准。

这一步的验证习惯帮我省了很多调试时间。记住:通道通了和电话拨出去了是两件事,二者要用不同方法分别验证。

5. 权限处理:PLACE_CALL 动态申请的正确姿势与降级兜底

5.1 OpenHarmony 权限模型和 Android 的差异

OpenHarmony 的权限大体分成 system_grant 和 user_grant 两类。和拨打电话相关的权限通常属于 user_grant,意思是除了在模块配置里声明,还必须运行时对用户弹窗动态请求;只声明不请求,调用时照样会被系统拦截。

这个模型和 Android 的运行时权限很接近,但团队迁移时最容易犯的错就是把 AndroidManifest 的权限声明直接照搬,漏掉动态申请那一步。在 OpenHarmony 上,动态申请不是可选项,是 user_grant 权限刚需的一环。

5.2 module.json5 声明与 abilityAccessCtrl 动态申请

第一步,在 ohos 工程的 module.json5 里声明权限。reason 字段要写清楚申请用途,部分发行版审核应用时会校验这段文案:

{ "module": { "requestPermissions": [ { "name": "ohos.permission.PLACE_CALL", "reason": "需要直接拨打业务电话", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] } }

第二步,在原生侧调用动态权限申请。OpenHarmony 里一般通过 abilityAccessCtrl 来做:

import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'; async function requestCallPermission(context: common.UIAbilityContext): Promise<boolean> { const atManager = abilityAccessCtrl.createAtManager(); const permissions: Permissions[] = ['ohos.permission.PLACE_CALL']; const result = await atManager.requestPermissionsFromUser(context, permissions); if (result.authResults.length > 0) { return result.authResults[0] === 0; } return false; }

这里有几个关键点。requestPermissionsFromUser是异步的,返回值里的 authResults 数组和传入权限列表一一对应,0 表示授权成功。真机上这个弹窗要等用户操作,所以必须 await,不要在 onAttached 或者页面 build 阶段同步去做,否则可能拿不到结果。

5.3 用户拒绝后的降级方案

直接拨号是强交互,用户第一次往往不理解为什么 App 要电话权限。所以不要只把权限申请弹窗扔出去就结束,我会在权限被拒时做一个二级处理:要么引导用户去设置页手动授权,要么降级成调起拨号盘。拨号盘并不需要 PLACE_CALL 权限,它只是把号码填好,仍由用户确认是否拨出。

降级逻辑放到 Dart 侧更灵活,因为你可以根据原生侧返回的权限结果切换 UI 提示。原生侧把权限结果原样返回给 Dart,Dart 侧再决定继续直接拨号还是弹提示引导。这样即使权限没拿到,App 也还有一个可用入口,不至于功能完全瘫痪。

这里分享一个我自己的习惯:权限申请成功后,把结果缓存到本地并记录时间;下次进入页面且时间在有效期内,就不再弹窗请求。用户对每次都弹权限框的厌恶程度很高,这个处理能明显减少体验上的摩擦。

5.4 后台触发与前台限制的注意点

还有一类特殊情况:如果拨号动作是 App 退到后台后由某个回调触发的,比如网络侧下发指令要求立即外呼,那么动态权限弹窗是不能在后台弹出的。多数系统会直接让请求失败,或者等 App 回到前台再处理。

这种场景下,我建议提前在前台完成授权,后台任务只负责把号码传给原生侧,不再动态申请。权限状态也最好统一由一个单例管理,Dart 和原生都能查询,避免多处各查各的造成状态不一致。

6. 实测复盘:从 MissingPluginException 到打包报错的三道坎

6.1 第一道坎:MissingPluginException 的完整排查链路

这个异常是 Flutter 插件适配里最经典的问题。运行时日志会明确告诉你是哪个方法和哪条通道找不到实现:

MissingPluginException(No implementation found for method makePhoneCall on channel flutter_phone_direct_caller)

注意日志里已经包含通道名。如果连通道名都没出现,那可能 Dart 侧调用的代码路径和你预期的不一样。如果通道名对上了,就按下面的顺序排查:

  1. 确认插件模块是否真正进到工程。在.flutter-plugins和 ohos 模块依赖里能找到 flutter_phone_direct_caller 的路径吗?找不到,说明 pubspec 解析阶段就忽略了它。
  2. 确认原生侧 handler 是否注册。插件类写好了吗?写好了之后,在 EntryAbility 或模块初始化代码里真的加载了吗?很多人只写了类,忘了注册。
  3. 确认注册时机。如果原生侧注册发生在 Flutter 引擎启动之后,而 Dart 侧又在引擎刚启动时立刻调用,可能出现竞态。解决办法是把拨号调用放到页面交互事件里,天然规避。
  4. 确认通道名和参数格式。最常见的低级错误是复制通道名时带了隐藏字符或者大小写不一致。这种问题日志不会提示,只能对照源码一行行看。

我当时遇到的就是第一类:OpenHarmony 分支的 flutter_tool 没把插件扫描进注册清单,导致原生侧根本没有加载插件类。手动注册后问题立刻消失。

6.2 第二道坎:打包阶段 AssertionError 与 Could not close internal 缓存

热搜词里提到的flutter 打包 java.lang.assertionerror: java.lang.exception: could not close i...,我在 OpenHarmony 工程里也踩过。现象是执行flutter build hap时,构建进程在收尾阶段抛 AssertionError,日志里还有一句 "could not close internal" 之类的字样。第一次遇到还以为是机器资源不够,仔细看堆栈才发现是增量构建的缓存状态和当前源码不一致。

我的处理办法是四步:

  1. 执行flutter clean;
  2. 删除 ohos 目录下的.hvigor和 build 缓存目录;
  3. 在 DevEco Studio 里重新 Sync 工程;
  4. 如果还不行,检查 Java 版本是否满足 hvigor 编译器要求,尤其是 Windows 机器上装了多个 JDK 的情况。

这套流程我至少用了三次,全部解决。核心思路就是:Flutter 和 DevEco 两套构建系统共享同一个 ohos 目录,增量缓存的失效规则又彼此独立,任何一方残留都会让另一方检测到不可靠的中间状态,从而在最不该出错的地方抛异常。

6.3 第三道坎:模拟器上的假象

模拟器是另一个大坑。OpenHarmony 模拟器上,权限弹窗会正常出现,你点允许后代码也不报错,但通话就是没有发生。原因很简单:模拟器没有真实的 telephony 服务,startAbility只是把请求发出去了,底层却没有模块接住。

所以验证这个功能前,建议把确认清单拉出来:

  • 必须用真机,且系统内置了可用的拨号应用;
  • 已授权 PLACE_CALL;
  • 拨号按钮触发时 App 处于前台;
  • 号码符合目标地区规则,错号也会导致呼叫失败。

我在开发板上还遇到一个版本差异:同一份代码,在标准 OpenHarmony ROM 上tel:URI 方式可以拨号,在某个厂商发行版上却被系统拦截,只能改成显式指定 bundleName 和 abilityName 的方式。这个差异无法提前从文档里发现,所以我的插件代码里把两种实现都保留了,根据运行时异常动态切换。

6.4 空会话场景的防御

还有一个容易忽略的点:当 Dart 侧传入的参数为空时,原生侧取到的 number 一定是空。如果不判空,后面 startAbility 的 URI 拼出来就是tel:null,轻则错误日志,重则直接崩溃。

我在插件代码里对 number 做了 trim,并且限制了最小长度。长度小于 3 的号码直接返回错误,不会去发起一个没有意义的呼叫。这个简单的防御能省掉大量后续排查时间,尤其是来自 QA 的"偶尔拨不出去"反馈。

7. 适配完之后,我建议你做一次公共化封装

7.1 把拨号能力抽成团队内部模块

这次适配完成后,我把 fork 版的库收进了团队的公共依赖库,并且只对外暴露三个能力:拨号、拨号结果回调、权限状态查询。Dart 侧封装成单例,业务方不需要关心原生侧用的是 URI 还是显式 bundleName,也不需要考虑权限申请细节。

对一个公司内部有多个 OpenHarmony Flutter 应用的团队来说,这是最值得做的一步。一套适配,多个业务复用,后续系统升级或权限策略变了,只需要改一个库,而不是去每个业务工程里翻代码。我甚至把权限状态做成了 ValueNotifier,业务页面可以直接监听授权状态变化,不需要自己轮询。

7.2 给后来者的实在建议

最后说点掏心窝的话。OpenHarmony 上的 Flutter 生态还在快速变化,插件适配这件事,本质上不是你会不会 Flutter 的问题,而是你能不能快速理解 Flutter 插件协议和 OpenHarmony 能力之间映射的问题。

遇到三方库不支持 ohos 平台时,不需要一上来就判死刑。花点时间拆开它的 Dart 侧源码,看看它到底用了几条 channel、几个方法、什么参数结构,然后照着官方插件模板写一个原生适配,很多时候一个下午就能搞定。反而是那些通道名藏在深层依赖里的库,适配起来要费更多精力,建议优先绕开或换成功能等价的替代品。

如果你也正在做类似的第三方库移植,可以先对照本文搭一个最小验证工程,把 channel 打通,再逐步加上权限、降级逻辑和异常捕获。这套路子我在 OpenHarmony 的真机和开发板上都验证过,稳定可复现,相信对你也会有帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询