踩坑 · 原生与真机
分类:踩坑记录 → 原生与真机 覆盖:权限、平台通道、真机差异、iOS/Android 平台特有
MissingPluginException
症状
MissingPluginException: No implementation found for method getXxx on channel com.xxx/native原因(按概率排序)
- 热重载后原生代码未生效 → 必须冷启动(Full Restart) ← 最常见
- 该平台根本没实现这个方法(只写了 Android 没写 iOS)
- channel 名两端不一致(拼写、大小写)
- 插件未注册(自建
FlutterEngine时忘了GeneratedPluginRegistrant)
解法
bash
# 1. 先做这一步:停止应用重新运行(不是热重载 R,是 Shift+F5 或 flutter run)若仍报:
- 核对两端的 channel 名字符串完全一致
- 确认该平台实现了这个方法(Android 的 Kotlin + iOS 的 Swift 都要写)
- 自建引擎时确认注册插件:
kotlin
GeneratedPluginRegistrant.registerWith(flutterEngine)预防 改了原生代码就冷启动,别指望热重载。平台通道完整说明见 21 篇。
权限申请了但依然失败
症状 已调用权限申请 API,功能仍不可用,或用户拒绝后无法再弹窗。
原因
- AndroidManifest / Info.plist 没声明权限(最常见)
- 用户选了"不再询问",后续申请直接返回拒绝
- 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 一定被调用。
只在真机上复现(模拟器正常)
症状 模拟器一切正常,真机出现崩溃/功能异常/性能差。
原因(按常见度)
- 性能差异:模拟器用电脑 CPU/GPU,真机(尤其低端安卓)性能差得多
- 权限差异:真机的权限弹窗行为、系统设置不同
- 架构差异:模拟器常是 x86,真机是 arm
- 系统版本差异:厂商 ROM 的行为差异(尤其国内安卓)
- 推送/传感器:模拟器根本不支持推送
排查
bash
# 真机跑 profile 模式看性能(不要只看 debug)
fvm flutter run --profile -d <device-id>
# 看真机日志
adb logcat | grep flutter # Android
# iOS 用 Xcode -> Devices and Simulators -> Open Console预防
- 开发中期就定期在真机上验证,不要最后才测
- 至少备一台低端安卓机做性能基线
- 推送功能只能在真机测
iOS 能跑,Android 崩溃(或反之)
症状 单平台正常,另一平台启动即崩或特定功能崩溃。
原因(按概率)
- 该平台缺权限声明或配置
- 用了平台不支持的 API(如 Web 上
dart:io) - minSdkVersion / deployment target 不满足插件要求
- 原生侧只实现了一个平台
解法
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 上架被拒的具体原因与整改
- [ ] 真机热更新/代码推送的限制
- [ ] 签名配置错误导致无法覆盖安装