Flutter移动端实战:原生通信Platform Channel与自定义插件开发

Flutter的渲染引擎Skia(新版为Impeller)能绘制绝大多数UI,但调用原生平台API时仍需通过Platform Channel桥接。无论是读取设备传感器、调用蓝牙模块还是集成第三方原生SDK,Platform Channel都是Flutter移动端开发中绕不开的底层通信机制。理解其消息序列化原理和编解码流程,是开发高质量Flutter插件的基础。

Platform Channel消息机制原理

Platform Channel使用二进制消息传递,Flutter侧与原生侧各自维护一个MethodChannel引用,通过名称匹配建立通信管道。消息编码采用StandardMessageCodec,支持null、bool、int、double、String、Uint8List、List、Map等基本类型的自动序列化:

import 'package:flutter/services.dart';

class NativeDeviceInfo {
  static const _channel = MethodChannel('com.yunthe/device_info');

  static Future> getDeviceInfo() async {
    final result = await _channel.invokeMethod>('getDeviceInfo');
    return result ?? {};
  }

  static Future requestPermission(String permission) async {
    final granted = await _channel.invokeMethod('requestPermission', {
      'permission': permission,
    });
    return granted ?? false;
  }

  static void listenBatteryChange(void Function(int level) onChanged) {
    EventChannel('com.yunthe/battery_event').receiveBroadcastStream().listen((event) {
      if (event is int) onChanged(event);
    });
  }
}

MethodChannel是异步双向通信:Flutter发起调用后,消息通过Dart VM的FFI桥接到原生层,原生层处理完毕后将结果原路返回。EventChannel用于原生侧持续向Flutter推送数据流。

Android原生侧实现

Android侧通过MethodChannel.MethodCallHandler接收Flutter消息,在Activity中注册:

// MainActivity.kt
class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.yunthe/device_info"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "getDeviceInfo" -> {
                        val info = hashMapOf(
                            "model" to android.os.Build.MODEL,
                            "brand" to android.os.Build.BRAND,
                            "androidVersion" to android.os.Build.VERSION.RELEASE,
                            "sdkInt" to android.os.Build.VERSION.SDK_INT,
                        )
                        result.success(info)
                    }
                    "requestPermission" -> {
                        val perm = call.argument("permission")
                        if (perm != null) {
                            ActivityCompat.requestPermissions(this, arrayOf(perm), 1001)
                            result.success(true)
                        } else {
                            result.error("INVALID_ARG", "permission is null", null)
                        }
                    }
                    else -> result.notImplemented()
                }
            }
    }
}

EventChannel实现需要EventChannel.StreamHandler:

class BatteryEventListener(
    private val context: Context, messenger: BinaryMessenger
) : EventChannel.StreamHandler {
    private var eventSink: EventChannel.EventSink? = null
    private var receiver: BroadcastReceiver? = null

    init {
        EventChannel(messenger, "com.yunthe/battery_event").setStreamHandler(this)
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
        receiver = object : BroadcastReceiver() {
            override fun onReceive(ctx: Context?, intent: Intent?) {
                val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
                val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
                if (scale > 0) {
                    eventSink?.success((level.toFloat() / scale * 100).toInt())
                }
            }
        }
        context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
    }

    override fun onCancel(arguments: Any?) {
        eventSink = null
        receiver?.let { context.unregisterReceiver(it) }
    }
}

iOS原生侧实现

// AppDelegate.swift
@objc class AppDelegate: FlutterAppDelegate {
    override func application(_ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(
            name: "com.yunthe/device_info",
            binaryMessenger: controller.binaryMessenger
        )
        channel.setMethodCallHandler { [weak self] (call, result) in
            switch call.method {
            case "getDeviceInfo":
                let info: [String: Any] = [
                    "model": Device.current.name ?? "Unknown",
                    "brand": "Apple",
                    "iosVersion": UIDevice.current.systemVersion,
                ]
                result(info)
            case "requestPermission":
                guard let args = call.arguments as? [String: Any],
                      let perm = args["permission"] as? String else {
                    result(FlutterError(code: "INVALID_ARG", message: nil, details: nil))
                    return
                }
                DispatchQueue.main.async { self?.requestPermission(perm, result: result) }
            default:
                result(FlutterMethodNotImplemented)
            }
        }
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

自定义编解码与插件项目结构

StandardMessageCodec不支持自定义类型序列化。传递复杂对象时使用JSONMethodCodec:

final channel = MethodChannel('com.yunthe/custom', const JSONMethodCodec());

class SensorData {
  final double x, y, z;
  final int timestamp;
  SensorData(this.x, this.y, this.z, this.timestamp);
  Map toJson() => {'x': x, 'y': y, 'z': z, 'timestamp': timestamp};
  factory SensorData.fromJson(Map j) =>
    SensorData(j['x'] as double, j['y'] as double, j['z'] as double, j['timestamp'] as int);
}

final raw = await channel.invokeMethod>('getSensorData');
if (raw != null) print(SensorData.fromJson(raw));

实际项目中Platform Channel代码应封装为独立插件,使用federated platform interface模式:

# 创建插件项目
flutter create --template=plugin --platforms=android,ios -a kotlin -i swift yunthe_device

# lib/yunthe_device.dart - 抽象平台接口
abstract class YuntheDevicePlatform extends PlatformInterface {
  YuntheDevicePlatform() : super(token: _token);
  static final Object _token = Object();
  static YuntheDevicePlatform _instance = MethodChannelYuntheDevice();
  static YuntheDevicePlatform get instance => _instance;

  Future> getDeviceInfo() {
    throw UnimplementedError('not implemented');
  }
}

# lib/method_channel_yunthe_device.dart - MethodChannel实现
class MethodChannelYuntheDevice extends YuntheDevicePlatform {
  final _channel = const MethodChannel('com.yunthe/device_info');
  @override
  Future> getDeviceInfo() async {
    return await _channel.invokeMethod('getDeviceInfo') ?? {};
  }
}

Federated模式让web和桌面端各自提供实现,Dart侧代码完全一致。

线程安全与性能优化

Platform Channel消息在Android/iOS侧默认运行在主线程,耗时操作必须切到后台线程:

// Android - 子线程执行IO后切回主线程
 MethodChannel(messenger, CHANNEL).setMethodCallHandler { call, result ->
    when (call.method) {
        "readLargeFile" -> {
            Thread {
                try {
                    val data = readLargeFile(call.argument("path")!!)
                    runOnUiThread { result.success(data) }
                } catch (e: Exception) {
                    runOnUiThread { result.error("IO_ERROR", e.message, null) }
                }
            }.start()
        }
    }
}

// iOS - DispatchQueue
channel.setMethodCallHandler { (call, result) in
    DispatchQueue.global(qos: .userInitiated).async {
        do {
            let data = try self.readLargeFile(call.arguments as! String)
            DispatchQueue.main.async { result(data) }
        } catch {
            DispatchQueue.main.async {
                result(FlutterError(code: "IO_ERROR", message: error.localizedDescription, details: nil))
            }
        }
    }
}

高频通信场景(传感器数据流、实时音频),StandardMessageCodec的序列化开销会成为瓶颈。改用BasicMessageChannel + BinaryCodec传递原始二进制:

// Flutter侧
final _binaryChannel = BasicMessageChannel(
  'com.yunthe/audio_stream', BinaryCodec(),
);
_binaryChannel.setMessageHandler((ByteData? message) async {
  if (message != null) _audioPlayer.write(message);
  return null;
});

// Android侧
val binaryChannel = BasicMessageChannel(
    messenger, "com.yunthe/audio_stream", BinaryCodec.INSTANCE
)
fun pushAudioData(data: ByteArray) {
    val buf = ByteBuffer.allocateDirect(data.size)
    buf.put(data); buf.flip()
    binaryChannel.send(buf) { _ -> }
}

BasicMessageChannel + BinaryCodec跳过类型编解码,数据直接以ByteBuffer传递。实测传输1MB音频数据耗时从StandardMessageCodec的15ms降至1ms以下,适合实时音视频流和高频传感器场景。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/flutter-yi-dong-duan-shi-zhan-yuan-sheng-tong-xin/

(0)
小编小编
上一篇 8小时前
下一篇 8小时前

相关推荐