撰寫自訂平台特定程式碼
本指南說明如何撰寫自訂平台特定程式碼。某些平台特定功能可透過現有套件取得;請參閱使用套件。
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 位元) | 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>) |
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
選取選單項目檔案 > 開啟...
導覽至您的 Flutter 應用程式所在的目錄,並選取其中的 android 資料夾。點擊確定。
在專案檢視中,開啟位於 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
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 資料夾。點擊確定。
在專案檢視中,開啟位於 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
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 資料夾。點擊確定。
在使用 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
// 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 資料夾。點擊確定。
請確保 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) {
// 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,且應用程式會顯示「電池電量無法使用」。
步驟 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
#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
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++ 自動完成。開啟
my_application.cc
檔案。
首先,在檔案頂端,#include <flutter_linux/flutter_linux.h>
之後,新增必要的 include
#include <math.h>
#include <upower.h>
將 FlMethodChannel
新增至 _MyApplication
結構
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 的型別安全平台通道
#先前的範例使用 MethodChannel
在主機和用戶端之間進行通訊,這並非型別安全。呼叫和接收訊息取決於主機和用戶端宣告相同的引數和資料類型,訊息才能運作。您可以使用 Pigeon 套件來替代 MethodChannel
,以產生程式碼,並以結構化的型別安全方式傳送訊息。
使用 Pigeon 時,訊息傳輸協定是在 Dart 的子集中定義,然後會為 Android、iOS、macOS 或 Windows 產生訊息傳輸程式碼。您可以在 pub.dev 上的 pigeon
頁面上找到更完整的範例和更多資訊。
使用 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
(root Isolate
)中調用,或者從已註冊為背景 Isolate
的 Isolate
中調用。平台端的處理程式可以在平台的主執行緒上執行,如果使用任務佇列(Task Queue),則也可以在背景執行緒上執行。您可以非同步地在任何執行緒上調用平台端的處理程式。
從背景隔離區使用外掛程式和通道
#外掛程式和通道可以被任何 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 {
// Call the desired channel message here.
}
在 Java 中
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Call the desired channel message here.
}
});
在 iOS 中跳轉至主執行緒
#為了符合通道的主執行緒要求,您可能需要從背景執行緒跳轉到 iOS 的主執行緒來執行通道方法。您可以在 iOS 中透過在主 dispatch queue 上執行一個 區塊(block) 來完成此操作。
在 Objective-C 中
dispatch_async(dispatch_get_main_queue(), ^{
// Call the desired channel message here.
});
在 Swift 中
DispatchQueue.main.async {
// Call the desired channel message here.
}
除非另有說明,否則本網站上的文件反映 Flutter 的最新穩定版本。頁面最後更新於 2024-09-26。 檢視原始碼 或 回報問題。