一、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: Int
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryLevel = 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! FlutterViewController
    let 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.current
    device.isBatteryMonitoringEnabled = true
    if 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.MethodChannel

class 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 UIKit

class 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 = nil
        eventSink = nil
        return 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.StringCodec

class 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.MethodCallHandler

class 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对象发送结果回Flutter
                result.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的数据"
        // 发送结果回Flutter
        result(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 {
  @override
  Widget 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}");
    }
  }

  @override
  Widget 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.MethodChannel

class 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并启动新的Activity
                val 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 in
        if 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 {
  @override
  Widget 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}");
    }
  }

  @override
  Widget 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.TypeToken

class 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>>() {}.type
                    val 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) in
      switch 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: Int
  var name: String
  var 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';

  @override
  void 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');
    }
  }

  @override
  Widget 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.MethodChannel

class MainActivity: FlutterActivity() {
    // 与Flutter端相同的通道名称
    private val CHANNEL = "com.example.channel/transfer"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 初始化MethodChannel
        val channel = MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL)

        // ...其他代码

        // 用于向Flutter发送数据
        fun sendDataToFlutter() {
            // 使用MethodChannel调用Flutter端定义的方法,并传递数据
            channel.invokeMethod("onDataReturn", "这是来自原生平台的数据")
        }

        // 假设有个触发发送数据到Flutter的逻辑
        sendDataToFlutter()
    }
}

iOS端(Swift)设置返回数据

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

// ...其他导入
import Flutter

class ViewController: UIViewController {
    private var channel: FlutterMethodChannel?

    override func viewDidLoad() {
        super.viewDidLoad()

        // 初始化MethodChannel
        guard 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')),
      );
    }
  }

  @override
  Widget 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.Bundle

class 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.MethodChannel

class 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返回数据到Flutter
            MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("onDataReturn", returnData)
        }
    }

    companion object {
        const val REQUEST_CODE = 100
    }
}

iOS端(Swift)设置返回数据

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

// 原生页面ViewController
import UIKit

class 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 in
      self?.flutterResult?(data)
    }
    // 展示原生ViewController
    controller.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()}";
      });
    }
  }

  @override
  Widget 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.MethodChannel

class 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) {
                // 捕获异常并将错误返回到Flutter
                result.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 {
          // 如果有异常,将异常信息返回给Flutter
          result(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: "演示错误"])
  }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐