作为跨端框架,Flutter 不可避免地需要与原生做交互。在开发过程中,有些功能需要直接调用原生平台提供的 API 或功能,例如访问设备的电池状态、相机、传感器等。为了实现这些功能,Flutter 提供了平台通道(MethodChannel),允许 Flutter 与 iOS 和 Android 原生代码进行通信。这种桥接机制使得开发者能够在跨平台应用中充分利用每个操作系统的特性和功能
接下来我们以获取电量为例,探讨下如何在 iOS 和 Android 平台上编写相应的原生代码,并通过平台通道将结果传递回 Flutter 层
Android
android 需要修改的文件是 MainActivity.kt
,未更改的文件如下
kt
package com.example.test_drive
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()
更改后如下,做了以下的改动
- 固定
channel
名称 :通过BASE_CHANNEL_NAME
和BATTERY_CHANNEL_NAME
变量定义了通道名称,并通过字符串拼接方式构建了最终的MethodChannel
名称 - 配置
MethodChannel
:在configureFlutterEngine
方法中,通过flutterEngine.dartExecutor.binaryMessenger
创建了MethodChannel
并设置了方法调用处理程序 - 获取电池电量 :在
getBatteryLevel
方法中,通过BatteryManager
获取电池电量。如果获取成功,返回电量百分比;如果失败,返回-1
作为失败的标志
kt
package com.example.test_drive
import android.os.BatteryManager
import android.os.Build
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val BASE_CHANNEL_NAME = "com.example.testDrive"
private val BATTERY_CHANNEL_NAME = "$BASE_CHANNEL_NAME/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, BATTERY_CHANNEL_NAME).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
-1
}
}
}
iOS
iOS 需要修改的是 AppDelegate.swift
文件,其路径为 ios/Runner/AppDelegate.swift
未修改的文件如下
swift
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
更改后如下,做了以下的改动
- 获取 FlutterViewController 实例,参考
controller
相关代码 - 定义平台通道名称,参考
batteryChannel
相关代码 - 设置方法调用处理程序,参考
setMethodCallHandler
相关代码 - 新增方法:获取电池电量,参考
receiveBatteryLevel
相关代码
swift
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let baseChannelName = "com.example.testDrive"
let batteryChannelName = "\(baseChannelName)/battery"
let batteryChannel = FlutterMethodChannel(name: batteryChannelName,
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
if call.method == "getBatteryLevel" {
self.receiveBatteryLevel(result: result)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
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))
}
}
}
如何使用
需要注意的是,更改了原生代码,无论是 android 还是 iOS,都需要重新 build 一次,而不是简单地 reload,我们创建一个 bridge_page.dart
页面,展示下如何调用
定义的 MethodChannel
要与在原生文件中定义的一致,通过 platform.invokeMethod('getBatteryLevel')
调用原生代码获取电池电量
dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class BridgePage extends StatefulWidget {
const BridgePage({super.key});
@override
BridgePageState createState() => BridgePageState();
}
class BridgePageState extends State<BridgePage> {
static const platform = MethodChannel('com.example.testDrive/battery');
String _batteryLevel = 'Unknown battery level';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bridge Page Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Battery Level: $_batteryLevel'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get 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;
});
}
}