Skip to content

踩坑 · 原生与真机 ​

分类:踩坑记录 → 原生与真机 覆盖:权限、平台通道、真机差异、iOS/Android 平台特有


MissingPluginException ​

症状

MissingPluginException: No implementation found for method getXxx on channel com.xxx/native

原因(按概率排序)

  1. 热重载后原生代码未生效 → 必须冷启动(Full Restart) ← 最常见
  2. 该平台根本没实现这个方法(只写了 Android 没写 iOS)
  3. channel 名两端不一致(拼写、大小写)
  4. 插件未注册(自建 FlutterEngine 时忘了 GeneratedPluginRegistrant)

解法

bash
# 1. 先做这一步:停止应用重新运行(不是热重载 R,是 Shift+F5 或 flutter run)

若仍报:

  • 核对两端的 channel 名字符串完全一致
  • 确认该平台实现了这个方法(Android 的 Kotlin + iOS 的 Swift 都要写)
  • 自建引擎时确认注册插件:
kotlin
GeneratedPluginRegistrant.registerWith(flutterEngine)

预防 改了原生代码就冷启动,别指望热重载。平台通道完整说明见 21 篇。


权限申请了但依然失败 ​

症状 已调用权限申请 API,功能仍不可用,或用户拒绝后无法再弹窗。

原因

  1. AndroidManifest / Info.plist 没声明权限(最常见)
  2. 用户选了"不再询问",后续申请直接返回拒绝
  3. iOS 缺少 NS*UsageDescription 描述文案

解法

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
xml
<!-- ios/Runner/Info.plist —— 描述文案必填,否则直接崩溃或被拒 -->
<key>NSCameraUsageDescription</key>
<string>用于扫描设备二维码</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>用于获取当前电站位置</string>

用户选了"不再询问"时,引导去设置页:

dart
await openAppSettings();   // app_settings / permission_handler

预防

  • 加权限时同步改三处:Dart 申请代码 + AndroidManifest + Info.plist
  • 权限被永久拒绝要有引导去设置的 UI
  • 详见 15 篇权限与设备能力

Platform Channel 回调永远不返回 ​

症状 调用原生方法后 await 永远不完成,无报错无日志。

原因原生侧回调没在主线程执行,或某个分支忘了调用 result.success/error。

解法

kotlin
// Android:耗时任务切后台,回调必须切回主线程
if (call.method == "queryData") {
    thread {
        val data = query()               // 后台
        runOnUiThread {
            result.success(data)          // ← 必须主线程
        }
    }
}
swift
// iOS:切 main queue
DispatchQueue.main.async { result(data) }

检查每个分支(包括异常分支)都调用了 result.xxx,否则 Dart 侧会一直等。

预防 写 Channel 时第一时间处理线程;用 try/finally 保证 result 一定被调用。


只在真机上复现(模拟器正常) ​

症状 模拟器一切正常,真机出现崩溃/功能异常/性能差。

原因(按常见度)

  1. 性能差异:模拟器用电脑 CPU/GPU,真机(尤其低端安卓)性能差得多
  2. 权限差异:真机的权限弹窗行为、系统设置不同
  3. 架构差异:模拟器常是 x86,真机是 arm
  4. 系统版本差异:厂商 ROM 的行为差异(尤其国内安卓)
  5. 推送/传感器:模拟器根本不支持推送

排查

bash
# 真机跑 profile 模式看性能(不要只看 debug)
fvm flutter run --profile -d <device-id>

# 看真机日志
adb logcat | grep flutter      # Android
# iOS 用 Xcode -> Devices and Simulators -> Open Console

预防

  • 开发中期就定期在真机上验证,不要最后才测
  • 至少备一台低端安卓机做性能基线
  • 推送功能只能在真机测

iOS 能跑,Android 崩溃(或反之) ​

症状 单平台正常,另一平台启动即崩或特定功能崩溃。

原因(按概率)

  1. 该平台缺权限声明或配置
  2. 用了平台不支持的 API(如 Web 上 dart:io)
  3. minSdkVersion / deployment target 不满足插件要求
  4. 原生侧只实现了一个平台

解法

bash
# 看原生层的崩溃堆栈(Flutter 层看不到)
adb logcat            # Android
# iOS:Xcode -> Organizer -> Crashes,或控制台

检查插件的平台要求:

yaml
# 插件文档通常标注
# Android: minSdkVersion 21+
# iOS: 12.0+

预防

  • 引插件时同时看 Android 与 iOS 的最低版本要求
  • 每个平台都要跑一遍主流程

混合开发:Flutter 页面首次打开慢 ​

症状 Add-to-App 场景,第一次打开 Flutter 页面卡顿 300ms~1s。

原因 首次打开才创建 FlutterEngine(加载 Dart 代码 + 初始化引擎开销大)。

解法 应用启动时预热引擎(见 26 篇):

kotlin
// Application.onCreate 中
flutterEngine = FlutterEngine(this).apply {
    dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
FlutterEngineCache.getInstance().put("main_engine", flutterEngine)

页面用 withCachedEngine("main_engine") 而不是 withNewEngine()。

预防 引擎预热是 Add-to-App 的必做项,不是优化项。


混合开发:返回键直接退出整个 Flutter 容器 ​

症状 Flutter 内部有多层页面,按返回键却直接关闭整个 Flutter 页面回到原生。

原因 返回事件被原生容器消费,Flutter 内部栈没机会处理。

解法

dart
PopScope(
  canPop: await router.canPop(),
  onPopInvokedWithResult: (didPop, _) {
    if (!didPop) {
      // Flutter 内部栈已空 → 通知原生关闭容器
      const MethodChannel('com.haier.energy/native')
          .invokeMethod('finishFlutterPage');
    }
  },
  child: child,
)

预防 混合栈的返回策略要在集成初期就定清楚,见 26 篇路由打通。


待记录 ​

  • [ ] 国内安卓厂商 ROM 的推送/权限特殊行为
  • [ ] iOS 上架被拒的具体原因与整改
  • [ ] 真机热更新/代码推送的限制
  • [ ] 签名配置错误导致无法覆盖安装