编写自定义平台特定代码
本指南介绍如何编写自定义平台特定代码。 某些平台特定功能可通过现有软件包获得; 请参阅使用软件包。
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)和主机(平台)之间传递消息:
为了确保用户界面保持响应,消息和响应是异步传递的。
在客户端,MethodChannel
允许发送与方法调用相对应的消息。在平台端, Android 上的 MethodChannel
(MethodChannelAndroid
) 和 iOS 上的 FlutterMethodChannel
(MethodChanneliOS
) 允许接收方法调用并发送回结果。这些类允许您开发几乎没有“样板”代码的平台插件。
平台通道数据类型支持和编解码器
#标准平台通道使用标准消息编解码器,该编解码器支持对类似 JSON 的简单值的有效二进制序列化,例如布尔值、数字、字符串、字节缓冲区以及这些值的列表和映射(有关详细信息,请参阅StandardMessageCodec
)。当您发送和接收值时,这些值的序列化和反序列化到消息和从消息中发生。
下表显示了 Dart 值如何在平台端接收以及反之亦然:
Dart | Kotlin |
---|---|
null | null |
bool | Boolean |
int (<=32 bits) | Int |
int (>32 bits) | Long |
double | Double |
String | String |
Uint8List | ByteArray |
Int32List | IntArray |
Int64List | LongArray |
Float32List | FloatArray |
Float64List | DoubleArray |
List | List |
Map | HashMap |
Dart | Java |
---|---|
null | null |
bool | java.lang.Boolean |
int (<=32 bits) | java.lang.Integer |
int (>32 bits) | java.lang.Long |
double | java.lang.Double |
String | java.lang.String |
Uint8List | byte[] |
Int32List | int[] |
Int64List | long[] |
Float32List | float[] |
Float64List | double[] |
List | java.util.ArrayList |
Map | java.util.HashMap |
Dart | Swift |
---|---|
null | nil (NSNull when nested) |
bool | NSNumber(value: Bool) |
int (<=32 bits) | NSNumber(value: Int32) |
int (>32 bits) | NSNumber(value: Int) |
double | NSNumber(value: Double) |
String | String |
Uint8List | FlutterStandardTypedData(bytes: Data) |
Int32List | FlutterStandardTypedData(int32: Data) |
Int64List | FlutterStandardTypedData(int64: Data) |
Float32List | FlutterStandardTypedData(float32: Data) |
Float64List | FlutterStandardTypedData(float64: Data) |
List | Array |
Map | Dictionary |
Dart | Objective-C |
---|---|
null | nil (NSNull when nested) |
bool | NSNumber numberWithBool: |
int (<=32 bits) | NSNumber numberWithInt: |
int (>32 bits) | NSNumber numberWithLong: |
double | NSNumber numberWithDouble: |
String | NSString |
Uint8List | FlutterStandardTypedData typedDataWithBytes: |
Int32List | FlutterStandardTypedData typedDataWithInt32: |
Int64List | FlutterStandardTypedData typedDataWithInt64: |
Float32List | FlutterStandardTypedData typedDataWithFloat32: |
Float64List | FlutterStandardTypedData typedDataWithFloat64: |
List | NSArray |
Map | NSDictionary |
Dart | C++ |
---|---|
null | EncodableValue() |
bool | EncodableValue(bool) |
int (<=32 bits) | EncodableValue(int32_t) |
int (>32 bits) | EncodableValue(int64_t) |
double | EncodableValue(double) |
String | EncodableValue(std::string) |
Uint8List | EncodableValue(std::vector<uint8_t>) |
Int32List | EncodableValue(std::vector<int32_t>) |
Int64List | EncodableValue(std::vector<int64_t>) |
Float32List | EncodableValue(std::vector<float>) |
Float64List | EncodableValue(std::vector<double>) |
List | EncodableValue(std::vector<EncodableValue>) |
Map | EncodableValue(std::map<EncodableValue, EncodableValue>) |
Dart | C (GObject) |
---|---|
null | FlValue() |
bool | FlValue(bool) |
int | FlValue(int64_t) |
double | FlValue(double) |
String | FlValue(gchar*) |
Uint8List | FlValue(uint8_t*) |
Int32List | FlValue(int32_t*) |
Int64List | FlValue(int64_t*) |
Float32List | FlValue(float*) |
Float64List | FlValue(double*) |
List | FlValue(FlValue) |
Map | FlValue(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
。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
// 获取电池电量。
接下来,在方法通道上调用一个方法, 使用 String
标识符 getBatteryLevel
指定要调用的具体方法。 调用可能会失败——例如, 如果平台不支持 平台 API(例如在模拟器中运行时), 因此将 invokeMethod
调用包装在 try-catch 语句中。
使用返回的结果来更新 setState
内 _batteryLevel
中的用户界面状态。
// 获取电池电量。
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
方法, 使其包含一个小的用户界面,该界面以字符串形式显示电池 状态,以及一个用于刷新值的按钮。
@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 主机部分:
启动 Android Studio
选择菜单项 文件 > 打开...
导航到包含 Flutter 应用的目录, 并在其中选择 android 文件夹。单击 确定。
在项目视图的 kotlin 文件夹中打开位于 kotlin 文件夹中的
MainActivity.kt
文件。
在 configureFlutterEngine()
方法中,创建一个 MethodChannel
并调用 setMethodCallHandler()
。确保使用与 Flutter 客户端中使用的相同的通道名称。
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 应用中编写的代码完全相同。
首先,在文件顶部添加所需的导入:
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()
方法下方:
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
参数返回成功和错误情况的响应。 如果调用了未知方法,则改为报告该方法。
删除以下代码:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
并替换为以下内容:
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 主机部分:
启动 Android Studio
选择菜单项 文件 > 打开...
导航到包含 Flutter 应用的目录, 并在其中选择 android 文件夹。单击 确定。
在项目视图的 java 文件夹中打开
MainActivity.java
文件。
接下来,在 configureFlutterEngine()
方法中创建一个 MethodChannel
并设置 MethodCallHandler
。 确保使用与 Flutter 客户端中使用的相同的通道名称。
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 应用中编写的代码完全相同。
首先,在文件顶部添加所需的导入:
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()
方法下方:
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
参数返回成功和错误情况的响应。 如果调用了未知方法,则改为报告该方法。
删除以下代码:
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// This method is invoked on the main thread.
// TODO
}
);
并替换为以下内容:
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 主机部分:
启动 Xcode。
选择菜单项 文件 > 打开...。
导航到包含 Flutter 应用的目录,然后选择其内部的 ios 文件夹。单击 确定。
在使用 Objective-C 的标准模板设置中添加对 Swift 的支持:
在项目导航器中展开 Runner > Runner。
在项目导航器中打开位于 Runner > Runner 下的
AppDelegate.swift
文件。
覆盖 application:didFinishLaunchingWithOptions:
函数并创建一个与通道名称 samples.flutter.dev/battery
绑定的 FlutterMethodChannel
:
@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
的底部添加以下内容作为新方法:
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 代码。如果调用了未知方法,则改为报告该方法。
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 主机部分:
启动 Xcode。
选择菜单项 文件 > 打开...。
导航到包含 Flutter 应用的目录, 然后选择其内部的 ios 文件夹。单击 确定。
确保 Xcode 项目构建时没有错误。
在项目导航器中打开位于 Runner > Runner 下的
AppDelegate.m
文件。
创建一个 FlutterMethodChannel
,并在 application didFinishLaunchingWithOptions:
方法中添加一个处理程序。 确保使用与 Flutter 客户端中使用的相同的通道名称。
#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
之前添加以下方法:
- (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
参数返回成功和错误情况的响应。如果调用了未知方法,则改为报告该方法。
__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 主机部分:
在项目目录中运行一次
flutter build windows
以生成 Visual Studio 解决方案文件。启动 Visual Studio。
选择 打开项目或解决方案。
导航到包含 Flutter 应用的目录,然后进入 build 文件夹,然后进入 windows 文件夹,然后选择
batterylevel.sln
文件。 单击 打开。
添加平台通道方法的 C++ 实现:
在解决方案资源管理器中展开 batterylevel > 源文件。
打开
flutter_window.cpp
文件。
首先,在文件顶部添加必要的包含,就在 #include "flutter_window.h"
之后:
#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
:
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
部分之后,添加以下内容作为新函数:
static int GetBatteryLevel() {
SYSTEM_POWER_STATUS status;
if (GetSystemPowerStatus(&status) == 0 || status.BatteryLifePercent == 255) {
return -1;
}
return status.BatteryLifePercent;
}
最后,完成前面添加的 setMethodCallHandler()
方法。 您需要处理单个平台方法 getBatteryLevel()
, 因此在 call
参数中对其进行测试。 此平台方法的实现调用 前面步骤中编写的 Windows 代码。如果调用了未知方法,则改为报告该方法。
删除以下代码:
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
// TODO
});
并替换为以下内容:
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 主机部分:
启动 Xcode。
选择菜单项 文件 > 打开...。
导航到包含 Flutter 应用的目录,然后选择其内部的 macos 文件夹。单击 确定。
添加平台通道方法的 Swift 实现:
在项目导航器中展开 Runner > Runner。
在项目导航器中打开位于 Runner > Runner 下的
MainFlutterWindow.swift
文件。
首先,在文件顶部添加必要的导入,就在 import FlutterMacOS
之后:
import IOKit.ps
在 awakeFromNib
方法中创建一个与通道名称 samples.flutter.dev/battery
绑定的 FlutterMethodChannel
:
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
的底部添加以下内容作为新方法:
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 代码。如果调用了未知方法,则改为报告该方法。
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 进行调整。
启动 Visual Studio Code。
打开项目内部的 linux 目录。
在询问:
是否要配置项目“linux”?
的提示中选择 是。 这将启用 C++ 自动完成。打开
runner/my_application.cc
文件。
首先,在文件顶部添加必要的包含,就在 #include <flutter_linux/flutter_linux.h>
之后:
#include <math.h>
#include <upower.h>
向 _MyApplication
结构体添加一个 FlMethodChannel
:
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
FlMethodChannel* battery_channel;
};
确保在 my_application_dispose
中清理它:
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
之后:
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
行之后,添加以下内容作为新函数:
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
函数之后添加以下代码:
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 文件:
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 使用:
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
,它支持使用自定义消息编解码器的基本 异步消息传递。您还可以使用专门的 BinaryCodec
、 StringCodec
和 JSONMessageCodec
类,或创建您自己的编解码器。
您还可以查看cloud_firestore
插件中的自定义编解码器示例, 该插件能够序列化和反序列化比默认类型更多的类型。
通道和平台线程
#当在平台端调用目标为 Flutter 的通道时, 请在平台的主线程上调用它们。 当在 Flutter 中调用目标为平台端的通道时, 请从任何作为根 Isolate
的 Isolate
或注册为根 Isolate
的后台 Isolate
调用它们。 平台端的处理程序可以在平台的主线程上执行, 或者如果使用任务队列,则可以在后台线程上执行。 您可以异步地在任何线程上调用平台端的处理程序。
从后台 Isolate 使用插件和通道
#任何 Isolate
都可以使用插件和通道,但该 Isolate
必须是 根 Isolate
(由 Flutter 创建)或注册为根 Isolate
的后台 Isolate
。
以下示例演示如何注册后台 Isolate
以便 从后台 Isolate
使用插件。
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 中:
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);
}
在 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 中:
+ (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 中:
Handler(Looper.getMainLooper()).post {
// 在此处调用所需的通道消息。
}
在 Java 中:
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// 在此处调用所需的通道消息。
}
});
在 iOS 中跳转到主线程
#为了符合通道的主线程要求, 您可能需要从后台线程跳转到 iOS 的主线程来执行通道方法。 您可以在 iOS 中通过在主调度队列 上执行 块 来实现此目的:
在 Objective-C 中:
dispatch_async(dispatch_get_main_queue(), ^{
// 在此处调用所需的通道消息。
});
在 Swift 中:
DispatchQueue.main.async {
// 在此处调用所需的通道消息。
}
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。