适合谁看

  • 正在做多平台 Flutter 工程的人

  • 想优雅处理平台不可用情况的人

  • 不想让页面层到处写 try/catch 的人

问题背景

一旦项目开始同时保留:

  • Android

  • iOS

  • HarmonyOS

就很难保证每个平台都有完全一样的原生实现。
这时候 MissingPluginException 就会成为一个很常见的边界问题。

它最麻烦的地方不在于异常本身,而在于它很容易把平台差异直接泄漏到页面层。
最常见的坏味道通常是:

  • 页面层自己捕获 MissingPluginException

  • 页面层自己判断“当前是不是鸿蒙”

  • 页面层自己决定要不要忽略

这样做短期能跑,长期边界会越来越差。

项目中的真实场景

当前项目已经在部分 channel 里体现了更稳的处理思路。

例如:

  • app/lib/core/platform/anti_peep_protection_channel.dart

  • app/lib/core/platform/intent_navigation_channel.dart

你可以直接看到,这些边界层已经主动处理了:

  • on MissingPluginException

而不是把异常抛给页面层。

这说明项目当前已经在实践一个很重要的原则:

插件不存在,是平台边界问题,不是页面业务问题。

核心实现

先说结论:

MissingPluginException 最适合被收口在 core/platform/ 这一层,而不是直接泄漏给页面层。

一、为什么页面层不该直接理解它

页面层真正应该关心的,通常只是:

  • 这个能力现在能不能用

  • 如果不能用,页面怎么退让

页面层不应该关心的则包括:

  • 当前平台有没有注册这个原生插件

  • 这个异常到底是不是 MissingPluginException

  • 哪个平台实现了、哪个没实现

因为这些信息都更接近:

  • 平台实现差异

  • 原生插件注册状态

它们天生就属于边界层和平台层,而不是业务页面层。

二、为什么 core/platform/ 是最适合兜底的位置

在当前项目结构里,app/lib/core/platform/ 正好就承担着:

  • Flutter 页面和原生插件之间的边界

所以当页面调用:

  • AntiPeepProtectionChannel.activateCollectionProtection()

时,边界层完全有能力自己处理:

  • 插件存在 → 正常调用

  • 插件不存在 → 静默忽略或降级

这层处理掉之后,页面最终拿到的只会是:

  • 能力生效

  • 或者能力当前不可用但页面还能继续工作

这比让页面层自己撞异常要健康得多。

三、当前项目里是怎么做的

anti_peep_protection_channel.dart_invoke(),可以直接看到:

  • try

  • on MissingPluginException

  • 其他异常再记录 warning

这说明这里对 MissingPluginException 的处理思路是:

  • 不是所有平台都必须有这项鸿蒙原生能力

  • 没有就忽略,不要把页面搞炸

再看 intent_navigation_channel.dart 里的 _consumePending() 也是类似逻辑:

  • 先尝试 invokeMethod

  • 如果当前平台没有原生插件,就在 MissingPluginException 里直接忽略

这两处都很能说明一个原则:

  • 插件缺失不一定是错误,有时只是当前平台不支持

四、什么情况下应该静默忽略

这也是实际项目里很重要的判断。

如果某个能力满足下面这些特征,通常更适合静默忽略:

  • 当前平台没有这项实现是合理的

  • 页面没有这个能力也不至于核心流程断掉

  • 用户不一定需要被强提醒

比如:

  • 非鸿蒙平台没有防窥插件

  • 非鸿蒙平台没有待处理导航消费逻辑

这时静默忽略通常比弹错误更好。

五、什么情况下不该只靠静默忽略

也不是所有 MissingPluginException 都应该直接吃掉。

如果某个能力满足下面这些情况,就要更谨慎:

  • 理论上当前平台就应该有实现

  • 缺失意味着工程接入异常

  • 没有它会导致主流程明显不可用

这时更合理的做法往往是:

  • 在边界层记录日志

  • 对页面层暴露“能力不可用”的稳定状态

  • 必要时再由页面决定是否展示用户级提示

也就是说:

  • 边界层负责吞掉原始异常

  • 页面层负责决定产品提示

而不是反过来。

六、为什么这不只是 try/catch 技巧

很多人看到这里会觉得:

  • 不就是在边界层写个 try/catch 吗

但真正重要的并不是语法,而是职责划分。

如果你把 MissingPluginException 处理放在页面层,意味着页面层开始知道:

  • 平台插件注册状态

  • HarmonyOS 原生实现可用性

这会把本来应该留在平台边界层的复杂度直接泄漏给业务层。

所以这件事的本质不是异常处理技巧,而是:

  • 你愿不愿意守住平台边界

七、多平台共存时,降级策略为什么比“报错干净”更重要

对这种同时保留 Android、iOS、HarmonyOS 的工程来说,多平台共存几乎天然意味着:

  • 有些鸿蒙能力是局部存在的

这时真正有价值的不是“平台差异被暴露得很彻底”,而是:

  • 页面在差异存在时还能稳定工作

所以更成熟的思路通常是:

  • 在边界层吸收平台差异

  • 对页面层只暴露能力是否可用、状态是否变化

而不是让页面自己理解底层异常名字。

八、如果把这个问题落到鸿蒙教程里,最该强调什么

如果你是在写鸿蒙 Flutter 教程,这里最该强调的不是:

  • “学会捕获 MissingPluginException

而是:

  • 学会判断它是不是合理的平台缺失

  • 学会把平台差异收口在边界层

  • 学会给页面层提供稳定语义,而不是原始异常

因为真正值钱的不是异常名字,而是这条职责边界。

关键代码位置

  • app/lib/core/platform/anti_peep_protection_channel.dart

  • app/lib/core/platform/intent_navigation_channel.dart

鸿蒙侧实现

从鸿蒙侧看,插件存在时当然会正常工作。
但这篇真正讨论的重点不是鸿蒙插件本身,而是:

  • 当某个平台没有这项鸿蒙插件时,Flutter 这边该怎么优雅退让

所以它更偏边界层设计,而不是原生层能力设计。

Flutter 侧实现

从 Flutter 侧看,core/platform/ 最适合承担三件事:

  • 调原生

  • 收异常

  • 决定如何把平台差异收口成稳定结果或状态

这也是为什么 MissingPluginException 最适合在这里结束,而不是继续往上冒。

常见坑

  • 页面层到处捕获 MissingPluginException

  • 一律弹错,导致非鸿蒙平台体验很差

  • 用异常名字替代能力可用性设计

  • 边界层没有兜底,导致页面一旦跨平台运行就到处 try/catch

  • 该记录日志的时候完全静默,导致真正接入异常被吞掉

可复用模板

static Future<void> _invoke(String method) async {
  try {
    await _channel.invokeMethod<void>(method);
  } on MissingPluginException {
    // 当前平台没有这项原生实现,静默降级
  } catch (e, stackTrace) {
    // 记录其他异常
  }
}
处理顺序
1. 先判断插件缺失是不是当前平台的正常情况
2. 正常情况 -> 边界层静默降级
3. 非正常情况 -> 边界层记录日志并暴露稳定状态
4. 页面层只决定用户提示,不直接理解原始异常

本篇总结

MissingPluginException 不是只靠 try/catch 就能讲完的问题。
它真正考验的是:你有没有把“平台实现差异”留在平台边界层,而不是让页面层自己去理解原生插件是否存在。
当前项目的做法之所以稳,就在于它已经开始把这类差异收口在 core/platform/ 里了。

Logo

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

更多推荐