跳到主内容

编写自定义平台特定代码

了解如何在您的应用程序中编写自定义平台特定代码。

本指南介绍了如何在 Flutter 中使用自定义平台特定代码。

概述

#

您可以在 Flutter 应用程序中使用平台特定代码。这样做的一些常见方法包括

Flutter 支持以下平台和平台特定语言

  • Android:Kotlin、Java
  • iOS:Swift、Objective-C
  • Windows:C++
  • macOS:Objective-C
  • Linux:C

平台通道的架构概述

#

消息通过平台通道在客户端(UI)和宿主(平台)之间传递,如图所示

Platform channels architecture

在前面的图中,消息和响应通过通道异步传递,以确保用户界面保持响应。在客户端,MethodChannel for Flutter 允许发送对应于方法调用的消息。在平台端,MethodChannel for AndroidFlutterMethodChannel for iOS 允许接收方法调用并使用结果进行回复。这些类允许您使用很少的 样板代码 开发平台插件。

数据类型支持

#

标准的平台通道 API 和 Pigeon 包使用一种标准的名为 StandardMessageCodec 的消息编解码器,该编解码器支持对简单 JSON 样值的二进制序列化,例如布尔值、数字、字符串、字节缓冲区、列表和映射。当您发送和接收值时,这些值的序列化和反序列化会发生自动。

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

DartKotlin
nullnull
boolBoolean
int (<=32 位)Int
int (>32 位)Long
doubleDouble
StringString
Uint8ListByteArray
Int32ListIntArray
Int64ListLongArray
Float32ListFloatArray
Float64ListDoubleArray
ListList
MapHashMap
DartJava
nullnull
booljava.lang.Boolean
int (<=32 位)java.lang.Integer
int (>32 位)java.lang.Long
doublejava.lang.Double
Stringjava.lang.String
Uint8Listbyte[]
Int32Listint[]
Int64Listlong[]
Float32Listfloat[]
Float64Listdouble[]
Listjava.util.ArrayList
Mapjava.util.HashMap
DartSwift
nullnil (嵌套时为 NSNull)
boolNSNumber(value: Bool)
int (<=32 位)NSNumber(value: Int32)
int (>32 位)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)
boolNSNumber numberWithBool
int (<=32 位)NSNumber numberWithInt
int (>32 位)NSNumber numberWithLong
doubleNSNumber numberWithDouble
StringNSString
Uint8ListFlutterStandardTypedData typedDataWithBytes
Int32ListFlutterStandardTypedData typedDataWithInt32
Int64ListFlutterStandardTypedData typedDataWithInt64
Float32List FlutterStandardTypedData typedDataWithFloat32
Float64List FlutterStandardTypedData typedDataWithFloat64
ListNSArray
MapNSDictionary
DartC++
nullEncodableValue()
boolEncodableValue(bool)
int (<=32 位)EncodableValue(int32_t)
int (>32 位)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>)
Map EncodableValue(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');
  // Get battery level.

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

使用返回的结果在 setState 内部的 _batteryLevel 中更新用户界面状态。

dart
// Get battery level.
String _batteryLevel = 'Unknown battery level.';

Future<void> _getBatteryLevel() async {
  String batteryLevel;
  try {
    final result = await platform.invokeMethod<int>('getBatteryLevel');
    batteryLevel = 'Battery level at $result % .';
  } on PlatformException catch (e) {
    batteryLevel = "Failed to get battery level: '${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('Get Battery Level'),
          ),
          Text(_batteryLevel),
        ],
      ),
    ),
  );
}

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

#

首先,在 Android Studio 中打开 Flutter 应用程序的 Android 宿主部分

  1. 启动 Android Studio。

  2. 选择菜单项 File > Open...

  3. 导航到包含 Flutter 应用程序的目录,然后选择其中的 android 文件夹。单击 OK

  4. 在项目视图中,打开位于 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 ->
      // This method is invoked on the main thread.
      // TODO
    }
  }
}

添加 Android Kotlin 代码,该代码使用 Android 电池 API 检索电池电量。此代码与您在原生 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 {
      // This method is invoked on the main thread.
      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. 选择菜单项 File > Open...

  3. 导航到包含 Flutter 应用程序的目录,然后选择其中的 android 文件夹。单击 OK

  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) -> {
            // This method is invoked on the main thread.
            // TODO
          }
        );
  }
}

添加 Android Java 代码,该代码使用 Android 电池 API 检索电池电量。此代码与您在原生 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;

然后将以下内容添加为活动类中的新方法,位于 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) -> {
            // This method is invoked on the main thread.
            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. 选择菜单项 File > Open...

  3. 导航到包含 Flutter 应用程序的目录,然后选择其中的 ios 文件夹。单击 OK

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

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

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

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

AppDelegate.swift
swift
@main
@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
      // This method is invoked on the UI thread.
      // Handle battery messages.
    })

    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. 选择菜单项 File > Open...

  3. 导航到包含 Flutter 应用程序的目录,然后选择其中的 ios 文件夹。单击 OK

  4. 确保 Xcode 项目可以成功构建,没有错误。

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

创建一个 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) {
    // This method is invoked on the UI thread.
    // 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) {
  // This method is invoked on the UI thread.
  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. 选择菜单项 File > Open...

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

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

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

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

首先,在文件的顶部添加必要的导入项,紧接 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
      // This method is invoked on the UI thread.
      // Handle battery messages.
    }

    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>

FlMethodChannel 添加到 _MyApplication 结构体

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 方法,并在调用 fl_register_plugins 之后,使用通道名称 samples.flutter.dev/battery 初始化 battery_channel

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() {
  // Find the first available battery and report that.
  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 包调用平台特定代码

#

你可以使用 Pigeon 包作为 Flutter 的平台通道 API 的替代方案,以生成以结构化、类型安全的方式发送消息的代码。Pigeon 的工作流程如下

  • Flutter 应用通过平台通道将结构化的类型安全消息发送到其 主机,即应用的非 Dart 部分。

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

使用此包消除了在主机和客户端之间匹配消息名称和数据类型的字符串的需要。它支持嵌套类、将消息分组到 API 中、生成异步包装器代码以及双向发送消息。生成的代码可读性强,并保证不同版本的多个客户端之间不会发生冲突。

使用 Pigeon,消息传递协议定义在 Dart 的一个子集中,然后生成 Android、iOS、macOS 或 Windows 的消息传递代码。例如

pigeon_source.dart
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);
}
use_pigeon.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}');
}

你可以在 pub.dev 上的 pigeon 页面上找到完整的示例和更多信息。

通道和平台线程

#

在调用目的地为 Flutter 的平台侧通道时,在平台的 main 线程上调用它们。在 Flutter 中调用目的地为平台侧的通道时,要么从是根 Isolate 的任何 Isolate 调用它们,或者从注册为后台 IsolateIsolate 调用它们。平台侧的处理程序可以在平台的 main 线程上执行,或者如果使用任务队列,可以在后台线程上执行。你可以异步地在任何线程上调用平台侧的处理程序。

从后台隔离区使用插件和通道

#

插件和通道可以由任何 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);
}

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

#

为了使 Android 应用上的平台侧处理程序在后台线程上执行,你必须使用任务队列 API。

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

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

#

为了使 iOS 应用上的平台侧处理程序在后台线程上执行,你必须使用任务队列 API。

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)
}
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];
}

跳转到 UI 线程 (Android)

#

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

kotlin
Handler(Looper.getMainLooper()).post {
  // Call the desired channel message here.
}
java
new Handler(Looper.getMainLooper()).post(new Runnable() {
  @Override
  public void run() {
    // Call the desired channel message here.
  }
});

跳转到主线程 (iOS)

#

为了符合通道的 main 线程要求,你可能需要从后台线程跳转到 iOS 的 main 线程来执行通道方法。你可以在 iOS 中通过在 main block 上执行 dispatch queue 来实现此目的

objc
dispatch_async(dispatch_get_main_queue(), ^{
  // Call the desired channel message here.
});
swift
DispatchQueue.main.async {
  // Call the desired channel message here.
}

补充材料

#

常用通道和编解码器

#

以下是一些你可以用来编写平台特定代码的常见平台通道 API 列表

  • MethodChannel for Flutter:一个命名通道,您可以使用它通过异步方法调用与平台插件进行通信。默认情况下,此通道使用 StandardMessageCodec 编码器。此通道不是类型安全的,这意味着调用和接收消息取决于主机和客户端声明相同的参数和数据类型,以便消息能够正常工作。

  • BasicMessageChannel for Flutter:一个命名通道,支持基本的异步消息传递,使用受支持的消息编码器。不是类型安全的。

  • Engine Embedder APIs for Platforms:这些特定于平台的 API 包含特定于平台的通道 API。

您可以创建自己的编码器或使用现有的编码器。以下是一些您可以与特定于平台的代码一起使用的现有编码器列表

  • StandardMessageCodec:一种常用的消息编码器,它将各种数据类型编码和解码为平台无关的二进制格式,以便在平台通道上传输。在发送和接收值时,值到消息的序列化和反序列化会自动发生。有关支持的数据类型列表,请参阅 平台通道数据类型支持

  • BinaryCodec:一种消息编码器,它在 Flutter 应用程序的 Dart 端和本机平台端之间传递原始二进制数据。它不执行任何更高层次的数据结构编码或解码。

  • StringCodec:一种消息编码器,它使用 UTF-8 编码对字符串进行编码和解码。

  • JSONMessageCodec:一种消息编码器,它使用 UTF-8 编码对 JSON 格式的数据进行编码和解码。

  • FirestoreMessageCodec:一种消息编码器,用于处理在 Flutter 应用程序和本机 Firebase Firestore SDK(在 Android 和 iOS 上)之间通过平台通道发送的消息的交换。

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

#

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

将平台特定代码发布为包

#

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