Flutter 的優(yōu)勢與缺點(diǎn)
Flutter 作為一個跨平臺的移動 UI 框架汉嗽,可以快速在 iOS 和 Android 上構(gòu)建高質(zhì)量的原生用戶界面欲逃。可以說是一套代碼做到了多端運(yùn)行饼暑,我們常說的 Flutter 跨平臺稳析,其實是 UI 跨平臺,編寫好了一套 UI 代碼撵孤,就可以在 IOS迈着、Android、Web 上呈現(xiàn)出同樣的效果邪码,節(jié)省了大量的人力成本裕菠,這也是 Flutter 越來越流行的原因之一。
然而 Flutter 也有它的缺點(diǎn)闭专,很明顯的是它并不能直接調(diào)用平臺的系統(tǒng)功能奴潘,比如使用藍(lán)牙、相機(jī)影钉、GPS画髓、音量、電池等平委,因此要在 Flutter 中調(diào)用這些能力就必須和原生平臺進(jìn)行通信奈虾。
Flutter 架構(gòu)概覽
了解一個框架的全貌,有助于我們從更高的視角去看待一門新技術(shù),而避免深陷代碼細(xì)節(jié)肉微,無法自拔匾鸥。首先來看一張官方給出的 Flutter 整體架構(gòu)圖,如下碉纳。
官方將 Flutter 框架大致分為了三層勿负,F(xiàn)ramework、Engine劳曹、Embedder奴愉。下面我將詳細(xì)講解一下這三層到底是什么,在實際開發(fā)中承擔(dān)著怎樣的角色铁孵。
Dart Framework 層
作為一個 Flutter 開發(fā)者锭硼,辛勤的碼農(nóng),你每天都在這片土地?fù)]灑著汗水库菲。
你每天使用的各種 UI 組件(Flutter UI 控件已經(jīng)快接近 400 個了)账忘,各種 Flutter Package志膀,F(xiàn)lutter Plugin熙宇,層出不窮的第三方庫讓你感到頭皮發(fā)涼。所以我不過多介紹大家都應(yīng)該懂了吧溉浙,Dart 是最好的語言~
Engine 層
Flutter 引擎烫止,官方是這樣介紹它的。
Flutter 的核心戳稽,主要使用 C++ 編寫馆蠕,提供了 Flutter 應(yīng)用所需的原語。當(dāng)需要繪制新一幀的內(nèi)容時惊奇,引擎將負(fù)責(zé)對需要合成的場景進(jìn)行柵格化互躬。它提供了 Flutter 核心 API 的底層實現(xiàn),包括圖形(通過 Skia)颂郎、文本布局吼渡、文件及網(wǎng)絡(luò) IO、輔助功能支持乓序、插件架構(gòu)和 Dart 運(yùn)行環(huán)境及編譯環(huán)境的工具鏈寺酪。
我覺得官方描述得已經(jīng)很詳細(xì)了,總結(jié)一下就是:它負(fù)責(zé) Flutter UI 的渲染以及宿主的交互替劈,接入了 Engine 的就叫做宿主寄雀,如 Android 品臺接入了 Flutter Engine,那么宿主就是 Android 平臺陨献。
Flutter Engine 是開源的盒犹,在 github 上可以找到,傳送門 https://github.com/flutter/engine
直觀感受下這個 Engine 項目都是用哪些語言編寫的,如下圖急膀。其實主要語言還是 C++膜蛔,其中 Dart 很多是測試代碼。
Embedder 平臺嵌入層
官方是這樣介紹它的脖阵。
平臺嵌入層是用于呈現(xiàn)所有 Flutter 內(nèi)容的原生系統(tǒng)應(yīng)用皂股,它充當(dāng)著宿主操作系統(tǒng)和 Flutter 之間的粘合劑的角色。當(dāng)你啟動一個 Flutter 應(yīng)用時命黔,嵌入層會提供一個入口呜呐,初始化 Flutter 引擎,獲取 UI 和柵格化線程悍募,創(chuàng)建 Flutter 可以寫入的紋理蘑辑。嵌入層同時負(fù)責(zé)管理應(yīng)用的生命周期,包括輸入的操作(例如鼠標(biāo)坠宴、鍵盤和觸控)洋魂、窗口大小的變化、線程管理和平臺消息的傳遞喜鼓。 Flutter 擁有 Android副砍、iOS、Windows庄岖、macOS 和 Linux 的平臺嵌入層豁翎,當(dāng)然,開發(fā)者可以創(chuàng)建自定義的嵌入層
我覺得官方這段解釋得同樣很好隅忿,我?guī)缀鯚o法有更好的解釋~但我還是要談?wù)勛约旱囊娊庑陌驗閺某绦騿T的角度看代碼比談概念更具體。Embedder 層的代碼同樣包含于 Engine 項目中背桐,如下圖优烧。
Embedder 層代碼整體可以分為兩大塊
- 各平臺的 Embedder 層代碼,用于接入 Flutter Engine链峭。如 Android 平臺 Embedder 是用 Java 寫的畦娄,當(dāng)然還有與其對應(yīng)的 C++ 代碼,方便 Java 調(diào)用 JNI 來和 Flutter Engine 之間通信熏版。自然 IOS 就是 Object-C 了纷责。
- 共用的 Embedder 層代碼,C++ 編寫撼短, 用于平臺消息傳遞等功能再膳。
了解了 Flutter 架構(gòu),下面開始進(jìn)入實戰(zhàn)環(huán)節(jié)曲横。
Flutter 如何與特定平臺進(jìn)行通信
本小節(jié)再次強(qiáng)調(diào)“特定平臺”這個概念喂柒,因為 Flutter 它只是一個 UI 跨平臺的框架不瓶,讀者需要牢記于心,凡是涉及到平臺相關(guān)的功能灾杰,還是必須要由原生平臺來實現(xiàn)蚊丐。本文以 Flutter 在 Android 平臺上的應(yīng)用,來講解 Flutter 該怎樣和 Android 原生進(jìn)行通信艳吠。
Flutter 與特定平臺進(jìn)行通信的流程大致是這樣的麦备。
- Flutter 通過類似 JNI 方法調(diào)用的方式與 Flutter Engine 通信
- Flutter Engine 層調(diào)用特定平臺的 Embedder 層代碼
- 特定平臺接收到來自 Embedder 層的消息,進(jìn)行業(yè)務(wù)處理
反之昭娩,特定平臺想和 Flutter 進(jìn)行通信凛篙,順序剛好和上面相反。
使用 MethodChannel 通道進(jìn)行方法調(diào)用
Flutter 與特定平臺進(jìn)行通信需要通過“平臺通道”栏渺,細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn)呛梆,平臺通道(Platform Channels)被官方劃分在 Engine 層。平臺通道分為三種類型磕诊,分別是 BasicMessageChannel填物、MethodChannel、EventChannel霎终。其中 MethodChannel 它使用異步方法調(diào)用的方式進(jìn)行平臺通信滞磺,這也是最常用的一種方式。
編寫 Flutter 端代碼
這里以官方的一個手機(jī)電量查詢例子來演示整個方法調(diào)用過程神僵,F(xiàn)lutter 端代碼如下雁刷。優(yōu)雅的做法是,遵循職責(zé)單一的原則保礼,將每一個方法通道封裝成一個類。比如我這個通道是用來管理電量的责语,那么就叫 BatteryChannel
炮障,所有和電量有關(guān)的方法都封裝在這個類中。
class BatteryChannel {
static const _batteryChannelName = "cn.blogss/battery"; // 1.方法通道名稱
static MethodChannel _batteryChannel;
static void initChannels(){
_batteryChannel = MethodChannel(_batteryChannelName); // 2. 實例化一個方法通道
}
// 3. 異步任務(wù)坤候,通過平臺通道與特定平臺進(jìn)行通信胁赢,獲取電量,這里的宿主平臺是 Android
static getBatteryLevel() async {
String batteryLevel;
try {
final int result = await _batteryChannel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
return batteryLevel;
}
}
在你需要使用這個通道的時候白筹,在 Flutter 頁面中這樣做就行了智末。
class BatteryRoute extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return BatteryRouteState();
}
}
class BatteryRouteState extends State<BatteryRoute> {
String _batteryLevel = 'Unknown battery level.';
// 3.異步獲取到電量,然后重新渲染頁面
getBatteryLevel() async{
_batteryLevel = await BatteryChannel.getBatteryLevel();
setState(() {});
}
@override
void initState() {
super.initState();
BatteryChannel.initChannels(); // 1. 初始化通道
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BatteryRoute"),
centerTitle: true,
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new ElevatedButton(
child: new Text('Get Battery Level'),
onPressed: (){
getBatteryLevel(); // 2. 調(diào)用通道方法
},
),
new Text(_batteryLevel),
],
),
),
);
}
}
編寫 Android 端代碼
Android 端代碼與 Flutter 端類似徒河,如下所示系馆。同樣這個類叫BatteryChannel
,下面是 Kotlin 寫的顽照,沒了解過的讀者理解上來可能有點(diǎn)難度由蘑。不過它也很簡單闽寡,這個通道就負(fù)責(zé)電量的查詢了。只需實現(xiàn) MethodChannel.MethodCallHandler 接口尼酿,重寫 onMethodCall 方法爷狈,這樣 Flutter 端的方法調(diào)用就會進(jìn)入到這里。
class BatteryChannel(flutterEngine: BinaryMessenger, context: Context): MethodChannel.MethodCallHandler {
private val batteryChannelName = "cn.blogss/battery"
private var channel: MethodChannel
private var mContext: Context
companion object {
private const val TAG = "BatteryChannel"
}
init {
Log.d(TAG, "init")
channel = MethodChannel(flutterEngine, batteryChannelName)
channel.setMethodCallHandler(this)
mContext = context;
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.d(TAG, "onMethodCall: " + call.method)
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 batteryLevel: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val batteryManager = mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(mContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
然后我們還要在 MainActivity 中實例化一下這個通道裳擎,如下涎永。常用的做法是在 configureFlutterEngine 這個方法中實例化我們的通道就行了,有多少個通道鹿响,就在這里實例化多少個通道土辩。
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
BatteryChannel(flutterEngine.dartExecutor.binaryMessenger,context) // 實例化通道
}
}
最后看下效果。
寫在最后
本文詳細(xì)介紹了 Flutter 框架概覽抢野,分析了 Dart Framework拷淘、Engine、Embedded 三層實際開發(fā)中所處的位置指孤,然后通過代碼實戰(zhàn)启涯,F(xiàn)lutter 通過平臺通道與 Android 平臺進(jìn)行通信,查詢到了手機(jī)電量恃轩。讀者應(yīng)該對這兩部分知識有了深刻的理解與運(yùn)用结洼,下一篇,我將繼續(xù)帶領(lǐng)大家更深層次的探索平臺通道通信機(jī)制叉跛!
如果你對我感興趣松忍,請移步到 http://blogss.cn ,進(jìn)一步了解筷厘。
- 如果本文幫助到了你鸣峭,歡迎點(diǎn)贊和關(guān)注 ??
- 由于作者水平有限,文中如果有錯誤酥艳,歡迎在評論區(qū)指正 ??
- 本文首發(fā)于簡書摊溶,未經(jīng)許可禁止轉(zhuǎn)載 ??