编写自定义平台特定代码
本指南介绍了如何在 Flutter 中使用自定义平台特定代码。
概述
#你可以在 Flutter 应用中使用平台特定代码。几种常见的方法包括:
使用 Flutter 的平台通道 API 在 Flutter 和目标平台之间传递消息。更多信息请参阅使用平台通道调用平台特定代码。
使用
Pigeon
包生成类型安全的平台特定代码。更多信息请参阅使用 Pigeon 包调用平台特定代码。
Flutter 支持以下平台和平台特定语言:
- Android: Kotlin, Java
- iOS: Swift, Objective-C
- Windows: C++
- macOS: Objective-C
- Linux: C
平台通道的架构概览
#消息通过平台通道在客户端(UI)和宿主(平台)之间传递,如下图所示:
在上图中,消息和响应通过通道异步传递,以确保用户界面保持响应。在客户端,Flutter 的 MethodChannel
允许发送对应于方法调用的消息。在平台端,Android 的 MethodChannel
和 iOS 的 FlutterMethodChannel
允许接收方法调用并发送结果。这些类使你能够以极少的样板代码开发平台插件。
数据类型支持
#标准的平台通道 API 和 Pigeon 包使用一个名为 StandardMessageCodec
的标准消息编解码器,它支持对简单的类似 JSON 的值(如布尔值、数字、字符串、字节缓冲区、列表和映射)进行高效的二进制序列化。这些值与消息之间的序列化和反序列化在你发送和接收值时自动发生。
下表显示了 Dart 值在平台端如何接收,反之亦然:
Dart | Kotlin |
---|---|
null | null |
bool | Boolean |
int (<=32 位) | Int |
int (>32 位) | 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 位) | java.lang.Integer |
int (>32 位) | 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 ) |
bool | NSNumber(value: Bool) |
int (<=32 位) | NSNumber(value: Int32) |
int (>32 位) | 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 ) |
bool | NSNumber numberWithBool |
int (<=32 位) | NSNumber numberWithInt |
int (>32 位) | 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 位) | EncodableValue(int32_t) |
int (>32 位) | 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="">) |
</encodablevalue,></int64_t></int32_t></uint8_t>
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');
// Get battery level.
接下来,在方法通道上调用一个方法,使用 String
标识符 getBatteryLevel
指定要调用的具体方法。调用可能会失败——例如,如果平台不支持该平台 API(例如在模拟器中运行时),因此请将 invokeMethod
调用包装在 try-catch 语句中。
使用返回的结果在 setState
内部更新 _batteryLevel
中的用户界面状态。
// 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
方法,以包含一个显示电池状态字符串的用户界面和一个用于刷新值的按钮。
@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 宿主部分
启动 Android Studio
选择菜单项 File > Open...
导航到包含 Flutter 应用的目录,然后选择其中的 android 文件夹。点击 OK。
打开 Project 视图中 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 ->
// This method is invoked on the main thread.
// 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 {
// 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 宿主部分
启动 Android Studio
选择菜单项 File > Open...
导航到包含 Flutter 应用的目录,然后选择其中的 android 文件夹。点击 OK。
打开 Project 视图中 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) -> {
// This method is invoked on the main thread.
// 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) -> {
// 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 宿主部分
启动 Xcode。
选择菜单项 File > Open...。
导航到包含 Flutter 应用的目录,然后选择其中的 ios 文件夹。点击 OK。
在使用 Objective-C 的标准模板设置中添加对 Swift 的支持
在 Project 导航器中展开 Runner > Runner。
打开 Project 导航器中 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
// 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
文件的底部添加以下新方法
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。
选择菜单项 File > Open...。
导航到包含 Flutter 应用的目录,然后选择其中的 ios 文件夹。点击 OK。
确保 Xcode 项目构建无错误。
打开 Project 导航器中 Runner > Runner 下的
AppDelegate.m
文件。
在 application didFinishLaunchingWithOptions:
方法中创建一个 FlutterMethodChannel
并添加一个处理程序。确保使用与 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) {
// This method is invoked on the UI thread.
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
接下来,添加使用 iOS 电池 API 检索电池电量的 iOS Objective-C 代码。此代码与你在原生 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) {
// 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 宿主部分
在项目目录下运行一次
flutter build windows
以生成 Visual Studio 解决方案文件。启动 Visual Studio。
选择打开项目或解决方案。
导航到包含 Flutter 应用的目录,然后进入 build 文件夹,再进入 windows 文件夹,然后选择
batterylevel.sln
文件。点击 Open。
添加平台通道方法的 C++ 实现
在解决方案资源管理器中展开 batterylevel > Source Files。
打开
flutter_window.cpp
文件。
首先,在文件顶部,紧邻 #include "flutter_window.h"
之后添加必要的 include 文件
#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。
选择菜单项 File > Open...。
导航到包含 Flutter 应用的目录,然后选择其中的 macos 文件夹。点击 OK。
添加平台通道方法的 Swift 实现
在 Project 导航器中展开 Runner > Runner。
打开 Project 导航器中 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
// This method is invoked on the UI thread.
// Handle battery messages.
}
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 文件
#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
方法,并在调用 fl_register_plugins
之后,使用通道名称 samples.flutter.dev/battery
初始化 battery_channel
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() {
// 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
函数之后添加以下代码
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 生成消息代码。例如:
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);
}
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 的通道时,请在平台的主线程上调用它们。在 Flutter 中调用指向平台侧的通道时,可以从任何作为根 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);
}
在后台线程上执行通道处理程序 (Android)
#为了让通道的平台侧处理程序在 Android 应用的后台线程上执行,你必须使用任务队列 API。
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)
}
@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。
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)
}
+ (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 中,你可以通过向 Android 的 UI 线程 Looper
post()
一个 Runnable
来实现这一点,这将导致 Runnable
在下一个机会在主线程上执行。
Handler(Looper.getMainLooper()).post {
// Call the desired channel message here.
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Call the desired channel message here.
}
});
跳转到主线程 (iOS)
#为了满足通道的主线程要求,你可能需要从后台线程跳转到 iOS 的主线程来执行通道方法。在 iOS 中,你可以通过在主调度队列上执行一个块来实现这一点。
dispatch_async(dispatch_get_main_queue(), ^{
// Call the desired channel message here.
});
DispatchQueue.main.async {
// Call the desired channel message here.
}
补充内容
#常用通道和编解码器
#以下是一些可用于编写平台特定代码的常见平台通道 API 列表:
Flutter 的
MethodChannel
:一个命名通道,你可以使用它通过异步方法调用与平台插件进行通信。默认情况下,此通道使用StandardMessageCodec
编解码器。此通道不是类型安全的,这意味着调用和接收消息取决于宿主和客户端声明相同的参数和数据类型才能使消息正常工作。Flutter 的
BasicMessageChannel
:一个命名通道,支持使用受支持的消息编解码器进行基本、异步的消息传递。非类型安全。平台的引擎嵌入器 API:这些平台特定 API 包含平台特定通道 API。
你可以创建自己的编解码器或使用现有的编解码器。以下是一些可以与平台特定代码一起使用的现有编解码器列表:
StandardMessageCodec
:一种常用的消息编解码器,它将各种数据类型编码和解码为与平台无关的二进制格式,以便在平台通道上传输。值与消息之间的序列化和反序列化在你发送和接收值时自动发生。有关支持的数据类型列表,请参阅平台通道数据类型支持。BinaryCodec
:一种消息编解码器,用于在 Flutter 应用的 Dart 侧和原生平台侧之间传递原始二进制数据。它不执行任何更高级的数据结构编码或解码。StringCodec
:一种消息编解码器,用于使用 UTF-8 编码对字符串进行编码和解码。JSONMessageCodec
:一种消息编解码器,用于使用 UTF-8 编码对 JSON 格式数据进行编码和解码。FirestoreMessageCodec
:一种消息编解码器,用于处理 Flutter 应用与原生 Firebase Firestore SDK(在 Android 和 iOS 上)之间通过平台通道发送的消息交换。
将平台特定代码与 UI 代码分离
#如果你希望在多个 Flutter 应用中使用平台特定代码,可以考虑将代码分离到一个位于主应用之外的目录中的平台插件。有关详细信息,请参阅开发包。
将平台特定代码发布为包
#要与 Flutter 生态系统中的其他开发者共享你的平台特定代码,请参阅发布包。