前言
我們都知道Flutter開發的app是可以同時在iOS和Android系統上執行的。顯然Flutter需要有和Native通訊的能力。在實際專案中,Flutter並不能全面滿足專案需求,比如獲取一些硬體資訊(如電池電量),這些都是一些比較簡單的Native需求,Flutter官方也給出了一些比較常用的Plugin。但在實際專案中可能需求就沒那麼簡單了,比如融雲通訊、環信通訊,再或者專案自定義的需求,這可能就需要我們自己去寫外掛了。這裡我就以Flutter獲取Native電池電量和電池狀態(充電中、充滿電、未充電)為例(包括IOS-Swift 和 Android-Kotlin)來實現自己的需求。
思路
Flutter是如何做到的呢?當然是Flutter官方給的Platform Channels。如圖:
上圖來自Flutter官網,表明了Platform Channels的架構示意圖。Platform Channel包括MethodChannel、EventChannel和BasicMessageChannel三大通道。
Platform Channel 支援的資料型別
比較常用
Dart | iOS-Swift | Android-Kotlin |
---|---|---|
null | nil | null |
bool | Bool | Boolean |
int | Int | Int |
float | Int | Int |
double | Double | Double |
String | String | String |
List | Array | List |
Map | Dictionary | HashMap |
MethodChannel
俗稱方法通道(個人見解)用於傳遞方法呼叫
以 Flutter 獲取 手機電量為例, 在 Flutter 介面中要想獲取 Android/iOS 的電量, 首先要在 Native 編寫獲取電量的功能, 供 Flutter 來呼叫。
- Native - iOS
public class SwiftFlutterPluginLearningPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftFlutterPluginLearningPlugin()
let methodChannel = FlutterMethodChannel(name: "plugins.limit.io/battery", binaryMessenger: registrar.messenger())
//註冊電池(plugins.limit.io/battery)方法通道
registrar.addMethodCallDelegate(instance, channel: methodChannel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getBatteryLevel":
let batterLevel = getBatteryLevel()
if(batterLevel == -1) {
result(FlutterError(code: "UNAVAILABLE", message: "Battery info unavailable", details: nil));
} else {
result(batterLevel)
}
default:
result(FlutterMethodNotImplemented)
}
}
//獲取電池電量
private func getBatteryLevel() -> Float {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == .unknown {
return -1;
} else {
return UIDevice.current.batteryLevel
}
}
}
複製程式碼
- Native - Android
public class FlutterPluginLearningPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
private var applicationContext: Context? = null
/**
* 方法通道
*/
private var methodChannel: MethodChannel? = null
/**
* 連線到引擎
*/
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(flutterPluginBinding.applicationContext, flutterPluginBinding.binaryMessenger)
}
private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationContext = applicationContext
methodChannel = MethodChannel(messenger, "plugins.limit.io/battery")
methodChannel?.setMethodCallHandler(this)
}
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val instance = FlutterPluginLearningPlugin()
instance.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}
/**
* 回撥方法
*/
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getBatteryLevel") {
val batterLevel = getBatteryLevel()
if (batterLevel != -1) {
result.success(batterLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented()
}
}
/**
* 從引擎中脫離
*/
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = null
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}
/**
* 獲取電池電量方法
*/
private fun getBatteryLevel(): Int {
var batteryLevel = -1
batteryLevel = if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = applicationContext?.let { getSystemService(it, BatteryManager::class.java) }
batteryManager!!.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
複製程式碼
- Flutter
flutter 外掛
class FlutterPluginLearning {
factory FlutterPluginLearning() {
if (_instance == null) {
final MethodChannel methodChannel =
const MethodChannel('plugins.limit.io/battery');
_instance = FlutterPluginLearning.init(methodChannel);
}
return _instance;
}
FlutterPluginLearning.init(this._methodChannel);
static FlutterPluginLearning _instance;
final MethodChannel _methodChannel;
Future<int> get batteryLevel => _methodChannel
.invokeMethod<int>('getBatteryLevel')
.then<int>((dynamic result) => result);
}
複製程式碼
flutter 呼叫
FlutterPluginLearning _battery = FlutterPluginLearning();
_battery.batteryLevel.then((int batteryLevel) {
//to do something you want
});
複製程式碼
EventChannel
俗稱流通道(個人見解)用於資料流(event streams)的通訊 以 Flutter 獲取 手機電池狀態為例, 在 Flutter 介面中要想獲取 Android/iOS 的電池狀態, 首先要在 Native 編寫獲取電池狀態的功能, 供 Flutter 來呼叫。
- Native - iOS
public class SwiftFlutterPluginLearningPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
var eventSink: FlutterEventSink?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftFlutterPluginLearningPlugin()
let evenChannel = FlutterEventChannel(name: "plugins.limit.io/charging", binaryMessenger: registrar.messenger())
evenChannel.setStreamHandler(instance)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
UIDevice.current.isBatteryMonitoringEnabled = true
self.sendBatteryStateEvent()
NotificationCenter.default.addObserver(self, selector: #selector(onBatteryStateDidChange(notification:)), name: UIDevice.batteryStateDidChangeNotification, object: nil)
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
NotificationCenter.default.removeObserver(self)
eventSink = nil
return nil
}
@objc private func onBatteryStateDidChange(notification: NSNotification?) {
self.sendBatteryStateEvent()
}
private func sendBatteryStateEvent() {
if eventSink == nil {
return
}
let state = UIDevice.current.batteryState
switch state {
case .full:
eventSink!("full")
break
case .charging:
eventSink!("charging")
break
case .unplugged:
eventSink!("unplugged")
break
default:
eventSink!(FlutterError(code: "UNAVAILABLE", message: "Charging status unavailable", details: nil))
break
}
}
}
複製程式碼
- Native - Android
public class FlutterPluginLearningPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
private var applicationContext: Context? = null
/**
* 事件流通道
* Native 需要頻繁的傳送訊息給 Flutter, 比如監聽網路狀態, 藍芽裝置等等然後傳送給 Flutter
*/
private var eventChannel: EventChannel?= null
private var chargingStateChangeReceiver: BroadcastReceiver? = null
/**
* 連線到引擎
*/
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(flutterPluginBinding.applicationContext, flutterPluginBinding.binaryMessenger)
}
private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationContext = applicationContext
eventChannel = EventChannel(messenger, "plugins.limit.io/charging")
eventChannel?.setStreamHandler(this)
}
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val instance = FlutterPluginLearningPlugin()
instance.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}
/**
* 回撥方法
*/
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
}
/**
* 從引擎中脫離
*/
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = null
eventChannel?.setStreamHandler(null)
eventChannel = null
}
/**
* 監聽
*/
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
chargingStateChangeReceiver = createChargingStateChangeReceiver(events)
applicationContext?.registerReceiver(chargingStateChangeReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
/**
* 取消監聽
*/
override fun onCancel(arguments: Any?) {
applicationContext?.unregisterReceiver(chargingStateChangeReceiver)
chargingStateChangeReceiver = null
}
private fun createChargingStateChangeReceiver(events: EventChannel.EventSink?): BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)) {
BatteryManager.BATTERY_STATUS_CHARGING -> events?.success("charging")
BatteryManager.BATTERY_STATUS_FULL -> events?.success("full")
BatteryManager.BATTERY_STATUS_DISCHARGING -> events?.success("discharging")
else -> events?.error("UNAVAILABLE", "Charging status unavailable", null)
}
}
}
}
}
複製程式碼
- Flutter
flutter 外掛
enum BatteryState {
/// The battery is completely full of energy.
full,
/// The battery is currently storing energy.
charging,
/// The battery is currently losing energy.
discharging
}
class FlutterPluginLearning {
factory FlutterPluginLearning() {
if (_instance == null) {
final EventChannel eventChannel =
const EventChannel('plugins.limit.io/charging');
_instance = FlutterPluginLearning.init(eventChannel);
}
return _instance;
}
FlutterPluginLearning.init(this._eventChannel);
static FlutterPluginLearning _instance;
final EventChannel _eventChannel;
Stream<BatteryState> _onBatteryStateChanged;
Stream<BatteryState> get onBatteryStateChanged {
if (_onBatteryStateChanged == null) {
_onBatteryStateChanged = _eventChannel
.receiveBroadcastStream()
.map((dynamic event) => _parseBatteryState(event));
}
return _onBatteryStateChanged;
}
BatteryState _parseBatteryState(String state) {
switch (state) {
case 'full':
return BatteryState.full;
case 'charging':
return BatteryState.charging;
case 'discharging':
return BatteryState.discharging;
default:
throw ArgumentError('$state is not a valid BatteryState.');
}
}
}
複製程式碼
flutter 呼叫
FlutterPluginLearning _battery = FlutterPluginLearning();
StreamSubscription<BatteryState> _batteryStateSubscription;
_batteryStateSubscription =
_battery.onBatteryStateChanged.listen((BatteryState state) {
String batteryState = 'UnKnow';
switch (state) {
case BatteryState.full:
batteryState = '已充滿';
break;
case BatteryState.charging:
batteryState = '充電中';
break;
case BatteryState.discharging:
batteryState = '未充電';
break;
default:
}
// do something you want
});
複製程式碼
BasicMessageChannel
俗稱訊息直接通道(個人見解)用於傳遞字串和半結構化的資訊。 如果僅僅是簡單的通訊而不是呼叫某個方法或者是事件流, 可以使用 BasicMessageChannel。BasicMessageChannel 也可以實現 Flutter 和 Native 的雙向通訊, 下面的示例圖就是官方的例子:
詳細檢視(八)Flutter 和 Native之間的通訊詳解 的 BasicMessageChannel
Reference
原始碼
github.com/TBoyLi/flut… 覺得ok! Star ✨✨✨✨✨✨