本文主要介紹Flutter Plugin原理以及其開發和釋出過程。
前言
Flutter優勢主要體現在UI上--高效能且跨平臺表現一致。但是針對平臺(Android、IOS)的實現,如:獲取電量、判斷WiFi使用、調起WebView載入網頁等,得呼叫特定平臺的API包。Flutter Plugin就是為呼叫平臺API而生。
下文中所提及到的"平臺"指的是Android、IOS兩端。
介紹
Flutter Plugin
包含針對Android(Java或Kotlin程式碼)或iOS(Objective-C或Swift程式碼)的原生實現,通過Platform Channels
與FLutter(dart)層通訊並暴露API。
Platform Channels
- 允許Flutter UI和平臺之間傳遞訊息。
- Platform Channel中的訊息和響應是非同步傳遞的,以確保使用者介面保持響應。
- 一個Flutter應用可以存在多個channel,使用
name
作為區分。 - Platform Channel並非是
執行緒安全
,故平臺跟Flutter Engine的所有互動必須在平臺的主執行緒
中執行的。 - Flutter預設的訊息編解碼器是StandardMessageCodec class,現支援平臺資料型別如下:
建立Flutter Plugin
接下來介紹使用Android Studio建立Flutter Plugin。使用Visual Studio Code建立的過程也是大同小異,機智的你一定能舉一反三,在這裡就不一一細說。
第一步:選擇建立一個Flutter project
第二步:選擇Flutter Project的型別為Flutter Plugin
- Flutter Application 是建立一個純flutter專案工程
- Flutter Plugin 是建立一個可以暴露平臺API的外掛工程
- Flutter Package 是建立一個純Dart的元件包。
- Fultter Module 是建立一個Flutter Module,用於被引入現有的原生App。
第三步:填寫Project name等工程資訊,完成Flutter Plugin建立。
工程建立完成後的目錄結構如下:
- lib/flutter_plugin_eg.dart 是外掛包dart API實現
- android/src/main/ 是外掛包API Android的實現
- ios/Classes/ 是外掛包API IOS的實現
- example 是基於依賴當前外掛的純flutter示例工程,一般用作展示API呼叫。
注:從 Flutter 1.9 開始,iOS 新專案預設使用 Swift 語言,而非 Objective-C;Android 新專案則預設使用 Kotlin,而非 Java。如有需要,是可隨時切換回之前的 Objective-C 或 Java。
使用Android模擬器執行example工程
我們先看看示例工程在Android模擬器上的執行效果:
進入example工程目錄,執行lib/main.dart。
APP在Android模擬器執行後,可以看到螢幕出現了"Running on: Android 9"。
接下來我們通過程式碼看看flutter是怎麼顯示出當前平臺的系統版本。
示例工程 example/lib/main.dart
關鍵程式碼:
...
import 'package:flutter_plugin_eg/flutter_plugin_eg.dart';
...
platformVersion = await FlutterPluginEg.platformVersion;
...
setState(() {
_platformVersion = platformVersion;
});
...
body: Center(
child: Text('Running on: $_platformVersion\n'),
)
複製程式碼
示例工程中的lib/main.dart引入了我們剛剛建立Flutter Plugin中dart API實現flutter_plugin_eg.dart
。
隨後使用非同步的方式呼叫flutter_plugin_eg.dart
的FlutterPluginEg.platformVersion
並把返回值賦值給platformVersion
,隨後通過setState
方法把platformVersion
的值賦值給當前狀態元件的_platformVersion
,觸發UI重渲把_platformVersion
的值"Android 9"顯示出來。
Flutter Plugin中dart API實現 lib/flutter_plugin_eg.dart
全量程式碼:
import 'dart:async';
import 'package:flutter/services.dart';
class FlutterPluginEg {
static const MethodChannel _channel =
const MethodChannel('flutter_plugin_eg');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
複製程式碼
service.dart
暴露與平臺通訊的API,如:MethodChannel
Platform Channel的一種型別_channel
是FlutterPluginEg類的屬性,是一個例項化的MethodChannel
,name
為"flutter_plugin_eg"platformVersion
是FlutterPluginEg類的靜態可計算屬性
,會非同步返還一個String。platformVersion
中,呼叫_channel
的invokeMethod
方法,入參"getPlatformVersion"為呼叫平臺約定的方法名。然後把invokeMethod
的非同步結果賦值給String version
作為platformVersion
的返回值。
Flutter Plugin中Android實現
android/src/main/kotlin/.../FlutterPluginEgPlugin.kt 全量程式碼:
package com.yy.flutter_plugin_eg
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
class FlutterPluginEgPlugin: MethodCallHandler {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "flutter_plugin_eg")
channel.setMethodCallHandler(FlutterPluginEgPlugin())
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
}
複製程式碼
再次強調:一個Flutter應用是可以有多個
channel
,並而每個channel
都可以有多個method
,所以需要重點了解平臺的程式碼是通過怎麼樣去對接channel name 與method name。從上文.kt原始碼可以看到:
- 註冊MethodChannel約定通道名"flutter_plugin_eg",並開始監聽通道訊息。
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "flutter_plugin_eg")
channel.setMethodCallHandler(FlutterPluginEgPlugin())
}
}
複製程式碼
- 實現onMethodCall方法,判斷方法名"getPlatformVersion",返回
Android ${android.os.Build.VERSION.RELEASE}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
複製程式碼
當channel name 和 method name 約定後,Flutter Plugin就可以在dart中方便呼叫平臺的實現,並暴露API供Flutter專案使用。
Flutter Plugin中IOS實現
ios/Classes/SwiftFlutterPluginEgPlugin.swift
import Flutter
import UIKit
public class SwiftFlutterPluginEgPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "flutter_plugin_eg", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPluginEgPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
}
複製程式碼
可以看出,IOS與Android實現思路雷同:通過"flutter_plugin_eg"註冊FlutterMethodChannel
並開始監聽。
但驚奇發現Flutter Plugin中swift預設生成的程式碼居然沒FlutterMethodCall方法名判斷,在swift斷點看是能獲取到"getPlatformVersion"這個方法名的。
IOS模擬器上執行效果:
釋出Flutter Plugin
輪子造好後,可以釋出到pub.dev,以下內容純屬搬磚。
補充文件
在Flutter Plugin根目錄新增
- README.md:介紹包的檔案
- CHANGELOG.md 記錄每個版本中的更改
- LICENSE 包含軟體包許可條款的檔案
- 編寫API文件
釋出package
一旦你實現了一個包,你可以在Pub上釋出它 ,這樣其他開發人員就可以輕鬆使用它
在釋出之前,檢查pubspec.yaml、README.md以及CHANGELOG.md檔案,以確保其內容的完整性和正確性。
然後, 執行 dry-run 命令以檢視是否都準備OK了:
flutter packages pub publish --dry-run
複製程式碼
最後, 執行釋出命令:
flutter packages pub publish
複製程式碼
有關釋出的詳細資訊,請參閱Pub publishing docs
思考
開發一個功能完備的APP,說到底還是需要有原生平臺開發的能力,就如Flutter Plugin需要開發原生平臺部分。相信有很多Web前端童鞋也開始接觸Flutter,但是如果不去深入瞭解原生開發,那麼也只能停留在UI層上。