7.1 平台通道与插件开发:Dart 与原生的专线


7.1 平台通道与插件开发:Dart 与原生的专线

本节摘要:Flutter 引擎之外的平台能力——蓝牙、传感器、系统分享、厂商 SDK——要靠平台通道跨过去。本节讲透 MethodChannel 的往返协议与两端的线程约定,把轻记账的"电量感知同步"做成完整双端实现,再升级成结构规范的插件。读完你将不再被"这个能力没有现成插件"卡住。

通道的本质:异步消息加编解码

第 1 章说过平台通道住在宿主壳层,现在打开它。通道不是方法调用,而是异步消息传递:Dart 侧把调用名与参数打包成二进制消息发过引擎,平台侧(宿主壳里的原生代码)解包执行,把结果原路打包发回。三个由此而来的工程事实必须先立住:

一切皆异步。 通道调用返回 Future,哪怕原生实现快如闪电——消息跨越了线程与序列化边界。把通道当同步函数用,是通道类缺陷的第一来源。

编解码有边界。 参数与返回值要能被标准编解码器处理(基础类型、List、Map 及其嵌套),自定义对象要先转 Map;二进制大数据另有专门的通道类型,别把图片字节塞进普通消息。

线程有约定。 平台侧回调默认跑在平台主线程(Android 主线程、iOS 主线程),重活要自己挪;Dart 侧回调跑在界面 Isolate,返回前别做耗时处理。两端各自的线程纪律,下面代码里都有落点。

图 17 一次通道调用的完整往返

图 17 一次通道调用的完整往返

动手:电量感知同步的完整双端

轻记账的同步策略要感知电量——低电量时只同步 Wi-Fi 环境。这能力框架没封装,正好当通道的完整教材。Dart 侧先建类与调用:

import 'package:flutter/services.dart'; class PowerGateway { // 通道名要带命名空间,避免与其他插件撞名 static const _channel = MethodChannel('lightledger.dev/power'); /// 返回当前是否适合重度同步:电量充足且未在省电模式 Future<bool> canHeavySync() async { try { return await _channel.invokeMethod<bool>('canHeavySync') ?? false; } on PlatformException catch (e) { // 原生失败不崩应用:降级为允许同步,日志上报 debugPrint('电量查询失败:${e.code} ${e.message}'); return true; } } }

Android 侧在宿主壳里注册处理器(Kotlin):

class MainActivity : FlutterActivity() { override fun configureFlutterEngine(engine: FlutterEngine) { super.configureFlutterEngine(engine) MethodChannel(engine.dartExecutor.binaryMessenger, "lightledger.dev/power") .setMethodCallHandler { call, result -> when (call.method) { "canHeavySync" -> { val bm = getSystemService(BatteryManager::class.java) val level = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) val charging = bm.isCharging val saving = (getSystemService POWER_SERVICE as PowerManager).isPowerSaveMode // 电量与省电判断都是轻量查询,留在主线程没问题 result.success((level >= 30 || charging) && !saving) } else -> result.notImplemented() } } } }

iOS 侧(Swift)同样的协议换一种方言:

override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: "lightledger.dev/power", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { (call, result) in switch call.method { case "canHeavySync": let level = UIDevice.current.batteryLevel // -1 表示未知 let lowMode = ProcessInfo.processInfo.isLowPowerModeEnabled result((level < 0 ? true : level >= 30) && !lowMode) default: result(FlutterMethodNotImplemented) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) }

三份代码里藏着四条通道纪律。通道名带命名空间且两端一字不差;未知调用要回 notImplemented,让 Dart 侧能区分"没这个能力"与"能力失败";原生失败抛 PlatformException 带错误码,Dart 侧按码降级而不是崩;轻量查询留在主线程,重活显式挪后台(Android 用 Handler 或协程,iOS 用 DispatchQueue),挪完再用主线程回结果。持续推送类需求(传感器流、电量变化广播)改用 EventChannel,把"一次请求一次响应"换成"订阅后再取消",协议骨架不变。

从通道到插件:把能力变成可复用资产

写完发现别的项目也要用电量感知?把它封装成插件。插件的现代形态是联合插件(federated plugin):一个面向应用层的接口包,加若干平台实现包,应用引接口包,各平台实现按声明自动挂载。用脚手架起步:

$ flutter create --template=plugin --platforms=android,ios power_gate

生成物里的分工一眼即明:接口层是 Dart 代码,平台实现各自住在 android 与 ios 源码目录(就是上一小节那段通道代码搬家)。接口设计有一条金律——通道细节不出接口层:应用开发者只见 Future canHeavySync(),不见 MethodChannel。将来内部实现从通道换成 Pigeon 生成的类型安全代码,或换成框架原生 API(系统升级后官方封装出现了),应用层零改动。

发布前的检查单:接口层补 dartdoc 注释(这是插件的门面)、双端各自跑通示例应用、变更日志与许可证齐备。社区插件质量参差的原因多为缺后三样——自产插件别犯同样的懒。Pigeon 值得单独提一句:它让你先写接口定义、生成两端类型安全的代码,把"通道名拼错、参数漏传"这类问题从运行时提前到编译期,接口超过十来个方法的插件直接用它起步。

本节要点回顾

  • 通道是异步消息加编解码,不是方法调用:一切调用返回 Future,对象过界先转 Map;
  • 平台侧回调默认在平台主线程,轻活就地干、重活显式挪,结果回主线程再发;
  • 纪律四条:命名空间通道名、未知调用回 notImplemented、失败抛带码异常、Dart 侧按码降级;
  • 推送流用 EventChannel 订阅取消制,联合插件用接口包加平台实现包组织;
  • 接口层封装通道细节,能力升级应用无感;接口复杂上 Pigeon 提前到编译期。

点状能力打通了。如果需求反过来——整套 Flutter 界面要搬进一个原生老应用——就是下一节 Add-to-App 的主场。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U