Flutter | 如何優(yōu)雅的調(diào)用 Android 原生方法?

Flutter 的優(yōu)勢與缺點

Flutter 作為一個跨平臺的移動 UI 框架,可以快速在 iOS 和 Android 上構(gòu)建高質(zhì)量的原生用戶界面??梢哉f是一套代碼做到了多端運行,我們常說的 Flutter 跨平臺,其實是 UI 跨平臺,編寫好了一套 UI 代碼,就可以在 IOS、Android、Web 上呈現(xiàn)出同樣的效果,節(jié)省了大量的人力成本,這也是 Flutter 越來越流行的原因之一。

然而 Flutter 也有它的缺點,很明顯的是它并不能直接調(diào)用平臺的系統(tǒng)功能,比如使用藍(lán)牙、相機、GPS、音量、電池等,因此要在 Flutter 中調(diào)用這些能力就必須和原生平臺進行通信。

Flutter 架構(gòu)概覽

了解一個框架的全貌,有助于我們從更高的視角去看待一門新技術(shù),而避免深陷代碼細(xì)節(jié),無法自拔。首先來看一張官方給出的 Flutter 整體架構(gòu)圖,如下。

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é)對需要合成的場景進行柵格化。它提供了 Flutter 核心 API 的底層實現(xiàn),包括圖形(通過 Skia)、文本布局、文件及網(wǎng)絡(luò) IO、輔助功能支持、插件架構(gòu)和 Dart 運行環(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 很多是測試代碼。

Flutter Engine

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ù)勛约旱囊娊?,因為從程序員的角度看代碼比談概念更具體。Embedder 層的代碼同樣包含于 Engine 項目中,如下圖。

Flutter Embedder

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),下面開始進入實戰(zhàn)環(huán)節(jié)。

Flutter 如何與特定平臺進行通信

本小節(jié)再次強調(diào)“特定平臺”這個概念,因為 Flutter 它只是一個 UI 跨平臺的框架,讀者需要牢記于心,凡是涉及到平臺相關(guān)的功能,還是必須要由原生平臺來實現(xiàn)。本文以 Flutter 在 Android 平臺上的應(yīng)用,來講解 Flutter 該怎樣和 Android 原生進行通信。

Flutter 與特定平臺進行通信的流程大致是這樣的。

  • Flutter 通過類似 JNI 方法調(diào)用的方式與 Flutter Engine 通信
  • Flutter Engine 層調(diào)用特定平臺的 Embedder 層代碼
  • 特定平臺接收到來自 Embedder 層的消息,進行業(yè)務(wù)處理

反之,特定平臺想和 Flutter 進行通信,順序剛好和上面相反。

使用 MethodChannel 通道進行方法調(diào)用

Flutter 與特定平臺進行通信需要通過“平臺通道”,細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn),平臺通道(Platform Channels)被官方劃分在 Engine 層。平臺通道分為三種類型,分別是 BasicMessageChannel、MethodChannel、EventChannel。其中 MethodChannel 它使用異步方法調(diào)用的方式進行平臺通信,這也是最常用的一種方式。

編寫 Flutter 端代碼

這里以官方的一個手機電量查詢例子來演示整個方法調(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ù),通過平臺通道與特定平臺進行通信,獲取電量,這里的宿主平臺是 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 寫的,沒了解過的讀者理解上來可能有點難度。不過它也很簡單,這個通道就負(fù)責(zé)電量的查詢了。只需實現(xiàn) MethodChannel.MethodCallHandler 接口,重寫 onMethodCall 方法,這樣 Flutter 端的方法調(diào)用就會進入到這里。

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)  // 實例化通道
    }
}

最后看下效果。


Battery Level

寫在最后

本文詳細(xì)介紹了 Flutter 框架概覽,分析了 Dart Framework、Engine、Embedded 三層實際開發(fā)中所處的位置,然后通過代碼實戰(zhàn),F(xiàn)lutter 通過平臺通道與 Android 平臺進行通信,查詢到了手機電量。讀者應(yīng)該對這兩部分知識有了深刻的理解與運用,下一篇,我將繼續(xù)帶領(lǐng)大家更深層次的探索平臺通道通信機制!

如果你對我感興趣,請移步到 http://blogss.cn ,進一步了解。

  • 如果本文幫助到了你,歡迎點贊和關(guān)注 ??
  • 由于作者水平有限,文中如果有錯誤,歡迎在評論區(qū)指正 ??
  • 本文首發(fā)于簡書,未經(jīng)許可禁止轉(zhuǎn)載 ??
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容