撰写双端平台代码(插件编写实现)
- Architectural overview: platform channels
- 架构概述:平台通道
- Example: Calling platform-specific iOS and Android code using platform channels
- 示例: 通过平台通道调用平台的 iOS 和 Android 代码
- Step 1: Create a new app project
- 第一步:创建一个新的应用项目
- Step 2: Create the Flutter platform client
- 第二步:创建 Flutter 平台客户端
- Step 3: Add an Android platform-specific implementation
- 步骤 3: 添加 Android 平台的实现
- Step 4a: Add an iOS platform-specific implementation
- 步骤 4a:添加 iOS 平台的实现
- Step 4b: Add an iOS platform-specific implementation using Swift
- 步骤 4b:使用 Swift 添加 iOS 平台的实现
- Typesafe platform channels via Pigeon
- 通过 Pigeon 获得类型安全的通道
- Separate platform-specific code from UI code
- 从 UI 代码中分离平台相关代码
- Publish platform-specific code as a package
- 将平台相关代码作为 Package 进行提交
- Custom channels and codecs
- 自定义通道和编解码器
- Channels and Platform Threading
- 通道和平台线程
本指南介绍了如何编写自定义的平台相关代码,某些平台相关功能可通过已有的软件包获得,具体细节可查看: 在 Flutter 里使用 Packages。
This guide describes how to write custom platform-specific code. Some platform-specific functionality is available through existing packages; see using packages.
Flutter 使用了灵活的系统,它允许你调用相关平台的 API,无论是 Android 中的 Java 或 Kotlin 代码,还是 iOS 中的 Objective-C 或 Swift 代码。
Flutter uses a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
Flutter 使用了灵活系统,无论是在 Android 上的 Kotlin 还是 Java,亦或是 iOS 上的 Swift 或 Objective-C,它都允许你调用平台特定 API。
Flutter uses a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
Flutter 内置的平台特定 API 支持不依赖于任何生成代码,而是灵活的依赖于传递消息格式。或者,你也可以使用 Pigeon 这个 package,通过生成代码来 发送结构化类型安全消息。
Flutter’s builtin platform-specific API support does not rely on code generation, but rather on a flexible message passing style. Alternatively, the package Pigeon can be used for sending structured typesafe messages via code generation:
-
应用程序中的 Flutter 部分通过平台通道向其宿主(应用程序中的 iOS 或 Android 部分)发送消息。
The Flutter portion of the app sends messages to its host, the iOS or Android portion of the app, over a platform channel.
-
宿主监听平台通道并接收消息。然后,它使用原生编程语言来调用任意数量的相关平台 API,并将响应发送回客户端(即应用程序中的 Flutter 部分)。
The host listens on the platform channel, and receives the message. It then calls into any number of platform-specific APIs—using the native programming language—and sends a response back to the client, the Flutter portion of the app.
架构概述:平台通道
Architectural overview: platform channels
消息使用平台通道在客户端(UI)和宿主(平台)之间传递,如下图所示:
Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram:
消息和响应以异步的形式进行传递,以确保用户界面能够保持响应。
Messages and responses are passed asynchronously, to ensure the user interface remains responsive.
客户端做方法调用的时候 MethodChannel
会负责响应,从平台一侧来讲,Android 系统上使用 MethodChannelAndroid
、
iOS 系统使用 MethodChanneliOS
来接收和返回来自 MethodChannel
的方法调用。在开发平台插件的时候,可以减少样板代码。
On the client side, MethodChannel
enables sending
messages that correspond to method calls. On the platform side,
MethodChannel
on Android (MethodChannelAndroid
) and
FlutterMethodChannel
on iOS (MethodChanneliOS
)
enable receiving method calls and sending back a
result. These classes allow you to develop a platform plugin with very little
‘boilerplate’ code.
注意:如果需要,方法调用也可以反向发送,由平台充当客户端来调用 Dart
实现的方法。一个具体的例子是 quick_actions
插件。
Note: If desired, method calls can also be sent in the reverse direction,
with the platform acting as client to methods implemented in Dart.
A concrete example of this is the quick_actions
plugin.
平台通道数据类型及编解码器
Platform channel data types support and codecs
标准平台通道使用标准消息编解码器,它支持简单的类似 JSON
值的高效二进制序列化,例如布尔值、数字、字符串、字节缓冲区及这些类型的列表和映射(详情请参阅 StandardMessageCodec
)。当你发送和接收值时,它会自动对这些值进行序列化和反序列化。
The standard platform channels use a standard message codec that supports
efficient binary serialization of simple JSON-like values, such as booleans,
numbers, Strings, byte buffers, and Lists and Maps of these
(see StandardMessageCodec
) for details).
The serialization and deserialization of these values to and from
messages happens automatically when you send and receive values.
下表展示了如何在平台端接收 Dart 值,反之亦然:
The following table shows how Dart values are received on the platform side and vice versa:
Dart | Java | Kotlin | OC | Swift |
---|---|---|---|---|
null | null | null | nil (NSNull when nested) | nil |
bool | java.lang.Boolean | Boolean | NSNumber numberWithBool: | NSNumber(value: Bool) |
int | java.lang.Integer | Int | NSNumber numberWithInt: | NSNumber(value: Int32) |
int, if 32 bits not enough | java.lang.Long | Long | NSNumber numberWithLong: | NSNumber(value: Int) |
double | java.lang.Double | Double | NSNumber numberWithDouble: | NSNumber(value: Double) |
String | java.lang.String | String | NSString | String |
Uint8List | byte[] | ByteArray | FlutterStandardTypedData typedDataWithBytes: | FlutterStandardTypedData(bytes: Data) |
Int32List | int[] | IntArray | FlutterStandardTypedData typedDataWithInt32: | FlutterStandardTypedData(int32: Data) |
Int64List | long[] | LongArray | FlutterStandardTypedData typedDataWithInt64: | FlutterStandardTypedData(int64: Data) |
Float64List | double[] | DoubleArray | FlutterStandardTypedData typedDataWithFloat64: | FlutterStandardTypedData(float64: Data) |
List | java.util.ArrayList | List | NSArray | Array |
Map | java.util.HashMap | HashMap | NSDictionary | Dictionary |
示例: 通过平台通道调用平台的 iOS 和 Android 代码
Example: Calling platform-specific iOS and Android code using platform channels
以下代码演示了如何调用平台相关 API 来检索并显示当前的电池电量。它通过平台消息 getBatteryLevel()
来调用 Android 的 BatteryManager
API 及 iOS 的 device.batteryLevel
API。
The following code demonstrates how to call a platform-specific API
to retrieve and display the current battery level.
It uses the Android BatteryManager
API,
and the iOS device.batteryLevel
API, via a single platform message,
getBatteryLevel()
.
该示例在主应用程序中添加平台相关代码。如果想要将该代码重用于多个应用程序,那么项目的创建步骤将略有差异(查看 Flutter Packages 的开发和提交),但平台通道代码仍以相同方式编写。
The example adds the platform-specific code inside the main app itself. If you want to reuse the platform-specific code for multiple apps, the project creation step is slightly different (see developing packages), but the platform channel code is still written in the same way.
注意:可在 /examples/platform_channel/
中获得使用 Java 实现的
Android 及使用 Objective-C 实现的 iOS 的该示例完整可运行的代码。对于用 Swift 实现的 iOS 代码,请参阅 /examples/platform_channel_swift/
。
Note: The full, runnable source-code for this example is available in
/examples/platform_channel/
for Android with Java and
iOS with Objective-C. For iOS with Swift,
see /examples/platform_channel_swift/
.
第一步:创建一个新的应用项目
Step 1: Create a new app project
首先创建一个新的应用:
Start by creating a new app:
-
在终端中运行:
flutter create batterylevel
In a terminal run:
flutter create batterylevel
默认情况下,我们的模板使用 Kotlin 编写 Android 或使用 Swift 编写 iOS 代码。要使用
Java 或 Objective-C,请使用 -i
和/或 -a
标志:
By default our template supports writing Android code using Kotlin,
or iOS code using Swift. To use Java or Objective-C,
use the -i
and/or -a
flags:
-
在终端中运行:
flutter create -i swift -a kotlin batterylevel
In a terminal run:
flutter create -i swift -a kotlin batterylevel
第二步:创建 Flutter 平台客户端
Step 2: Create the Flutter platform client
应用程序的 State
类保持当前应用的状态。扩展它以保持当前的电池状态。
The app’s State
class holds the current app state.
Extend that to hold the current battery state.
首先,构建通道。在返回电池电量的单一平台方法中使用 MethodChannel
。
First, construct the channel. Use a MethodChannel
with a single
platform method that returns the battery level.
通道的客户端和宿主端通过传递给通道构造函数的通道名称进行连接。一个应用中所使用的所有通道名称必须是唯一的;使用唯一的 域前缀 为通道名称添加前缀,比如:samples.flutter.dev/battery
。
The client and host sides of a channel are connected through a channel name
passed in the channel constructor. All channel names used in a single app must
be unique; prefix the channel name with a unique ‘domain
prefix’, for example: 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 = const MethodChannel('samples.flutter.dev/battery');
// Get battery level.
}
接下来,在方法通道上调用方法(指定通过 String 标识符 getBatteryLevel
调用的具体方法)。调用可能会失败—比如,如果平台不支持此平台
API(比如在模拟器中运行),所以将 invokeMethod
调用包裹在 try-catch 语句中。
Next, invoke a method on the method channel, specifying the concrete method
to call using the String identifier getBatteryLevel
.
The call might fail—for example if the platform does not support the
platform API (such as when running in a simulator), so wrap the
invokeMethod
call in a try-catch statement.
在 setState
中使用返回结果来更新 _batteryLevel
内的用户界面状态。
Use the returned result to update the user interface state in _batteryLevel
inside setState
.
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
最后,将模板中的 build
方法替换为包含以字符串形式显示电池状态、并包含一个用于刷新该值的按钮的小型用户界面。
Finally, replace the build
method from the template to contain a small user
interface that displays the battery state in a string,
and a button for refreshing the value.
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
步骤 3: 添加 Android 平台的实现
Step 3: Add an Android platform-specific implementation
首先在 Android Studio 中打开 Flutter 应用的 Android 宿主部分:
Start by opening the Android host portion of your Flutter app in Android Studio:
-
启动 Android Studio
Start Android Studio
-
选择菜单项 File > Open…
Select the menu item File > Open…
-
导航到包含 Flutter 应用的目录,然后选择其中的 android 文件夹。点击 OK。
Navigate to the directory holding your Flutter app, and select the android folder inside it. Click OK.
-
在项目视图中打开 kotlin 文件夹下的
MainActivity.kt
文件(注意:如果使用 Android Studio 2.3 进行编辑,请注意 kotlin 目录的显示名称为 java)。Open the file
MainActivity.kt
located in the kotlin folder in the Project view. (Note: If editing with Android Studio 2.3, note that the kotlin folder is shown as if named java.)
在 configureFlutterEngine()
方法中创建一个 MethodChannel
并调用
setMethodCallHandler()
。确保使用的通道名称与 Flutter 客户端使用的一致。
Inside the configureFlutterEngine()
method, create a MethodChannel
and call
setMethodCallHandler()
. Make sure to use the same channel name as
was used on the Flutter client side.
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) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// Note: this method is invoked on the main thread.
// TODO
}
}
}
添加使用 Android battery API 来检索电池电量的 Android Kotlin 代码。该代码与你在原生 Android 应用中编写的代码完全相同。
Add the Android Kotlin code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.
首先在文件头部添加所需的依赖:
First, add the needed imports at the top of the file:
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()
方法下方添加以下新方法:
Next, add the following method in the MainActivity
class,
below the configureFlutterEngine()
method:
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
}
最后,完成前面添加的 onMethodCall()
方法。你需要处理单个平台方法 getBatteryLevel()
,所以在参数 call
中对其进行验证。该平台方法的实现是调用上一步编写的 Android 代码,并使用 result
参数来返回成功和错误情况下的响应。如果调用了未知方法,则报告该方法。
Finally, complete the setMethodCallHandler()
method added earlier. You need to
handle a single platform method, getBatteryLevel()
, so test for that in the
call
argument. The implementation of this platform method calls the
Android code written in the previous step, and returns a response for both
the success and error cases using the result
argument.
If an unknown method is called, report that instead.
删除以下代码:
Remove the following code:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// Note: this method is invoked on the main thread.
// TODO
}
并替换成以下内容:
And replace with the following:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: 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 宿主部分:
Start by opening the Android host portion of your Flutter app in Android Studio:
-
启动 Android Studio
Start Android Studio
-
选择菜单项 File > Open…
Select the menu item File > Open…
-
导航到包含 Flutter 应用的目录,然后选择其中的 android 文件夹。点击 OK。
Navigate to the directory holding your Flutter app, and select the android folder inside it. Click OK.
-
在项目视图中打开 java 文件夹下的
MainActivity.java
文件。Open the
MainActivity.java
file located in the java folder in the Project view.
接下来,在 configureFlutterEngine()
方法中创建一个 MethodChannel
并设置一个
MethodCallHandler
。确保使用的通道名称与 Flutter 客户端使用的一致。
Next, create a MethodChannel
and set a MethodCallHandler
inside the configureFlutterEngine()
method.
Make sure to use the same channel name as was used on the
Flutter client side.
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) -> {
// Note: this method is invoked on the main thread.
// TODO
}
);
}
}
添加使用 Android battery API 来检索电池电量的 Android Java 代码。该代码与你在原生 Android 应用中编写的代码完全相同。
Add the Android Java code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.
首先在文件头部添加所需的依赖:
First, add the needed imports at the top of the file:
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 类中的 onCreate()
方法下方添加以下新方法:
Then add the following as a new method in the activity class,
below the configureFlutterEngine()
method:
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;
}
最后,完成前面添加的 onMethodCall()
方法,你需要处理单个平台方法 getBatteryLevel()
,所以在参数 call
中对其进行验证。该平台方法的实现是调用上一步编写的 Android 代码,并使用 result
参数来返回成功和错误情况下的响应。如果调用了未知方法,则报告该方法。
Finally, complete the setMethodCallHandler()
method added earlier.
You need to handle a single platform method, getBatteryLevel()
,
so test for that in the call
argument. The implementation of
this platform method calls the Android code written
in the previous step, and returns a response for both
the success and error cases using the result
argument.
If an unknown method is called, report that instead.
移除以下代码:
Remove the following code:
(call, result) -> {
// Note: this method is invoked on the main thread.
// TODO
}
并替换成以下内容:
And replace with the following:
(call, result) -> {
// Note: 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 模拟器,请在扩展控件面板中设置电池电量,可从工具栏中的 … 按钮访问。
You should now be able to run the app on Android. If using the Android Emulator, set the battery level in the Extended Controls panel accessible from the … button in the toolbar.
步骤 4a:添加 iOS 平台的实现
Step 4a: Add an iOS platform-specific implementation
首先在 Xcode 中打开 Flutter 应用的 iOS 宿主部分:
Start by opening the iOS host portion of the Flutter app in Xcode:
-
启动 Xcode
Start Xcode.
-
选择菜单项 File > Open…
Select the menu item File > Open….
-
导航到包含 Flutter 应用的目录,然后选择其中的 ios 文件夹。点击 OK。
Navigate to the directory holding your Flutter app, and select the ios folder inside it. Click OK.
-
确保 Xcode 项目构建没有错误。
Make sure the Xcode projects builds without errors.
-
打开项目导航 Runner > Runner 下的
AppDelegate.m
文件。Open the file
AppDelegate.m
, located under Runner > Runner in the Project navigator.
在 application didFinishLaunchingWithOptions:
方法中创建一个 FlutterMethodChannel
并添加一个处理程序。确保使用的通道名称与 Flutter 客户端使用的一致。
Create a FlutterMethodChannel
and add a handler inside the application
didFinishLaunchingWithOptions:
method. Make sure to use the same channel name
as was used on the Flutter client side.
#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) {
// Note: this method is invoked on the UI thread.
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
接下来添加使用 iOS battery API 来检索电池电量的 iOS Objective-C 代码。该代码与你在原生 iOS 应用中编写的代码完全相同。
Next, add the iOS ObjectiveC code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app.
在 AppDelegate
类中的 @end
之前添加以下方法:
Add the following method in the AppDelegate
class, just before @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
参数来返回成功和错误情况下的响应。如果调用了未知方法,则报告该方法。
Finally, complete the setMethodCallHandler()
method added earlier.
You need to handle a single platform method, getBatteryLevel()
,
so test for that in the call
argument. The implementation of
this platform method calls the iOS code written in the previous step,
and returns a response for both the success and error cases using
the result
argument. If an unknown method is called, report that instead.
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// Note: 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 info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
现在你应该可以在 iOS 中运行该应用。如果使用了 iOS 模拟器,注意它并不支持 battery API,并且应用会显示 ‘battery info unavailable’。
You should now be able to run the app on iOS. If using the iOS Simulator, note that it does not support battery APIs, and the app displays ‘battery info unavailable’.
步骤 4b:使用 Swift 添加 iOS 平台的实现
Step 4b: Add an iOS platform-specific implementation using Swift
注意:以下步骤与 4a 类似,唯一的区别是使用了 Swift 而非 Objective-C。
Note: The following steps are similar to step 4a, only using Swift rather than Objective-C.
此步骤假设你在第一步中使用 -i swift
选项创建了项目。
This step assumes that you created your project in step 1.
using the -i swift
option.
首先在 Xcode 中打开 Flutter 应用的 iOS 宿主部分:
Start by opening the iOS host portion of your Flutter app in Xcode:
-
启动 Xcode
Start Xcode.
-
选择菜单项 File > Open…
Select the menu item File > Open….
-
导航到包含 Flutter 应用的目录,然后选择其中的 ios 文件夹。点击 OK。
Navigate to the directory holding your Flutter app, and select the ios folder inside it. Click OK.
在使用 Objective-C 的标准模板设置中添加对 Swift 的支持:
Add support for Swift in the standard template setup that uses Objective-C:
-
在项目导航中展开 Expand Runner > Runner。
Expand Runner > Runner in the Project navigator.
-
打开项目导航
Runner > Runner
下的AppDelegate.swift
文件。Open the file
AppDelegate.swift
located under Runner > Runner in the Project navigator.
重写 application:didFinishLaunchingWithOptions:
方法并创建一个绑定了通道名称
samples.flutter.dev/battery
的 FlutterMethodChannel
:
Override the application:didFinishLaunchingWithOptions:
function and create
a FlutterMethodChannel
tied to the channel name
samples.flutter.dev/battery
:
@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({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
接下来添加使用 iOS battery API 来检索电池电量的 iOS Swift 代码。该代码与你在原生 iOS 应用中编写的代码完全相同。
Next, add the iOS Swift code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app.
在 AppDelegate.swift
末尾添加以下新方法:
Add the following as a new method at the bottom of 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 info unavailable",
details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
最后,完成前面添加的 setMethodCallHandler()
方法。你需要处理单个平台方法 getBatteryLevel()
,所以在参数 call
中对其进行验证。该平台方法的实现是调用上一步编写的 iOS 代码。如果调用了未知方法,则报告该方法。
Finally, complete the setMethodCallHandler()
method added earlier. You need
to handle a single platform method, getBatteryLevel()
, so test for that in
the call
argument. The implementation of this platform method calls
the iOS code written in the previous step. If an unknown method
is called, report that instead.
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
现在你应该可以在 iOS 中运行该应用。如果使用了 iOS 模拟器,注意它并不支持 battery API,并且应用会显示 ‘battery info unavailable’。
You should now be able to run the app on iOS. If using the iOS Simulator, note that it does not support battery APIs, and the app displays ‘battery info unavailable’.
通过 Pigeon 获得类型安全的通道
Typesafe platform channels via Pigeon
在之前的样例中,我们使用 MethodChannel
在 host 和 client 之间进行通信,然而这并不是类型安全的。为了正确通信,调用/接收消息取决于 host 和 client 声明相同的参数和数据类型。
Pigeon 包可以用作 MethodChannel
的替代品,它将生成以结构化类型安全方式发送消息的代码。
The previous example uses MethodChannel
to communicate between the host and
client which isn’t typesafe. Calling and receiving messages depends on the host
and client declaring the same arguments and datatypes in order for messages to
work. The Pigeon package can be used as an alternative to MethodChannel
to generate code that sends messages in a structured typesafe manner.
在 Pigeon 中,消息接口在 Dart 中进行定义,然后它将生成对应的 Android 以及 iOS 的代码。更复杂的例子以及更多信息尽在 Pigeon pub.dev page。
With Pigeon the messaging protocol is defined in a subset of Dart which then generates messaging code for Android or iOS. A more complete example and more information can be found on the Pigeon pub.dev page;
使用 Pigeon 消除了在主机和客户端之间匹配字符串的需要消息的名称和数据类型。它支持:嵌套类,消息转换为 API,生成异步包装代码并发送消息。生成的代码具有相当的可读性并保证在不同版本的多个客户端之间没有冲突。支持 Objective-C,Java,Kotlin 和 Swift(通过 Objective-C 互操作)语言。
Using Pigeon eliminates the need to match strings between host and client for the names and datatypes of messages. It supports: nested classes, grouping messages into APIs, generation of asynchronous wrapper code and sending messages in either direction. The generated code is readable and guarentees there will be no conflicts between multiple clients of different versions. Supported languages are Objective-C, Java, Kotlin and Swift (via Objective-C interop).
Pigeon 样例
Pigeon example
Pigeon file:
import 'package:pigeon/pigeon.dart';
class SearchRequest {
String query;
}
class SearchReply {
String result;
}
@HostApi()
abstract class Api {
SearchReply search(SearchRequest request);
}
Dart usage:
import 'generated_pigeon.dart'
void onClick() async {
SearchRequest request = SearchRequest()..query = 'test';
Api api = Api();
SearchReply reply = await api.search(request);
print('reply: ${reply.result}');
}
从 UI 代码中分离平台相关代码
Separate platform-specific code from UI code
如果你想要在多个 Flutter 应用中使用你的平台相关代码,则将代码分离为位于主应用目录之外的平台插件会很有用。相关细节查看 Flutter Packages 的开发和提交。
If you expect to use your platform-specific code in multiple Flutter apps, it can be useful to separate the code into a platform plugin located in a directory outside your main application. See developing packages for details.
将平台相关代码作为 Package 进行提交
Publish platform-specific code as a package
与 Flutter 生态中的其他开发者共享你的平台相关代码,可查看 提交 package。
To share your platform-specific code with other developers in the Flutter ecosystem, see publishing packages.
自定义通道和编解码器
Custom channels and codecs
除了上面提到的 MethodChannel
,你还可以使用更基础的
[BasicMessageChannel
][BasicMessageChannel],它支持使用自定义的消息编解码器进行基本的异步消息传递。你还可以使用专门的
[BinaryCodec
][BinaryCodec],[StringCodec
][StringCodec] 和
[JSONMessageCodec
][JSONMessageCodec] 类,或创建自己的编解码器。
Besides the above mentioned MethodChannel
,
you can also use the more basic
BasicMessageChannel
, which supports basic,
asynchronous message passing using a custom message codec.
You can also use the specialized BinaryCodec
,
StringCodec
, and JSONMessageCodec
classes, or create your own codec.
您还可以在 cloud_firestore
插件中查看自定义编解码器的示例,该插件可以序列化和反序列化比默认类型更多的类型。
You might also check out an example of a custom codec
in the cloud_firestore
plugin,
which is able to serialize and deserialize many more
types than the default types.
通道和平台线程
Channels and Platform Threading
在平台端编写代码时,请调用平台主线程上的所有通道方法。在 Android上,该线程有时称为“主线程”,但从技术上讲,它被称之为为 UI线程。用 @UiThread
注释需要在UI线程上运行的方法。在iOS上,此线程称为 主线程。
Invoke all channel methods on the platform’s main thread when writing code on
the platform side. On Android, this thread is sometimes called the “main
thread”, but it is technically defined as the UI thread.
Annotate methods that need to be run on the UI thread with @UiThread
.
On iOS, this thread is officially referred to as the main thread.
跳转到 Android 中的 UI 线程
Jumping to the UI thread in Android
为了符合通道跳转到 Android UI 线程的要求,你可能需要从后台线程跳转到 Android 的 UI 线程以执行通道的方法。在 Android 中的实现方式是:在一个叫 Looper
的 Android UI 线程里 post()
一个 Runnable
。这能使得 Runnable
在下一次机会时在主线程上执行。
To comply with channels’ UI thread requirement, you may need to jump from a
background thread to Android’s UI thread to execute a channel method. In
Android this is accomplished by post()
ing a Runnable
to Android’s UI
thread Looper
, which causes the Runnable
to execute on the main thread
at the next opportunity.
Java 代码:
In Java:
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Call the desired channel message here.
}
});
Kotlin 代码:
In Kotlin:
Handler(Looper.getMainLooper()).post {
// Call the desired channel message here.
}
跳转到 iOS 中的主线程
Jumping to the main thread in iOS
为了符合通道跳转到 iOS 主线程的要求,您可能需要从后台线程跳转到 iOS 的主线程来执行通道方法。在iOS中,这是通过在主 dispatch queue上执行 block来实现的:
To comply with channel’s main thread requirement, you may need to jump from a background thread to iOS’s main thread to execute a channel method. In iOS this is accomplished by executing a block on the main dispatch queue:
Objective-C 代码:
In Objective-C:
dispatch_async(dispatch_get_main_queue(), ^{
// Call the desired channel message here.
});
Swift 代码:
In Swift:
DispatchQueue.main.async {
// Call the desired channel message here.
}