编写自定义的特定平台代码
本指南介绍如何将自定义的特定平台代码与 Flutter 结合使用。
概述
#您可以在 Flutter 应用中使用特定于平台的代码。以下是一些常见的方法:
使用 Flutter 的平台通道 API 在 Flutter 和您所需的平台之间传递消息。有关更多信息,请参阅 使用平台通道调用特定平台代码。
使用
Pigeon
软件包生成类型安全的特定平台代码。有关更多信息,请参阅 使用 Pigeon 软件包调用特定平台代码。
Flutter 支持以下平台和特定平台语言:
- Android:Kotlin、Java
- iOS:Swift、Objective-C
- Windows:C++
- macOS:Objective-C
- Linux:C
平台通道的架构概述
#消息在客户端(UI)和宿主(平台)之间使用平台通道传递,如下图所示:
在上图中,消息和响应是异步通过通道传递的,以确保用户界面保持响应。在客户端,MethodChannel
for Flutter 允许发送对应于方法调用的消息。在平台端,MethodChannel
for Android 和 FlutterMethodChannel
for iOS 允许接收方法调用并发送结果。这些类使您能够开发具有极少样板代码的平台插件。
数据类型支持
#标准的平台通道 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.
接下来,在方法通道上调用方法,使用字符串标识符 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
选择菜单项 文件 > 打开...
导航到包含您的 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
选择菜单项 文件 > 打开...
导航到包含您的 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。
选择菜单项 文件 > 打开...。
导航到包含您的 Flutter 应用的目录,然后选择其中的 ios 文件夹。点击 OK。
为标准模板设置添加 Swift 支持,该模板使用 Objective-C。
在 Project 导航器中展开 **Runner > Runner**。
打开位于 Project 导航器中 **Runner > Runner** 下的
AppDelegate.swift
文件。
重写 application:didFinishLaunchingWithOptions:
函数,并创建一个与通道名称 samples.flutter.dev/battery
关联的 FlutterMethodChannel
。
@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
文件末尾添加以下内容作为新方法:
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 文件夹。点击 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 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) {
// 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,并且应用会显示“Battery level not available”。
步骤 5:添加 Windows 特定平台实现
#开始在 Visual Studio 中打开 Flutter 应用的 Windows 宿主部分
在项目目录中运行
flutter build windows
一次以生成 Visual Studio 解决方案文件。启动 Visual Studio。
选择 打开项目或解决方案。
导航到包含您的 Flutter 应用的目录,然后进入 build 文件夹,再进入 windows 文件夹,然后选择
batterylevel.sln
文件。点击 Open。
添加平台通道方法的 C++ 实现
在 Solution Explorer 中展开 **batterylevel > Source Files**。
打开
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 上运行该应用程序了。如果您的设备没有电池,它将显示“Battery level not available”。
步骤 6:添加 macOS 特定平台实现
#开始在 Xcode 中打开 Flutter 应用的 macOS 宿主部分
启动 Xcode。
选择菜单项 文件 > 打开...。
导航到包含您的 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 上运行该应用程序了。如果您的设备没有电池,它将显示“Battery level not available”。
步骤 7:添加 Linux 特定平台实现
#对于此示例,您需要安装 upower
开发标头。这很可能在您的发行版中可用,例如使用
sudo apt install libupower-glib-dev
首先,在您选择的编辑器中打开 Flutter 应用的 Linux 宿主部分。下面的说明适用于安装了“C/C++”和“CMake”扩展的 Visual Studio Code,但可以调整为其他 IDE。
启动 Visual Studio Code。
打开项目中的 linux 目录。
在询问“您想配置项目 'linux' 吗?”的提示中选择 Yes。这将启用 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
方法,并在调用 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 上运行该应用程序了。如果您的设备没有电池,它将显示“Battery level not available”。
使用 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
(由 Flutter 创建的那个)或已注册为根 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 中,您可以通过将 Runnable
post()
到 Android 的 UI 线程 Looper
来实现这一点,这会导致 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 列表:
MethodChannel
for Flutter:一个命名通道,您可以使用它通过异步方法调用与平台插件进行通信。默认情况下,此通道使用StandardMessageCodec
编解码器。此通道不是类型安全的,这意味着调用和接收消息取决于宿主和客户端声明相同的参数和数据类型,以便消息能够正常工作。BasicMessageChannel
for Flutter:一个支持基本异步消息传递的命名通道,使用支持的消息编解码器。非类型安全。平台 Engine Embedder APIs:这些特定于平台的 API 包含特定于平台的通道 API。
您可以创建自己的编解码器或使用现有的编解码器。以下是您可以在特定平台代码中使用的一些现有编解码器列表:
StandardMessageCodec
:一个常用的消息编解码器,它将各种数据类型编码和解码为平台无关的二进制格式,以便在平台通道上传输。当您发送和接收值时,这些值会自动进行序列化和反序列化。支持的数据类型列表,请参阅 平台通道数据类型支持。BinaryCodec
:一个消息编解码器,它在 Flutter 应用的 Dart 端和原生平台端之间传递原始二进制数据。它不对数据结构执行任何更高级别的编码或解码。StringCodec
:一个消息编解码器,它使用 UTF-8 编码来编码和解码字符串。JSONMessageCodec
:一个消息编解码器,它使用 UTF-8 编码来编码和解码 JSON 格式的数据。FirestoreMessageCodec
:一个消息编解码器,它处理您的 Flutter 应用和原生 Firebase Firestore SDK(在 Android 和 iOS 上)之间在平台通道上传输的消息交换。
将特定平台代码与 UI 代码分离
#如果您希望在多个 Flutter 应用中使用特定于平台的代码,可以考虑将其分离到一个位于主应用程序外部目录中的平台插件中。有关详细信息,请参阅 开发插件。
将特定平台代码发布为插件
#要与其他 Flutter 生态系统中的开发者共享您的特定平台代码,请参阅 发布插件。