Flutter與原生通訊 - Flutter Plugin

YYDev發表於2019-10-27

本文主要介紹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。

Flutter與原生通訊 - Flutter Plugin

Platform Channels

  • 允許Flutter UI和平臺之間傳遞訊息。
  • Platform Channel中的訊息和響應是非同步傳遞的,以確保使用者介面保持響應。
  • 一個Flutter應用可以存在多個channel,使用name作為區分。
  • Platform Channel並非是執行緒安全,故平臺跟Flutter Engine的所有互動必須在平臺的主執行緒中執行的。
  • Flutter預設的訊息編解碼器是StandardMessageCodec class,現支援平臺資料型別如下:
    Flutter與原生通訊 - Flutter Plugin

建立Flutter Plugin

接下來介紹使用Android Studio建立Flutter Plugin。使用Visual Studio Code建立的過程也是大同小異,機智的你一定能舉一反三,在這裡就不一一細說。

第一步:選擇建立一個Flutter project

Flutter與原生通訊 - Flutter Plugin

第二步:選擇Flutter Project的型別為Flutter Plugin

  • Flutter Application 是建立一個純flutter專案工程
  • Flutter Plugin 是建立一個可以暴露平臺API的外掛工程
  • Flutter Package 是建立一個純Dart的元件包。
  • Fultter Module 是建立一個Flutter Module,用於被引入現有的原生App。
    Flutter與原生通訊 - Flutter Plugin

第三步:填寫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。

Flutter與原生通訊 - Flutter Plugin

使用Android模擬器執行example工程

我們先看看示例工程在Android模擬器上的執行效果:

進入example工程目錄,執行lib/main.dart。

APP在Android模擬器執行後,可以看到螢幕出現了"Running on: Android 9"。

Flutter與原生通訊 - Flutter Plugin

接下來我們通過程式碼看看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.dartFlutterPluginEg.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,如:MethodChannelPlatform Channel的一種型別
  • _channel是FlutterPluginEg類的屬性,是一個例項化的MethodChannel,name為"flutter_plugin_eg"
  • platformVersion是FlutterPluginEg類的靜態可計算屬性,會非同步返還一個String。
  • platformVersion中,呼叫_channelinvokeMethod方法,入參"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"這個方法名的。

Flutter與原生通訊 - Flutter Plugin

IOS模擬器上執行效果:

Flutter與原生通訊 - Flutter Plugin

釋出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層上。

作者

Doerme

相關文章