Skip to main content

编写自定义平台特定代码

本指南介绍如何编写自定义平台特定代码。 某些平台特定功能可通过现有软件包获得; 请参阅使用软件包

Flutter 使用灵活的系统,允许您使用直接与这些 API 协同工作的语言调用平台特定 API:

  • Android 上的 Kotlin 或 Java
  • iOS 上的 Swift 或 Objective-C
  • Windows 上的 C++
  • macOS 上的 Objective-C
  • Linux 上的 C

Flutter 内置的平台特定 API 支持 不依赖于代码生成, 而是依赖于灵活的消息传递方式。 或者,您可以使用Pigeon 软件包用于发送结构化类型安全消息 并进行代码生成:

  • 应用的 Flutter 部分通过平台通道向其 主机(应用的非 Dart 部分)发送消息。

  • 主机 在平台通道上监听并接收消息。 然后,它调用任意数量的平台特定 API(使用本机编程语言),并将响应发送回 客户端(应用的 Flutter 部分)。

架构概述:平台通道

#

如下图所示,使用平台通道在客户端(UI)和主机(平台)之间传递消息:

Platform channels architecture

为了确保用户界面保持响应,消息和响应是异步传递的。

在客户端,MethodChannel 允许发送与方法调用相对应的消息。在平台端, Android 上的 MethodChannel (MethodChannelAndroid) 和 iOS 上的 FlutterMethodChannel (MethodChanneliOS) 允许接收方法调用并发送回结果。这些类允许您开发几乎没有“样板”代码的平台插件。

平台通道数据类型支持和编解码器

#

标准平台通道使用标准消息编解码器,该编解码器支持对类似 JSON 的简单值的有效二进制序列化,例如布尔值、数字、字符串、字节缓冲区以及这些值的列表和映射(有关详细信息,请参阅StandardMessageCodec)。当您发送和接收值时,这些值的序列化和反序列化到消息和从消息中发生。

下表显示了 Dart 值如何在平台端接收以及反之亦然:

DartKotlin
nullnull
boolBoolean
int (<=32 bits)Int
int (>32 bits)Long
doubleDouble
StringString
Uint8ListByteArray
Int32ListIntArray
Int64ListLongArray
Float32ListFloatArray
Float64ListDoubleArray
ListList
MapHashMap
DartJava
nullnull
booljava.lang.Boolean
int (<=32 bits)java.lang.Integer
int (>32 bits)java.lang.Long
doublejava.lang.Double
Stringjava.lang.String
Uint8Listbyte[]
Int32Listint[]
Int64Listlong[]
Float32Listfloat[]
Float64Listdouble[]
Listjava.util.ArrayList
Mapjava.util.HashMap
DartSwift
nullnil (NSNull when nested)
boolNSNumber(value: Bool)
int (<=32 bits)NSNumber(value: Int32)
int (>32 bits)NSNumber(value: Int)
doubleNSNumber(value: Double)
StringString
Uint8ListFlutterStandardTypedData(bytes: Data)
Int32ListFlutterStandardTypedData(int32: Data)
Int64ListFlutterStandardTypedData(int64: Data)
Float32ListFlutterStandardTypedData(float32: Data)
Float64ListFlutterStandardTypedData(float64: Data)
ListArray
MapDictionary
DartObjective-C
nullnil (NSNull when nested)
boolNSNumber numberWithBool:
int (<=32 bits)NSNumber numberWithInt:
int (>32 bits)NSNumber numberWithLong:
doubleNSNumber numberWithDouble:
StringNSString
Uint8ListFlutterStandardTypedData typedDataWithBytes:
Int32ListFlutterStandardTypedData typedDataWithInt32:
Int64ListFlutterStandardTypedData typedDataWithInt64:
Float32ListFlutterStandardTypedData typedDataWithFloat32:
Float64ListFlutterStandardTypedData typedDataWithFloat64:
ListNSArray
MapNSDictionary
DartC++
nullEncodableValue()
boolEncodableValue(bool)
int (<=32 bits)EncodableValue(int32_t)
int (>32 bits)EncodableValue(int64_t)
doubleEncodableValue(double)
StringEncodableValue(std::string)
Uint8ListEncodableValue(std::vector<uint8_t>)
Int32ListEncodableValue(std::vector<int32_t>)
Int64ListEncodableValue(std::vector<int64_t>)
Float32ListEncodableValue(std::vector<float>)
Float64ListEncodableValue(std::vector<double>)
ListEncodableValue(std::vector<EncodableValue>)
MapEncodableValue(std::map<EncodableValue, EncodableValue>)
DartC (GObject)
nullFlValue()
boolFlValue(bool)
intFlValue(int64_t)
doubleFlValue(double)
StringFlValue(gchar*)
Uint8ListFlValue(uint8_t*)
Int32ListFlValue(int32_t*)
Int64ListFlValue(int64_t*)
Float32ListFlValue(float*)
Float64ListFlValue(double*)
ListFlValue(FlValue)
MapFlValue(FlValue, FlValue)

示例:使用平台通道调用平台特定代码

#

以下代码演示如何调用 平台特定 API 来检索和显示 当前电池电量。它使用 Android 的 BatteryManager API、 iOS 的 device.batteryLevel API、 Windows 的 GetSystemPowerStatus API 以及 Linux 的 UPower API,并使用单个 平台消息 getBatteryLevel()

本示例在主应用程序本身内添加了平台特定的代码。如果您想将平台特定的代码重复用于多个应用程序, 项目创建步骤略有不同 (请参阅开发软件包), 但平台通道代码 仍然以相同的方式编写。

步骤 1:创建新的应用程序项目

#

首先创建一个新的应用程序:

  • 在终端中运行:flutter create batterylevel

默认情况下,我们的模板支持使用 Kotlin 编写 Android 代码, 或使用 Swift 编写 iOS 代码。要使用 Java 或 Objective-C, 请使用 -i 和/或 -a 标志:

  • 在终端中运行:flutter create -i objc -a java batterylevel

步骤 2:创建 Flutter 平台客户端

#

应用程序的 State 类保存当前应用程序状态。 将其扩展为保存当前电池状态。

首先,构造通道。使用具有单个返回电池电量平台方法的 MethodChannel

通道的客户端和主机端通过在通道构造函数中传递的通道名称连接。单个应用程序中使用的所有通道名称必须 唯一;例如,使用唯一的“域前缀”作为通道名称的前缀:samples.flutter.dev/battery

dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
dart
class _MyHomePageState extends State<MyHomePage> {
  static const platform = MethodChannel('samples.flutter.dev/battery');
  // 获取电池电量。

接下来,在方法通道上调用一个方法, 使用 String 标识符 getBatteryLevel 指定要调用的具体方法。 调用可能会失败——例如, 如果平台不支持 平台 API(例如在模拟器中运行时), 因此将 invokeMethod 调用包装在 try-catch 语句中。

使用返回的结果来更新 setState_batteryLevel 中的用户界面状态。

dart
// 获取电池电量。
String _batteryLevel = '未知电池电量。';

Future<void> _getBatteryLevel() async {
  String batteryLevel;
  try {
    final result = await platform.invokeMethod<int>('getBatteryLevel');
    batteryLevel = '电池电量为 $result %。';
  } on PlatformException catch (e) {
    batteryLevel = "未能获取电池电量:'${e.message}'。";
  }

  setState(() {
    _batteryLevel = batteryLevel;
  });
}

最后,替换模板中的 build 方法, 使其包含一个小的用户界面,该界面以字符串形式显示电池 状态,以及一个用于刷新值的按钮。

dart
@override
Widget build(BuildContext context) {
  return Material(
    child: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          ElevatedButton(
            onPressed: _getBatteryLevel,
            child: const Text('获取电池电量'),
          ),
          Text(_batteryLevel),
        ],
      ),
    ),
  );
}

步骤 3:添加 Android 平台特定实现

#

首先在 Android Studio 中打开 Flutter 应用的 Android 主机部分:

  1. 启动 Android Studio

  2. 选择菜单项 文件 > 打开...

  3. 导航到包含 Flutter 应用的目录, 并在其中选择 android 文件夹。单击 确定

  4. 在项目视图的 kotlin 文件夹中打开位于 kotlin 文件夹中的 MainActivity.kt 文件。

configureFlutterEngine() 方法中,创建一个 MethodChannel 并调用 setMethodCallHandler()。确保使用与 Flutter 客户端中使用的相同的通道名称。

MainActivity.kt
kotlin
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
  private val CHANNEL = "samples.flutter.dev/battery"

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // 此方法在主线程上调用。
      // TODO
    }
  }
}

添加使用 Android 电池 API 检索电池电量的 Android Kotlin 代码。此代码与您 在原生 Android 应用中编写的代码完全相同。

首先,在文件顶部添加所需的导入:

MainActivity.kt
kotlin
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES

接下来,在 MainActivity 类中添加以下方法, 位于 configureFlutterEngine() 方法下方:

MainActivity.kt
kotlin
  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
  }

最后,完成前面添加的 setMethodCallHandler() 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。 此平台方法的实现调用 前面步骤中编写的 Android 代码,并使用 result 参数返回成功和错误情况的响应。 如果调用了未知方法,则改为报告该方法。

删除以下代码:

MainActivity.kt
kotlin
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // This method is invoked on the main thread.
      // TODO
    }

并替换为以下内容:

MainActivity.kt
kotlin
    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()
      }
    }

首先在 Android Studio 中打开 Flutter 应用的 Android 主机部分:

  1. 启动 Android Studio

  2. 选择菜单项 文件 > 打开...

  3. 导航到包含 Flutter 应用的目录, 并在其中选择 android 文件夹。单击 确定

  4. 在项目视图的 java 文件夹中打开 MainActivity.java 文件。

接下来,在 configureFlutterEngine() 方法中创建一个 MethodChannel 并设置 MethodCallHandler。 确保使用与 Flutter 客户端中使用的相同的通道名称。

MainActivity.java
java
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "samples.flutter.dev/battery";

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // 此方法在主线程上调用。
            // TODO
          }
        );
  }
}

添加使用 Android 电池 API 检索电池电量的 Android Java 代码。此代码与您 在原生 Android 应用中编写的代码完全相同。

首先,在文件顶部添加所需的导入:

MainActivity.java
java
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;

然后在 activity 类中添加以下内容作为新方法, 位于 configureFlutterEngine() 方法下方:

MainActivity.java
java
  private int getBatteryLevel() {
    int batteryLevel = -1;
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    } else {
      Intent intent = new ContextWrapper(getApplicationContext()).
          registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
          intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }

    return batteryLevel;
  }

最后,完成前面添加的 setMethodCallHandler() 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。此平台方法的实现调用 前面步骤中编写的 Android 代码,并使用 result 参数返回成功和错误情况的响应。 如果调用了未知方法,则改为报告该方法。

删除以下代码:

MainActivity.java
java
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // This method is invoked on the main thread.
            // TODO
          }
      );

并替换为以下内容:

MainActivity.java
java
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // 此方法在主线程上调用。
            if (call.method.equals("getBatteryLevel")) {
              int batteryLevel = getBatteryLevel();

              if (batteryLevel != -1) {
                result.success(batteryLevel);
              } else {
                result.error("UNAVAILABLE", "Battery level not available.", null);
              }
            } else {
              result.notImplemented();
            }
          }
      );

您现在应该能够在 Android 上运行该应用。如果使用 Android 模拟器,请在工具栏中从 ... 按钮访问的扩展控件面板中设置电池电量。

步骤 4:添加 iOS 平台特定实现

#

首先在 Xcode 中打开 Flutter 应用的 iOS 主机部分:

  1. 启动 Xcode。

  2. 选择菜单项 文件 > 打开...

  3. 导航到包含 Flutter 应用的目录,然后选择其内部的 ios 文件夹。单击 确定

在使用 Objective-C 的标准模板设置中添加对 Swift 的支持:

  1. 在项目导航器中展开 Runner > Runner

  2. 在项目导航器中打开位于 Runner > Runner 下的 AppDelegate.swift 文件。

覆盖 application:didFinishLaunchingWithOptions: 函数并创建一个与通道名称 samples.flutter.dev/battery 绑定的 FlutterMethodChannel

AppDelegate.swift
swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // 此方法在 UI 线程上调用。
      // 处理电池消息。
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

接下来,添加使用 iOS 电池 API 检索电池电量的 iOS Swift 代码。此代码与您 在原生 iOS 应用中编写的代码完全相同。

AppDelegate.swift 的底部添加以下内容作为新方法:

AppDelegate.swift
swift
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))
  }
}

最后,完成前面添加的 setMethodCallHandler() 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。 此平台方法的实现调用 前面步骤中编写的 iOS 代码。如果调用了未知方法,则改为报告该方法。

AppDelegate.swift
swift
batteryChannel.setMethodCallHandler({
  [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
  // This method is invoked on the UI thread.
  guard call.method == "getBatteryLevel" else {
    result(FlutterMethodNotImplemented)
    return
  }
  self?.receiveBatteryLevel(result: result)
})

首先在 Xcode 中打开 Flutter 应用的 iOS 主机部分:

  1. 启动 Xcode。

  2. 选择菜单项 文件 > 打开...

  3. 导航到包含 Flutter 应用的目录, 然后选择其内部的 ios 文件夹。单击 确定

  4. 确保 Xcode 项目构建时没有错误。

  5. 在项目导航器中打开位于 Runner > Runner 下的 AppDelegate.m 文件。

创建一个 FlutterMethodChannel,并在 application didFinishLaunchingWithOptions: 方法中添加一个处理程序。 确保使用与 Flutter 客户端中使用的相同的通道名称。

AppDelegate.m
objc
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

  FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
                                          methodChannelWithName:@"samples.flutter.dev/battery"
                                          binaryMessenger:controller.binaryMessenger];

  [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    // 此方法在 UI 线程上调用。
    // TODO
  }];

  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

接下来,添加使用 iOS 电池 API 检索电池电量的 iOS ObjectiveC 代码。此代码与您 在原生 iOS 应用中编写的代码完全相同。

AppDelegate 类中,就在 @end 之前添加以下方法:

AppDelegate.m
objc
- (int)getBatteryLevel {
  UIDevice* device = UIDevice.currentDevice;
  device.batteryMonitoringEnabled = YES;
  if (device.batteryState == UIDeviceBatteryStateUnknown) {
    return -1;
  } else {
    return (int)(device.batteryLevel * 100);
  }
}

最后,完成前面添加的 setMethodCallHandler() 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。此平台方法的实现调用 前面步骤中编写的 iOS 代码,并使用 result 参数返回成功和错误情况的响应。如果调用了未知方法,则改为报告该方法。

AppDelegate.m
objc
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
  // 此方法在 UI 线程上调用。
  if ([@"getBatteryLevel" isEqualToString:call.method]) {
    int batteryLevel = [weakSelf getBatteryLevel];

    if (batteryLevel == -1) {
      result([FlutterError errorWithCode:@"UNAVAILABLE"
                                 message:@"Battery level not available."
                                 details:nil]);
    } else {
      result(@(batteryLevel));
    }
  } else {
    result(FlutterMethodNotImplemented);
  }
}];

您现在应该能够在 iOS 上运行该应用。 如果使用 iOS 模拟器, 请注意它不支持电池 API, 并且应用会显示“电池电量不可用”。

步骤 5:添加 Windows 平台特定实现

#

首先在 Visual Studio 中打开 Flutter 应用的 Windows 主机部分:

  1. 在项目目录中运行一次 flutter build windows 以生成 Visual Studio 解决方案文件。

  2. 启动 Visual Studio。

  3. 选择 打开项目或解决方案

  4. 导航到包含 Flutter 应用的目录,然后进入 build 文件夹,然后进入 windows 文件夹,然后选择 batterylevel.sln 文件。 单击 打开

添加平台通道方法的 C++ 实现:

  1. 在解决方案资源管理器中展开 batterylevel > 源文件

  2. 打开 flutter_window.cpp 文件。

首先,在文件顶部添加必要的包含,就在 #include "flutter_window.h" 之后:

flutter_window.cpp
cpp
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>

#include <memory>

编辑 FlutterWindow::OnCreate 方法并创建一个与通道名称 samples.flutter.dev/battery 绑定的 flutter::MethodChannel

flutter_window.cpp
cpp
bool FlutterWindow::OnCreate() {
  // ...
  RegisterPlugins(flutter_controller_->engine());

  flutter::MethodChannel<> channel(
      flutter_controller_->engine()->messenger(), "samples.flutter.dev/battery",
      &flutter::StandardMethodCodec::GetInstance());
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        // TODO
      });

  SetChildContent(flutter_controller_->view()->GetNativeWindow());
  return true;
}

接下来,添加使用 Windows 电池 API 检索电池电量的 C++ 代码。此代码与您 在原生 Windows 应用程序中编写的代码完全相同。

flutter_window.cpp 的顶部,#include 部分之后,添加以下内容作为新函数:

flutter_window.cpp
cpp
static int GetBatteryLevel() {
  SYSTEM_POWER_STATUS status;
  if (GetSystemPowerStatus(&status) == 0 || status.BatteryLifePercent == 255) {
    return -1;
  }
  return status.BatteryLifePercent;
}

最后,完成前面添加的 setMethodCallHandler() 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。 此平台方法的实现调用 前面步骤中编写的 Windows 代码。如果调用了未知方法,则改为报告该方法。

删除以下代码:

flutter_window.cpp
cpp
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        // TODO
      });

并替换为以下内容:

flutter_window.cpp
cpp
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        if (call.method_name() == "getBatteryLevel") {
          int battery_level = GetBatteryLevel();
          if (battery_level != -1) {
            result->Success(battery_level);
          } else {
            result->Error("UNAVAILABLE", "Battery level not available.");
          }
        } else {
          result->NotImplemented();
        }
      });

您现在应该能够在 Windows 上运行该应用程序。 如果您的设备没有电池, 它会显示“电池电量不可用”。

步骤 6:添加 macOS 平台特定实现

#

首先在 Xcode 中打开 Flutter 应用的 macOS 主机部分:

  1. 启动 Xcode。

  2. 选择菜单项 文件 > 打开...

  3. 导航到包含 Flutter 应用的目录,然后选择其内部的 macos 文件夹。单击 确定

添加平台通道方法的 Swift 实现:

  1. 在项目导航器中展开 Runner > Runner

  2. 在项目导航器中打开位于 Runner > Runner 下的 MainFlutterWindow.swift 文件。

首先,在文件顶部添加必要的导入,就在 import FlutterMacOS 之后:

MainFlutterWindow.swift
swift
import IOKit.ps

awakeFromNib 方法中创建一个与通道名称 samples.flutter.dev/battery 绑定的 FlutterMethodChannel

MainFlutterWindow.swift
swift
  override func awakeFromNib() {
    // ...
    self.setFrame(windowFrame, display: true)
  
    let batteryChannel = FlutterMethodChannel(
      name: "samples.flutter.dev/battery",
      binaryMessenger: flutterViewController.engine.binaryMessenger)
    batteryChannel.setMethodCallHandler { (call, result) in
      // 此方法在 UI 线程上调用。
      // 处理电池消息。
    }

    RegisterGeneratedPlugins(registry: flutterViewController)

    super.awakeFromNib()
  }
}

接下来,添加使用 IOKit 电池 API 检索电池电量的 macOS Swift 代码。此代码与您 在原生 macOS 应用中编写的代码完全相同。

MainFlutterWindow.swift 的底部添加以下内容作为新方法:

MainFlutterWindow.swift
swift
private func getBatteryLevel() -> Int? {
  let info = IOPSCopyPowerSourcesInfo().takeRetainedValue()
  let sources: Array<CFTypeRef> = IOPSCopyPowerSourcesList(info).takeRetainedValue() as Array
  if let source = sources.first {
    let description =
      IOPSGetPowerSourceDescription(info, source).takeUnretainedValue() as! [String: AnyObject]
    if let level = description[kIOPSCurrentCapacityKey] as? Int {
      return level
    }
  }
  return nil
}

最后,完成前面添加的 setMethodCallHandler 方法。 您需要处理单个平台方法 getBatteryLevel(), 因此在 call 参数中对其进行测试。 此平台方法的实现调用 前面步骤中编写的 macOS 代码。如果调用了未知方法,则改为报告该方法。

MainFlutterWindow.swift
swift
batteryChannel.setMethodCallHandler { (call, result) in
  switch call.method {
  case "getBatteryLevel":
    guard let level = getBatteryLevel() else {
      result(
        FlutterError(
          code: "UNAVAILABLE",
          message: "Battery level not available",
          details: nil))
     return
    }
    result(level)
  default:
    result(FlutterMethodNotImplemented)
  }
}

您现在应该能够在 macOS 上运行该应用程序。 如果您的设备没有电池, 它会显示“电池电量不可用”。

步骤 7:添加 Linux 平台特定实现

#

对于此示例,您需要安装 upower 开发人员头文件。 这可能可以通过您的发行版获得,例如:

sudo apt install libupower-glib-dev

首先在您选择的编辑器中打开 Flutter 应用的 Linux 主机部分。以下说明适用于安装了“C/C++”和“CMake”扩展的 Visual Studio Code,但可以针对其他 IDE 进行调整。

  1. 启动 Visual Studio Code。

  2. 打开项目内部的 linux 目录。

  3. 在询问:是否要配置项目“linux”? 的提示中选择 。 这将启用 C++ 自动完成。

  4. 打开 runner/my_application.cc 文件。

首先,在文件顶部添加必要的包含,就在 #include <flutter_linux/flutter_linux.h> 之后:

runner/my_application.cc
c
#include <math.h>
#include <upower.h>

_MyApplication 结构体添加一个 FlMethodChannel

runnner/my_application.cc
c
struct _MyApplication {
  GtkApplication parent_instance;
  char** dart_entrypoint_arguments;
  FlMethodChannel* battery_channel;
};

确保在 my_application_dispose 中清理它:

runner/my_application.cc
c
static void my_application_dispose(GObject* object) {
  MyApplication* self = MY_APPLICATION(object);
  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
  g_clear_object(&self->battery_channel);
  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}

编辑 my_application_activate 方法并使用通道名称 samples.flutter.dev/battery 初始化 battery_channel,就在调用 fl_register_plugins 之后:

runner/my_application.cc
c
static void my_application_activate(GApplication* application) {
  // ...
  fl_register_plugins(FL_PLUGIN_REGISTRY(self->view));

  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  self->battery_channel = fl_method_channel_new(
      fl_engine_get_binary_messenger(fl_view_get_engine(view)),
      "samples.flutter.dev/battery", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(
      self->battery_channel, battery_method_call_handler, self, nullptr);

  gtk_widget_grab_focus(GTK_WIDGET(self->view));
}

接下来,添加使用 Linux 电池 API 检索电池电量的 C 代码。此代码与您 在原生 Linux 应用程序中编写的代码完全相同。

my_application.cc 的顶部,G_DEFINE_TYPE 行之后,添加以下内容作为新函数:

runner/my_application.cc
c
static FlMethodResponse* get_battery_level() {
  // 查找第一个可用的电池并报告该电池。
  g_autoptr(UpClient) up_client = up_client_new();
  g_autoptr(GPtrArray) devices = up_client_get_devices2(up_client);
  if (devices->len == 0) {
    return FL_METHOD_RESPONSE(fl_method_error_response_new(
        "UNAVAILABLE", "Device does not have a battery.", nullptr));
  }

  UpDevice* device = UP_DEVICE(g_ptr_array_index(devices, 0));
  double percentage = 0;
  g_object_get(device, "percentage", &percentage, nullptr);

  g_autoptr(FlValue) result =
      fl_value_new_int(static_cast<int64_t>(round(percentage)));
  return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}

最后,添加前面调用 fl_method_channel_set_method_call_handler 时引用的 battery_method_call_handler 函数。 您需要处理单个平台方法 getBatteryLevel, 因此在 method_call 参数中对其进行测试。 此函数的实现调用 前面步骤中编写的 Linux 代码。如果调用了未知方法,则改为报告该方法。

get_battery_level 函数之后添加以下代码:

runner/my_application.cpp
cpp
static void battery_method_call_handler(FlMethodChannel* channel,
                                        FlMethodCall* method_call,
                                        gpointer user_data) {
  g_autoptr(FlMethodResponse) response = nullptr;
  if (strcmp(fl_method_call_get_name(method_call), "getBatteryLevel") == 0) {
    response = get_battery_level();
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  g_autoptr(GError) error = nullptr;
  if (!fl_method_call_respond(method_call, response, &error)) {
    g_warning("Failed to send response: %s", error->message);
  }
}

您现在应该能够在 Linux 上运行该应用程序。 如果您的设备没有电池, 它会显示“电池电量不可用”。

使用 Pigeon 的类型安全平台通道

#

前面的示例使用 MethodChannel 在主机和客户端之间进行通信, 它不是类型安全的。调用和接收 消息取决于主机和客户端声明 相同的参数和数据类型才能使消息正常工作。 您可以使用 Pigeon 软件包作为 MethodChannel 的替代方法 来生成以结构化、类型安全的方式发送消息的代码。

使用 Pigeon,消息协议是在 Dart 的一个子集中定义的,然后它会为 Android、iOS、macOS 或 Windows 生成消息代码。您可以在pigeon 中找到更完整的示例和更多信息

pub.dev 上的页面。

使用 Pigeon 消除了主机和客户端之间匹配 消息名称和数据类型的字符串的需要。 它支持:嵌套类、将消息分组 到 API 中、生成 异步包装器代码和双向发送消息。生成的代码可读性强, 并保证不同版本的多个客户端之间不会发生冲突。 支持的语言包括 Objective-C、Java、Kotlin、C++ 和 Swift(与 Objective-C 交互操作)。

Pigeon 示例

#

Pigeon 文件:

dart
import 'package:pigeon/pigeon.dart';

class SearchRequest {
  final String query;

  SearchRequest({required this.query});
}

class SearchReply {
  final String result;

  SearchReply({required this.result});
}

@HostApi()
abstract class Api {
  @async
  SearchReply search(SearchRequest request);
}

Dart 使用:

dart
import 'generated_pigeon.dart';

Future<void> onClick() async {
  SearchRequest request = SearchRequest(query: 'test');
  Api api = SomeApi();
  SearchReply reply = await api.search(request);
  print('reply: ${reply.result}');
}

将平台特定代码与 UI 代码分离

#

如果您希望在多个 Flutter 应用中使用平台特定代码, 您可能需要考虑 将代码分离到位于主应用程序目录之外的平台插件中。 有关详细信息,请参阅开发软件包

将平台特定代码发布为软件包

#

要与 Flutter 生态系统中的其他开发者共享您的平台特定代码, 请参阅发布软件包

自定义通道和编解码器

#

除了上面提到的 MethodChannel 之外, 您还可以使用更基本的 BasicMessageChannel,它支持使用自定义消息编解码器的基本 异步消息传递。您还可以使用专门的 BinaryCodecStringCodecJSONMessageCodec 类,或创建您自己的编解码器。

您还可以查看cloud_firestore 插件中的自定义编解码器示例, 该插件能够序列化和反序列化比默认类型更多的类型。

通道和平台线程

#

当在平台端调用目标为 Flutter 的通道时, 请在平台的主线程上调用它们。 当在 Flutter 中调用目标为平台端的通道时, 请从任何作为根 IsolateIsolate 或注册为根 Isolate 的后台 Isolate 调用它们。 平台端的处理程序可以在平台的主线程上执行, 或者如果使用任务队列,则可以在后台线程上执行。 您可以异步地在任何线程上调用平台端的处理程序。

从后台 Isolate 使用插件和通道

#

任何 Isolate 都可以使用插件和通道,但该 Isolate 必须是 根 Isolate(由 Flutter 创建)或注册为根 Isolate 的后台 Isolate

以下示例演示如何注册后台 Isolate 以便 从后台 Isolate 使用插件。

dart
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';

void _isolateMain(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  print(sharedPreferences.getBool('isDebug'));
}

void main() {
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, rootIsolateToken);
}

在后台线程上执行通道处理程序

#

为了使通道的平台端处理程序 在后台线程上执行,您必须使用 任务队列 API。目前,此功能仅 在 iOS 和 Android 上受支持。

在 Kotlin 中:

kotlin
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  val taskQueue =
      flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
  channel = MethodChannel(flutterPluginBinding.binaryMessenger,
                          "com.example.foo",
                          StandardMethodCodec.INSTANCE,
                          taskQueue)
  channel.setMethodCallHandler(this)
}

在 Java 中:

java
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
  BinaryMessenger messenger = binding.getBinaryMessenger();
  BinaryMessenger.TaskQueue taskQueue =
      messenger.makeBackgroundTaskQueue();
  channel =
      new MethodChannel(
          messenger,
          "com.example.foo",
          StandardMethodCodec.INSTANCE,
          taskQueue);
  channel.setMethodCallHandler(this);
}

在 Swift 中:

swift
public static func register(with registrar: FlutterPluginRegistrar) {
  let taskQueue = registrar.messenger.makeBackgroundTaskQueue()
  let channel = FlutterMethodChannel(name: "com.example.foo",
                                     binaryMessenger: registrar.messenger(),
                                     codec: FlutterStandardMethodCodec.sharedInstance,
                                     taskQueue: taskQueue)
  let instance = MyPlugin()
  registrar.addMethodCallDelegate(instance, channel: channel)
}

在 Objective-C 中:

objc
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  NSObject<FlutterTaskQueue>* taskQueue =
      [[registrar messenger] makeBackgroundTaskQueue];
  FlutterMethodChannel* channel =
      [FlutterMethodChannel methodChannelWithName:@"com.example.foo"
                                  binaryMessenger:[registrar messenger]
                                            codec:[FlutterStandardMethodCodec sharedInstance]
                                        taskQueue:taskQueue];
  MyPlugin* instance = [[MyPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

在 Android 中跳转到 UI 线程

#

为了符合通道的 UI 线程要求, 您可能需要从后台线程 跳转到 Android 的 UI 线程来执行通道方法。 在 Android 中,您可以通过将 Runnable post() 到 Android 的 UI 线程 Looper 来实现此目的, 这会导致 Runnable 在下一次机会时在 主线程上执行。

在 Kotlin 中:

kotlin
Handler(Looper.getMainLooper()).post {
  // 在此处调用所需的通道消息。
}

在 Java 中:

java
new Handler(Looper.getMainLooper()).post(new Runnable() {
  @Override
  public void run() {
    // 在此处调用所需的通道消息。
  }
});

在 iOS 中跳转到主线程

#

为了符合通道的主线程要求, 您可能需要从后台线程跳转到 iOS 的主线程来执行通道方法。 您可以在 iOS 中通过在主调度队列 上执行 来实现此目的:

在 Objective-C 中:

objc
dispatch_async(dispatch_get_main_queue(), ^{
  // 在此处调用所需的通道消息。
});

在 Swift 中:

swift
DispatchQueue.main.async {
  // 在此处调用所需的通道消息。
}