当前位置: 首页 > news >正文

Flutter与iOS和Android原生页面交互

一、Flutter 与原生页面交互的重要性和应用场景

Flutter 是一个由 Google 开发的开源框架,用于创建跨平台的移动、Web 和桌面应用程序。Flutter 允许开发者使用一套代码库为 Android 和 iOS 等平台构建美观、高性能的应用程序。然而,尽管 Flutter 提供了丰富的组件和库,某些情况下,开发者可能仍需要使用平台特有的功能或集成现有的原生页面和服务。

在以下场景中,Flutter 与原生页面交互显得尤为重要:

  1. 利用现有原生代码:在项目中可能已经存在大量的原生代码,重新用 Flutter 实现可能不现实或成本过高。
  2. 调用平台特有的API:一些平台特有的API(如支付、地图等)只能通过原生代码访问。
  3. 集成第三方原生库:一些第三方库只提供原生的接口,需要通过原生代码来集成。
  4. 性能关键任务:对于一些性能敏感的任务,原生代码往往可以提供更好的性能表现。
  5. 遵循平台设计规范:有时为了保持应用的一致性,需要使用原生组件来遵守特定平台的设计规范。

通过与原生页面的交互,Flutter 开发者可以充分利用平台的能力,同时保持应用的流畅性和用户体验的一致性。

二、基本概念

1.平台通道(Platform Channels)介绍

平台通道是Flutter中一个强大的机制,它允许Flutter与宿主平台(如Android和iOS)进行数据和任务的双向通信。这种机制解决了Flutter无法直接调用平台特定API的限制,使得Flutter应用能够实现原生平台的功能。

(1).通道的工作原理

平台通道通过使用一个消息传递的系统工作,该系统包括以下三个关键概念:

  1. 消息:传递的数据单元,可以是简单的字符串、数字或者更复杂的序列化数据结构。
  2. 编解码器:负责消息的序列化与反序列化。Flutter提供了几种标准的编解码器,例如JSONMessageCodecStandardMessageCodecStringCodecBinaryCodec
  3. 通道:连接Flutter和原生平台代码的通信通道。

(2).通道的种类

Flutter提供了三种类型的平台通道,每种通道都适合不同的应用场景:

MethodChannel用于传递方法调用及其响应。方法调用是单次的,通常用于执行原生操作并获取结果。

EventChannel用于创建一个数据流,它允许原生代码发送连续的事件数据给Flutter。这适合于监听原生端的事件或数据变化,如传感器数据或用户位置更新。

BasicMessageChannel用于传递没有固定响应的消息,支持自定义的编解码器。这适合于双向通信,比如状态同步或数据共享。

(3).序列化机制

由于消息需要在Flutter和原生代码之间传递,所以它们需要被序列化(转换为字节序列)和反序列化。Flutter框架提供了几个内置的编解码器来处理常见的数据类型,开发者也可以自定义编解码器。

(4).平台通道的使用

使用平台通道时,通常需要在Flutter端和原生端同时进行代码编写。

以下是一个简单的MethodChannel使用示例:

Flutter端
import 'package:flutter/services.dart';class BatteryLevel {static const MethodChannel _channel =MethodChannel('com.example.battery');static Future<int> getBatteryLevel() async {final int batteryLevel = await _channel.invokeMethod('getBatteryLevel');return batteryLevel;}
}
Android端(Kotlin)
class MainActivity: FlutterActivity() {private val CHANNEL = "com.example.battery"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {call, result ->if (call.method == "getBatteryLevel") {val batteryLevel = getBatteryLevel()if (batteryLevel != -1) {result.success(batteryLevel)} else {result.error("UNAVAILABLE", "Battery level not available.", null)}} else {result.notImplemented()}}}private fun getBatteryLevel(): Int {val batteryLevel: Intif (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManagerbatteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)} else {val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)}return batteryLevel}
}
iOS端(Swift)
import Flutter
import UIKit@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private let batteryChannel = "com.example.battery"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)let controller: FlutterViewController = window?.rootViewController as! FlutterViewControllerlet methodChannel = FlutterMethodChannel(name: batteryChannel,binaryMessenger: controller.binaryMessenger)methodChannel.setMethodCallHandler({[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in// Handle battery messages.guard call.method == "getBatteryLevel" else {result(FlutterMethodNotImplemented)return}self?.receiveBatteryLevel(result: result)})return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func receiveBatteryLevel(result: FlutterResult) {let device = UIDevice.currentdevice.isBatteryMonitoringEnabled = trueif device.batteryState == UIDevice.BatteryState.unknown {result(FlutterError(code: "UNAVAILABLE",message: "Battery level not available.",details: nil))} else {result(Int(device.batteryLevel * 100))}}
}

2.Flutter 平台通道比较及示例

在Flutter中,MethodChannelEventChannelBasicMessageChannel是三种不同的平台通道,用于实现Dart代码和原生平台代码之间的通信。

MethodChannel

MethodChannel通常用于发送方法调用请求,并接收单次响应。它适合于执行原生操作并获取结果的场景。

Flutter端
import 'package:flutter/services.dart';// 创建MethodChannel实例
const MethodChannel methodChannel = MethodChannel('com.example.channel/method');// 调用原生方法并获取电池电量
Future<int> getBatteryLevel() async {try {// 调用原生平台的'getBatteryLevel'方法final int result = await methodChannel.invokeMethod('getBatteryLevel');return result;} on PlatformException catch (e) {// 处理异常情况return -1;}
}
Android端(Kotlin)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannelclass MainActivity: FlutterActivity() {// 定义通道名称private val CHANNEL = "com.example.channel/method"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 创建MethodChannel实例并设置方法调用处理器MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->if (call.method == "getBatteryLevel") {// 执行获取电池电量的操作,并将结果返回给Flutter端val batteryLevel = getBatteryLevel()if (batteryLevel != -1) {result.success(batteryLevel)} else {result.error("UNAVAILABLE", "Battery level not available.", null)}} else {result.notImplemented()}}}// 模拟获取电池电量的函数private fun getBatteryLevel(): Int {// 这里只是一个示例,实际获取电池电量的方法与此不同return 100 // 假设电池电量是100%}
}
iOS端(Swift)
import Flutter
import UIKit@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {// 定义通道名称private let methodChannelName = "com.example.channel/method"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)guard let controller = window?.rootViewController as? FlutterViewController else {fatalError("rootViewController is not type FlutterViewController")}// 创建MethodChannel实例并设置方法调用处理器let methodChannel = FlutterMethodChannel(name: methodChannelName,binaryMessenger: controller.binaryMessenger)methodChannel.setMethodCallHandler({[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in// 检查方法名并执行相应操作if call.method == "getBatteryLevel" {// 执行获取电池电量的操作,并将结果返回给Flutter端self?.receiveBatteryLevel(result: result)} else {result(FlutterMethodNotImplemented)}})return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func receiveBatteryLevel(result: FlutterResult) {// 这里只是一个示例,实际获取电池电量的方法与此不同result(100) // 假设电池电量是100%}
}

EventChannel

EventChannel用于数据流(event streams),可以用来监听原生端发出的事件。例如,原生端的传感器数据或数据库更新。

Flutter端
import 'package:flutter/services.dart';// 创建EventChannel实例
const EventChannel eventChannel = EventChannel('com.example.channel/stream');// 监听来自原生平台的事件流
void listenToNativeEvents() {eventChannel.receiveBroadcastStream().listen((event) {// 处理原生平台发送的数据print('Received event: $event');}, onError: (error) {// 处理错误print('Received error: ${error.message}');});
}
Android端(Kotlin)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import java.util.*class MainActivity : FlutterActivity() {// 定义通道名称private val CHANNEL = "com.example.channel/stream"// 模拟传感器数据流private val sensorDataStream = Timer()override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 创建EventChannel实例并设置事件流处理器EventChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setStreamHandler(object : EventChannel.StreamHandler {override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {// 设置定时器,模拟传送传感器数据sensorDataStream.schedule(object : TimerTask() {override fun run() {// 模拟事件数据,这里发送当前时间戳events?.success(System.currentTimeMillis())}}, 0, 1000)}override fun onCancel(arguments: Any?) {// 取消事件流sensorDataStream.cancel()}})}
}
iOS端(Swift)
import Flutter
import UIKitclass StreamHandler: NSObject, FlutterStreamHandler {private var eventSink: FlutterEventSink?private var timer: Timer?func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {eventSink = events// 设置定时器,模拟传送传感器数据timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in// 模拟事件数据,这里发送当前时间戳events(Date().timeIntervalSince1970 * 1000)}return nil}func onCancel(withArguments arguments: Any?) -> FlutterError? {// 取消定时器timer?.invalidate()timer = nileventSink = nilreturn nil}
}@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {// 定义通道名称private let streamChannelName = "com.example.channel/stream"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)let controller: FlutterViewController = window?.rootViewController as! FlutterViewController// 创建EventChannel实例并设置事件流处理器let eventChannel = FlutterEventChannel(name: streamChannelName, binaryMessenger: controller.binaryMessenger)let streamHandler = StreamHandler()eventChannel.setStreamHandler(streamHandler)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}

BasicMessageChannel

BasicMessageChannel 允许发送和接收字符串和半结构化信息,适用于自定义编码和协议,或者需要频繁通信但数据量不大的场景。

Flutter端
import 'package:flutter/services.dart';// 创建BasicMessageChannel实例
BasicMessageChannel<String> basicMessageChannel =BasicMessageChannel<String>('com.example.channel/basic', StringCodec());// 向原生发送消息
Future<void> sendMessage(String message) async {String response = await basicMessageChannel.send(message);print('Received response: $response');
}// 接收来自原生的消息
void receiveMessages() {basicMessageChannel.setMessageHandler((String message) async {print('Received message: $message');return 'Received!';});
}
Android端(Kotlin)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.StringCodecclass MainActivity: FlutterActivity() {private val CHANNEL = "com.example.channel/basic"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 创建BasicMessageChannel实例val messageChannel = BasicMessageChannel<String>(flutterEngine.dartExecutor.binaryMessenger, CHANNEL, StringCodec.INSTANCE)// 设置消息接收器messageChannel.setMessageHandler { message, reply ->// 在这里处理从Flutter接收到的消息println("Received message: $message")// 回复消息给Flutter端reply.reply("Echo: $message")}}
}
iOS端(Swift)
import Flutter
import UIKit@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private let basicMessageChannelName = "com.example.channel/basic"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)guard let controller = window?.rootViewController as? FlutterViewController else {fatalError("rootViewController is not type FlutterViewController")}// 创建BasicMessageChannel实例let messageChannel = FlutterBasicMessageChannel(name: basicMessageChannelName,binaryMessenger: controller.binaryMessenger,codec: FlutterStringCodec.sharedInstance())// 设置消息接收器messageChannel.setMessageHandler { (message: Any?, reply: FlutterReply) in// 在这里处理从Flutter接收到的消息if let messageStr = message as? String {print("Received message: \(messageStr)")// 回复消息给Flutter端reply("Echo: \(messageStr)")}}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}

3.异步编程在平台通道中的应用

在Flutter中,平台通道的调用通常是异步的,这意味着你可以使用asyncawait关键字来等待原生代码的执行结果而不会阻塞UI线程。

例如,使用MethodChannel请求电池电量时,你通常会这样做:

Future<void> _getBatteryLevel() async {String batteryLevel;try {final int result = await NativeBridge.getBatteryLevel();batteryLevel = 'Battery level at $result % .';} on PlatformException {batteryLevel = 'Failed to get battery level.';}print(batteryLevel);
}

三、设置平台通道

1.定义通道名

我们将设置一个名为 "com.example.channel/method" 的 MethodChannel 平台通道,这个通道将用于Flutter与原生平台(iOS和Android)之间的通信。

2.在 Flutter端创建MethodChannel

在Flutter端,你需要创建一个 MethodChannel 实例,并通过它发送消息到原生平台。

// 导入相关包
import 'package:flutter/services.dart';// 定义通道名
const platformChannel = MethodChannel('com.example.channel/method');// 调用原生方法
Future<void> getNativeData() async {try {final String result = await platformChannel.invokeMethod('getNativeData');print('从原生平台获取的数据:$result');} on PlatformException catch (e) {print("调用原生平台方法失败:${e.message}");}
}

在上面的代码中,我们定义了一个方法通道名 "com.example.channel/method",并创建了 MethodChannel 实例。我们定义了一个名为 getNativeData 的函数,该函数调用原生平台的 'getNativeData' 方法,并处理来自原生平台的响应或异常。

3.在Android(Kotlin)端监听通道

在Android端,我们需要在 MainActivity 中设置 MethodChannel 并定义方法调用的处理逻辑。

// 导入相关包
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandlerclass MainActivity: FlutterActivity() {private val CHANNEL = "com.example.channel/method"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 设置MethodChannel并监听来自Flutter的调用MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->// 判断调用的方法名if (call.method == "getNativeData") {// 执行获取数据的逻辑val nativeData = "来自Android的数据"// 使用result对象发送结果回Flutterresult.success(nativeData)} else {// 如果方法未实现,返回未实现的错误result.notImplemented()}}}
}

在这段代码中,我们在 MainActivity 类中创建了一个 MethodChannel 实例,然后设置了一个 MethodCallHandler 来监听来自Flutter的方法调用。当Flutter调用 'getNativeData' 方法时,我们返回一些模拟的数据给Flutter。

3.在iOS(Swift)端监听通道

在iOS端,我们在 AppDelegate 中设置 MethodChannel 并处理方法调用。

import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {// 定义通道名 与Flutter端定义的通道名称保持一致private let channelName = "com.example.channel/method"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)guard let controller = window?.rootViewController as? FlutterViewController else {fatalError("rootViewController is not type FlutterViewController")}let channel = MethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)// 设置监听器以处理来自Flutter的方法调用channel.setMethodCallHandler {(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in// 检查方法名以确定执行哪个原生方法if call.method == "getNativeData" {// 执行获取数据逻辑let nativeData = "来自iOS的数据"// 发送结果回Flutterresult(nativeData)} else {// 如果方法未实现,返回未实现的错误result(FlutterMethodNotImplemented)}}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}

在这段Swift代码中,我们在 AppDelegate 类中同样定义了一个 MethodChannel,并设置了一个闭包作为 MethodCallHandler 来监听Flutter的方法调用。当Flutter请求 'getNativeData' 方法时,我们返回一串模拟的数据。

四、跳转到原生页面

为了实现Flutter跳转到原生页面的功能,我们需要在三个平台上编写相应的代码:Flutter、iOS和Android。

Flutter端

在Flutter端,我们使用MethodChannel来发起跳转到原生页面的请求。

// 导入必要的包
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: HomeScreen(),);}
}class HomeScreen extends StatelessWidget {// 创建MethodChannel实例,通道名称要与原生端一致static const platformChannel = MethodChannel('com.example.channel/native');// 调用原生方法跳转到原生页面的函数Future<void> openNativeScreen() async {try {// 调用原生平台的openNativeScreen方法await platformChannel.invokeMethod('openNativeScreen');} on PlatformException catch (e) {// 如果跳转失败,捕获异常print("Failed to open native screen: ${e.message}");}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Flutter Home Screen'),),body: Center(child: ElevatedButton(onPressed: openNativeScreen, // 点击按钮时发起跳转child: Text('Go to Native Screen'),),),);}
}

在上面的Flutter代码中,我们定义了一个按钮,当用户点击这个按钮时,openNativeScreen函数会通过MethodChannel发起一个名为openNativeScreen的方法调用。

Android端(Kotlin)

在Android端,在MainActivity中监听MethodChannel,并对openNativeScreen方法调用进行处理,启动一个新的Activity

// MainActivity.kt
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannelclass MainActivity: FlutterActivity() {private val CHANNEL = "com.example.channel/native"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->if (call.method == "openNativeScreen") {// 创建Intent并启动新的Activityval intent = Intent(this, NativeActivity::class.java)startActivity(intent)// 返回成功结果result.success(null)} else {result.notImplemented()}}}
}
// NativeActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity// 创建新的Activity用于展示原生页面
class NativeActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 设置原生页面的布局setContentView(R.layout.activity_native)}
}

在Android项目中,我们还需要在AndroidManifest.xml中注册NativeActivity

<activity android:name=".NativeActivity"><!-- 配置和其他Activity相关的属性 -->
</activity>

iOS端(Swift)

在iOS端,在AppDelegate中监听MethodChannel,并对openNativeScreen方法调用进行处理,启动一个新的UIViewController

import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private let channelName = "com.example.channel/native"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)if let controller = window?.rootViewController as? FlutterViewController {let channel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void inif call.method == "openNativeScreen" {// 实现UIViewController的跳转逻辑self.openNativeScreen(from: controller)result(nil)} else {result(FlutterMethodNotImplemented)}}}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func openNativeScreen(from flutterViewController: FlutterViewController) {// 创建原生页面的UIViewController
let nativeViewController = NativeViewController()// 从当前FlutterViewController进行页面跳转flutterViewController.present(nativeViewController, animated: true, completion: nil)}
}
// NativeViewController.swift
import UIKit// 创建一个新的UIViewController子类作为原生页面
class NativeViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()// 设置原生页面的属性,例如背景色view.backgroundColor = UIColor.white}
}

五、从 Flutter 传值给原生页面并得到响应

为了实现从Flutter应用传递数据到原生页面,并在原生页面中接收和解析这些数据,我们需要分别在Flutter、Android和iOS端编写代码。

Flutter端发送数据

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: HomeScreen(),);}
}class HomeScreen extends StatelessWidget {// 创建MethodChannel实例,通道名称要与原生端一致static const platformChannel = MethodChannel('com.example.channel/transfer');// 向原生页面发送简单数据Future<void> sendSimpleData() async {try {final String response = await platformChannel.invokeMethod('sendSimpleData', {'message': 'Hello from Flutter!'});print(response); // 打印原生页面返回的响应} on PlatformException catch (e) {print("Failed to send simple data: ${e.message}");}}// 向原生页面发送复杂数据(如JSON)Future<void> sendComplexData() async {try {final Map<String, dynamic> complexData = {'user': {'id': 1,'name': 'John Doe','email': 'johndoe@example.com',}};final String response = await platformChannel.invokeMethod('sendComplexData', {'data': json.encode(complexData)});print(response); // 打印原生页面返回的响应} on PlatformException catch (e) {print("Failed to send complex data: ${e.message}");}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Flutter to Native Data Transfer'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[ElevatedButton(onPressed: sendSimpleData,child: Text('Send Simple Data'),),ElevatedButton(onPressed: sendComplexData,child: Text('Send Complex Data'),),],),),);}
}

在Flutter端,我们创建了两个函数sendSimpleDatasendComplexData,用于发送简单和复杂数据。数据通过MethodChannel的invokeMethod函数发送到原生端,同时可以从原生端接收响应。

Android端(Kotlin)

在Android端,我们监听MethodChannel,并使用Gson库来解析JSON格式的复杂数据。

// MainActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.google.gson.Gson
import com.google.gson.reflect.TypeTokenclass MainActivity: FlutterActivity() {private val CHANNEL = "com.example.channel/transfer"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->when (call.method) {"sendSimpleData" -> {val message = call.argument<String>("message")// 使用传递的简单数据// ...// 发送响应回Flutter端result.success("Received simple data: $message")}"sendComplexData" -> {val data = call.argument<String>("data")val type = object : TypeToken<Map<String, Any>>() {}.typeval complexData: Map<String, Any> = Gson().fromJson(data, type)// 使用传递的复杂数据// ...// 发送响应回Flutter端result.success("Received complex data")}else -> result.notImplemented()}}}
}

iOS端(Swift)

在iOS端,我们监听MethodChannel,并使用Swift的Codable协议或JSONSerialization来解析JSON格式的复杂数据。

// AppDelegate.swift
import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private let channelName = "com.example.channel/transfer"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)guard let controller = window?.rootViewController as? FlutterViewController else {fatalError("rootViewController is not type FlutterViewController")}let channel = FlutterMethodChannel(name: channelName,binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler { [weak self] (call, result) inswitch call.method {case "sendSimpleData":if let message = call.arguments as? String {// 使用传递的简单数据// ...// 发送响应回Flutter端result("Received simple data: \(message)")} else {result(FlutterError(code: "INVALID_ARGUMENT", message: "Expected a string argument", details: nil))}case "sendComplexData":if let jsonString = call.arguments as? String,let data = jsonString.data(using: .utf8) {do {// 使用Codable进行解析let user = try JSONDecoder().decode(User.self, from: data)// 使用传递的复杂数据// ...// 发送响应回Flutter端result("Received complex data")} catch {result(FlutterError(code: "JSON_PARSE_ERROR", message: "Error parsing JSON", details: nil))}} else {result(FlutterError(code: "INVALID_ARGUMENT", message: "Expected a JSON string argument", details: nil))}default:result(FlutterMethodNotImplemented)}}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}// 定义User结构体以匹配传递的复杂数据结构
struct User: Codable {var id: Intvar name: Stringvar email: String
}

六、从原生页面回传数据到 Flutter

当需要从原生页面回传数据到Flutter时,有两种常见的方法:使用MethodChannel或者使用ActivityResult(Android端)/使用委托(delegate)/闭包(closures)(iOS端)。

  • 使用MethodChannel回传数据:此方法适用于任何时候原生代码需要主动发送数据到Flutter端的情况,不仅限于页面返回时。Flutter端通过MethodChannel与原生代码通信,可以接收来自原生端的数据。

  • 使用ActivityResult(Android)/闭包和协议委托(iOS):此方法一般用于原生页面关闭时,将数据回传到Flutter端。在Android上,可以通过setResult方法和Intent返回数据给前一个Activity。在iOS上,可以通过代理模式或闭包将数据回传给之前的控制器。

在Flutter端,我们需要设置好MethodChannel监听原生端发来的数据,或者在启动原生页面时等待结果。

方式一:远程过程调用

Flutter端设置了MethodChannel监听并处理原生端的调用,当原生代码处理完毕后,通过相同的MethodChannel返回结果。这是一种典型的RPC(远程过程调用)模式。

  • 单次请求-响应模式:Flutter发起调用,原生端返回数据,通信完成。
  • Flutter端主动请求:Flutter通过invokeMethod主动请求原生数据。
  • 异步等待原生端响应:Flutter调用后使用await关键字等待原生端完成操作并返回结果。
  • 适用场景:当Flutter需要原生端某个特定操作的结果时使用,比如获取设备信息、处理完毕的数据等。

Flutter端接收返回数据

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class HomeScreen extends StatefulWidget {@override_HomeScreenState createState() => _HomeScreenState();
}class _HomeScreenState extends State<HomeScreen> {// 创建一个MethodChannel,用于与原生平台通信static const platformChannel = MethodChannel('com.example.channel/transfer');// 用于显示从原生平台接收到的数据String _dataFromNative = 'No data';@overridevoid initState() {super.initState();// 在 initState 中设置监听器以处理原生平台发来的方法调用platformChannel.setMethodCallHandler(_handleMethodCall);}// 处理从原生平台接收到的方法调用Future<dynamic> _handleMethodCall(MethodCall call) async {switch (call.method) {case 'onDataReturn':// 当原生平台返回数据时,更新状态以显示数据setState(() {_dataFromNative = call.arguments;});break;default:throw MissingPluginException('notImplemented');}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Flutter Native Data Return'),),body: Center(// 显示从原生端接收到的数据child: Text(_dataFromNative),),);}
}

Android端(Kotlin)设置返回数据

在Android端,你可以在MainActivity中创建一个按钮,并在点击事件中使用MethodChannel向Flutter发送消息:

// ...其他导入
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannelclass MainActivity: FlutterActivity() {// 与Flutter端相同的通道名称private val CHANNEL = "com.example.channel/transfer"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化MethodChannelval channel = MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL)// ...其他代码// 用于向Flutter发送数据fun sendDataToFlutter() {// 使用MethodChannel调用Flutter端定义的方法,并传递数据channel.invokeMethod("onDataReturn", "这是来自原生平台的数据")}// 假设有个触发发送数据到Flutter的逻辑sendDataToFlutter()}
}

iOS端(Swift)设置返回数据

在iOS端,你可以在ViewController中为一个按钮,并在这个按钮回调中使用MethodChannel向Flutter发送消息:

// ...其他导入
import Flutterclass ViewController: UIViewController {private var channel: FlutterMethodChannel?override func viewDidLoad() {super.viewDidLoad()// 初始化MethodChannelguard let controller = self as? FlutterViewController else {fatalError("ViewController is not type FlutterViewController")}channel = FlutterMethodChannel(name: "com.example.channel/transfer", binaryMessenger: controller.binaryMessenger)// ...其他代码// 用于向Flutter发送数据func sendDataToFlutter() {// 使用MethodChannel调用Flutter端定义的方法,并传递数据channel?.invokeMethod("onDataReturn", arguments: "这是来自原生平台的数据")}// 假设有个触发发送数据到Flutter的逻辑sendDataToFlutter()}
}

方式二:事件订阅模式

是一种事件订阅模式,Flutter端通过MethodChannel设置监听器,任何时候原生端都可以主动调用这个通道并发送数据到Flutter,而Flutter端则在_listener_方法中处理所有接收到的消息。

  • 持续监听模式:Flutter监听原生端的方法调用,原生端可以在任何时刻主动发送消息。
  • 原生端主动发送消息:原生代码在适当的时候(如某事件发生后)主动调用MethodChannel向Flutter发送消息。
  • 适用场景:对于原生端事件的实时监听和处理,如位置更新、传感器数据等。

Flutter端接收返回数据

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class HomeScreen extends StatelessWidget {// 创建MethodChannel实例static const platformChannel = MethodChannel('com.example.channel/transfer');// 调用原生页面并等待返回结果Future<void> _navigateAndDisplaySelection(BuildContext context) async {final result = await platformChannel.invokeMethod('startNativeView');// 使用返回结果更新UI或状态if (result != null) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('原生页面返回的数据: $result')),);}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Flutter Native Data Return'),),body: Center(child: ElevatedButton(onPressed: () => _navigateAndDisplaySelection(context),child: Text('打开原生页面'),),),);}
}

在上面的Flutter端代码中,我们通过platformChannel.invokeMethod调用原生端的方法打开一个原生页面,并使用await关键字等待异步结果。当原生页面关闭并返回数据时,我们可以通过result变量接收这个数据,并通过ScaffoldMessenger显示在一个SnackBar中。

Android端(Kotlin)设置返回数据

在Android端,我们可以使用setResult方法和Intent回传数据。

// 原生页面Activity
import android.app.Activity
import android.content.Intent
import android.os.Bundleclass NativeActivity : Activity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 设置布局、初始化等操作// ...// 假设这是一个按钮点击后触发的事件val button = findViewById<Button>(R.id.button_id)button.setOnClickListener {val returnIntent = Intent()returnIntent.putExtra("result_key", "这里是从原生返回的数据")setResult(Activity.RESULT_OK, returnIntent)finish() // 关闭当前原生页面,回传数据到Flutter}}
}

 在Android原生端,你需要定义一个MethodChannel并监听startNativeView方法调用,然后打开一个新的Activity,并在关闭时设置返回结果:

// MainActivity.kt (Android端)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannelclass MainActivity: FlutterActivity() {private val CHANNEL = "com.example.channel/transfer"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {call, result ->if (call.method == "startNativeView") {// 调用原生页面val intent = Intent(this, NativeActivity::class.java)startActivityForResult(intent, REQUEST_CODE)} else {result.notImplemented()}}}// 接收原生页面返回的结果override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {val returnData = data?.getStringExtra("result_key") ?: "No data"// 使用MethodChannel返回数据到FlutterMethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("onDataReturn", returnData)}}companion object {const val REQUEST_CODE = 100}
}

iOS端(Swift)设置返回数据

在iOS端,我们可以使用闭包或者协议委托来回传数据。

// 原生页面ViewController
import UIKitclass NativeViewController: UIViewController {// 定义闭包,用于回传数据var onReturnData: ((String) -> Void)?override func viewDidLoad() {super.viewDidLoad()// 设置布局、初始化等操作// ...let button = UIButton()button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)}@objc func didTapButton() {// 当按钮被点击时,通过闭包传递数据回Flutter端onReturnData?("这里是从原生返回的数据")dismiss(animated: true, completion: nil) // 关闭当前页面}
}

在iOS原生端,你需要在Flutter与原生端之间建立一个MethodChannel,并监听Flutter端的调用,然后打开一个新的ViewController,并在关闭时回传数据:

// AppDelegate.swift (iOS端)
import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private var flutterResult: FlutterResult?override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {if let controller = window?.rootViewController as? FlutterViewController {let channel = FlutterMethodChannel(name: "com.example.channel/transfer",binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler({[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in// 保存FlutterResult回调self?.flutterResult = result// 检查是否是打开原生页面的调用if call.method == "startNativeView" {self?.showNativeView(from: controller)} else {result(FlutterMethodNotImplemented)}})}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func showNativeView(from controller: FlutterViewController) {let nativeViewController = NativeViewController()// 设置回调闭包,以便在ViewController关闭时返回数据nativeViewController.onReturnData = { [weak self] data inself?.flutterResult?(data)}// 展示原生ViewControllercontroller.present(nativeViewController, animated: true, completion: nil)}
}

七、错误处理和异常安全

错误处理是编写健壮软件的重要组成部分。下面将详细介绍如何在Flutter、Android(Kotlin)、iOS(Swift)三个平台上进行错误处理和异常安全。

我们在Flutter端通过MethodChannel调用原生代码,并且在原生端使用try-catch(Kotlin)或do-try-catch(Swift)来捕获可能发生的异常。如果捕获到异常,原生端会通过MethodChannel.Result(Kotlin)或FlutterResult(Swift)将错误信息传递回Flutter。

异常处理机制确保了当原生代码执行出错时,Flutter端可以收到错误信息,并且可以根据错误信息做出相应的处理,从而提供更加健壮的用户体验。

Flutter端处理原生代码抛出的异常

在Flutter端,我们可以通过try-catch块来捕获和处理通过MethodChannel调用原生代码时可能抛出的异常。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class ExceptionHandlingScreen extends StatefulWidget {@override_ExceptionHandlingScreenState createState() => _ExceptionHandlingScreenState();
}class _ExceptionHandlingScreenState extends State<ExceptionHandlingScreen> {static const platformChannel = MethodChannel('com.example.channel/methods');String _result = 'No data';Future<void> _invokeNativeMethod(String methodName) async {try {final String result = await platformChannel.invokeMethod(methodName);setState(() {_result = result;});} on PlatformException catch (e) {// 捕获由原生代码抛出的异常setState(() {_result = "原生代码发生异常:${e.message}";});} catch (e) {// 捕获其他异常setState(() {_result = "未知异常:${e.toString()}";});}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('异常处理示例'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[ElevatedButton(onPressed: () => _invokeNativeMethod('someMethod'),child: Text('调用原生方法'),),Padding(padding: const EdgeInsets.all(8.0),child: Text(_result),),],),),);}
}

Android端(Kotlin)

在Android(Kotlin)端,我们可以使用try-catch块来处理可能在执行方法调用时发生的异常,并通过MethodChannel.Result向Flutter端传递错误信息。

// MainActivity.kt (Android端)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannelclass MainActivity : FlutterActivity() {private val CHANNEL = "com.example.channel/methods"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->try {when (call.method) {"someMethod" -> {// 你的原生方法处理result.success("从原生返回的数据")}else -> result.notImplemented()}} catch (e: Exception) {// 捕获异常并将错误返回到Flutterresult.error("ERROR", "方法执行出错: ${e.localizedMessage}", null)}}}
}

iOS端(Swift)

在iOS(Swift)端,我们一般通过do-try-catch块来处理方法调用时可能抛出的异常,并通过FlutterResult传递错误信息给Flutter端。

// AppDelegate.swift (iOS端)
import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {private let channelName = "com.example.channel/methods"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {guard let controller = window?.rootViewController as? FlutterViewController else {fatalError("rootViewController is not type FlutterViewController")}let channel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler { (call, result) in// 检查方法名称并执行相应的原生代码if call.method == "someMethod" {do {// 尝试执行方法并捕获可能的异常let data = try self.someNativeMethod()result(data)} catch {// 如果有异常,将异常信息返回给Flutterresult(FlutterError(code: "ERROR",message: "方法执行出错: \(error.localizedDescription)",details: nil))}} else {result(FlutterMethodNotImplemented)}}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func someNativeMethod() throws -> String {// 这里是你的原生方法实现,可能会抛出异常// 例如,我们这里直接抛出一个异常来模拟错误情况throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "演示错误"])}
}

相关文章:

  • 技术详解 | 黑客如何通过PDF文件分发恶意软件
  • 基于java的电影院售票网站
  • ajax的优缺点有哪些?
  • 每日一题:用c语言中指针实现除2操作
  • Centos7安装Docker与Docker-compose【图文教程】
  • Flutter iOS上架指南
  • 【JAVAEE学习】探究Java中多线程的使用和重点及考点
  • Python语言例题集(010)
  • 苍穹外卖07(缓存菜品,SpringCache,缓存套餐,添加购物车菜品和套餐多下单,查看购物车,清除购物车,删除购物车中一个商品)
  • vue 数据埋点
  • Node.js入门
  • 韩顺平 | 零基础快速学Python
  • UE RPC 外网联机(1)
  • 《极客时间TonyBai go语言第一课》学习笔记
  • 在 Jupyter Notebook 中切换环境
  • (三)从jvm层面了解线程的启动和停止
  • Angular6错误 Service: No provider for Renderer2
  • C++入门教程(10):for 语句
  • nfs客户端进程变D,延伸linux的lock
  • python docx文档转html页面
  • Python 基础起步 (十) 什么叫函数?
  • Sass Day-01
  • Sequelize 中文文档 v4 - Getting started - 入门
  • SpringBoot几种定时任务的实现方式
  • SQLServer之索引简介
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 高性能JavaScript阅读简记(三)
  • 构建工具 - 收藏集 - 掘金
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 实现菜单下拉伸展折叠效果demo
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 新版博客前端前瞻
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • #{}和${}的区别?
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • (52)只出现一次的数字III
  • (C#)一个最简单的链表类
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (转)视频码率,帧率和分辨率的联系与区别
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET Core WebAPI中封装Swagger配置
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [@Controller]4 详解@ModelAttribute
  • [04]Web前端进阶—JS伪数组
  • [2669]2-2 Time类的定义
  • [AutoSar]BSW_OS 01 priority ceiling protocol(PCP)