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端接收返回数据

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

通过MethodChannel接收数据

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class HomeScreen extends StatefulWidget {@override_HomeScreenState createState() => _HomeScreenState();
}class _HomeScreenState extends State<HomeScreen> {static const platformChannel = MethodChannel('com.example.channel/transfer');String _dataFromNative = 'No data';@overridevoid initState() {super.initState();// 设置MethodChannel监听原生端消息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),),);}
}

通过ActivityResult接收数据

在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: "演示错误"])}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/784459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何通过Elasticsearch实现搜索的关键词达到高亮的效果

高亮 首先介绍一下什么是搜索的关键词达到高亮的效果&#xff0c;如图所示 当在百度里面搜索elasticsearch的时候&#xff0c;可以看到出现的搜索结果里面elasticsearch这个关键词明显与其他的条文不一样&#xff0c;用红颜色凸显了“高亮效果”。当我们想要在自己的项目里面…

1、初识drf

drf的学习需要学习者有django基本使用知识。 文章目录 什么是drf&#xff0c;有什么作用CBV是什么初步使用drf 下载以及django创建项目django最小启动内容修改setting修改 url 编写drf视图编辑url测试返回结果 什么是drf&#xff0c;有什么作用 drf(django rest-framework),让…

C#面:有几种连接到数据库的方式,请列举出来并分别说明它们可以连接哪些数据库

ADO.NET 连接&#xff1a; ADO.NET 是 .NET Framework 提供的一种数据访问技术&#xff0c;可以连接多种类型的数据库&#xff0c;包括但不限于SQL Server、Oracle、MySQL 等。通过 ADO.NET 连接&#xff0c;可以使用各种提供程序&#xff08;如SqlClient、OracleClient、OleD…

探索Vue脚手架:构建现代化Web应用的利器

随着现代Web应用程序的不断发展&#xff0c;前端开发者们需要更高效、更灵活的工具来构建复杂的用户界面。在这个领域中&#xff0c;Vue.js作为一种流行的JavaScript框架&#xff0c;为开发者提供了丰富的生态系统和强大的工具。而Vue脚手架&#xff0c;作为Vue.js开发过程中的…

手机有线投屏到直播姬pc端教程

1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 手机用usb数据线连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电(手机差异要求为仅充电),不同品牌手机要求可能不一样,根据实际的来 3 在投屏过程中不要更改usb的连接方式(不然电脑会死机需要重启) …

MultiPath HTTP:北大与华为合作部署FLEETY

当前的终端基本都能支持蜂窝网络和wifi网络&#xff0c;然而&#xff0c;不同的网络通路都不可避免的会出现信号不好或者其他因素引起的通路性能(吞吐量、时延等)下降。为了能够提升终端业务体验&#xff0c;很多不同的MultiPath方案被提出&#xff0c;其中&#xff0c;包括应用…

使用Detours进行HOOK

文章目录 Detours介绍Detours配置Detours进行Sleep Hook Detours介绍 Detours是微软研究院开发的一款软件工具&#xff0c;用于Windows平台上的应用程序重定向和修改。 它可以在运行时修改应用程序的执行路径&#xff0c;允许开发人员注入自定义代码来改变应用程序的 行为&…

vulhub中Apache Solr 远程命令执行漏洞复现(CVE-2017-12629)

Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发&#xff0c;主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个XML/JSON响应来实现。此次7.1.0之前版本总共爆出两个漏洞&#xff1a;[XM…

勒索病毒钱包地址与邮箱地址集合

勒索病毒攻击已经成为了全球最大的安全威胁之一&#xff0c;99%以上的勒索病毒交付赎金都是使用BTC&#xff0c;BTC已经涨到一万多美元了&#xff0c;最近一年针对企业的勒索病毒攻击也越来越多&#xff0c;勒索病毒黑产团队在加密受害者文件之后一般会留下一些联系方式&#x…

[Flutter]打包IPA

1.直接使用Xcode运行iOS工程 不用flutter构建&#xff0c;在Xcode中是可以独立进行构建运行和打包发布的。 1).运行项目 先将flutter的build清理 $ flutter clean $ flutter pub get 然后立即用XCode打开iOS工程运行 运行会报错&#xff1a; error: The sandbox is not …

分块大小

两个指针 复杂度 \(O(u*n\frac{n^2}{u})\) 根据均值不等式&#xff0c; \(u*n\frac{n^2}{u}\) 在 \(u*n\frac{n^2}{u}\) 时取最小值 即 \(u\sqrt{n}\) 三个指针&#xff08;带修&#xff09; 复杂度 \(O(u*n\frac{n^2}{u}\frac{n^3}{u^2})\) 显然&#xff0c; \(\frac{n^2}…

C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测

C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测 目录 C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测效果一览基本介绍模型描述程序…

Vue.js基础指令

&#xff08;在讲指令之前&#xff0c;可以先了解插值表达式&#xff0c;如果已经知道&#xff0c;当我没说&#xff09; 一.插值表达式 1.数据绑定最常见的形式就是双大括号的文本插值&#xff0c;Mustache上属性的值替代。只要绑定的数据对象上属性发生了改变&#xff0c;插…

设置asp.net core WebApi函数请求参数可空的两种方式

以下面定义的asp.net core WebApi函数为例&#xff0c;客户端发送申请时&#xff0c;默认三个参数均为必填项&#xff0c;不填会报错&#xff0c;如下图所示&#xff1a; [HttpGet] public string GetSpecifyValue(string param1,string param2,string param3) {return $"…

Node.js中的导入导出

Node.js中的导入导出 一.CommonJs标准二.ECMAScript标准1.默认导入导出2.命名导出和导入 一.CommonJs标准 导出语法&#xff1a; module.exports {对外属性名: 模块内私有变量 }导入语法&#xff1a; const 变量名 require(模块名或路径) // Node.js 环境内置模块直接写模…

【PSINS工具箱】EKF与UKF滤波

描述 对工具箱SINS/GPS&#xff0c;153例程的修改&#xff0c;将EKF和UKF放在一个文件里面&#xff0c;一次运行可以得到两个滤波的结果。 片段 运行截图 程序完整源代码 在有工具箱的情况下&#xff0c;直接运行此代码&#xff0c;即可得到结果 % 基于PSINS工具箱的IMU数据…

腾讯云轻量2核2G3M云服务器优惠价格61元一年,限制200GB月流量

腾讯云轻量2核2G3M云服务器优惠价格61元一年&#xff0c;配置为轻量2核2G、3M带宽、200GB月流量、40GB SSD盘&#xff0c;腾讯云优惠活动 yunfuwuqiba.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云轻量2核2G云服务器优惠价格 腾讯云&#xff1a;轻量应用服务器100%CPU性能…

pyqt 创建右键菜单栏

class MainModule(QMainWindow, Ui_MainWindow):def __init__(self):super().__init__(parentNone)self.setupUi(self)# 允许出现菜单栏self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)# 对空间添加右键菜单栏处理 self.tableWidget.customContextMenuRequested.…

Java设计模式详解:单例模式

设计模式详解&#xff1a;单例模式 文章目录 设计模式详解&#xff1a;单例模式一、单例模式的原理二、单例模式的实现推荐1、饿汉模式2、静态内部类 三、单例模式的案例四、单例模式的使用场景推荐总结 一、单例模式的原理 单例模式听起来很高大上&#xff0c;但其实它的核心…

速盾:cdn可以加速接口吗?

随着互联网应用的不断发展&#xff0c;网站的访问速度成为了用户体验的重要指标之一。为了解决网站访问速度过慢的问题&#xff0c;CDN&#xff08;内容分发网络&#xff09;技术被广泛应用。CDN作为一种分布式的网络架构&#xff0c;可以将静态资源如图片、视频、JS、CSS等内容…