設計、開發一個 Flutter Plugin 的實踐心得

做者:郝陽,聲網Agora 工程師git

如對咱們的Flutter插件開發過程感興趣,或遇到實時音視頻相關開發問題,歡迎訪問聲網 Agora問答版塊,發帖與咱們的工程師交流。github

應開發者們的需求,咱們在推出了 Agora Flutter SDK,它以 Flutter Plugin 的形式爲 Flutter App 增添實時音視頻能力。同時,咱們也給出了一個 Quickstart Demoweb

考慮到 Flutter 對於部分開發者來說,還是個新鮮事物。因此,咱們也分享了來自 RTC 開發者社區做者的 Flutter 開發經驗。其實,在開發 Agora Flutter SDK 之初,咱們的技術團隊也就如何基於 Flutter 實現實時音視頻,深刻作過一番調研。本文將就調研的過程和成果,爲你們分享一些咱們的經驗。瀏覽器

Flutter 如何調用原生代碼

咱們要作的是在 Flutter 上實現實時音視頻。那麼在開始具體的工做以前,首先須要瞭解 Flutter 是如何調用諸如「獲取媒體設備」這類原平生臺 API 的。bash

上方來自官方的架構圖已經足夠清晰了,Flutter 經過 MethodChannel 發起某一方法的調用,而後原平生臺收到消息後執行相應的實現(Java/Kotlin/Swift/Object-C)並異步地返回結果,以 getUserMedia 爲示例,首先在 Flutter 層中聲明這一方法,具體實現則是經過 MethodChannel 發送一條攜帶調用方法名和相應參數的信息。網絡

Future<MediaStream> getUserMedia(
  Map<String, dynamic> mediaConstraints
) async {
  // 獲取事前統一註冊好的 MethodChannel
  MethodChannel channel = WebRTC.methodChannel();
  try {
    // 經過該 MethodChannel 去調用對應的方法 getUserMedia
    final Map<dynamic, dynamic> response = await channel.invokeMethod(
      'getUserMedia', // 方法名
      <String, dynamic>{'constraints': mediaConstraints}, // 參數
    );
    // 基於異步返回的結果完成封裝一個在 Flutter 層使用的 MediaStream 對象
    String streamId = response["streamId"];
    MediaStream stream = new MediaStream(streamId);
    stream.setMediaTracks(response['audioTracks'], response['videoTracks']);
    // 返回結果
    return stream;
  } on PlatformException catch (e) {
    // 處理異常
    throw 'Unable to getUserMedia: ${e.message}';
  }
}
複製代碼

Future 表示一個異步的調用,相似 Javascript 的Promise;async/await 相似,在一個async 函數中,會相似同步地按順序去執行 await 方法,儘管 await 後面的是異步方法。架構

當平臺在 MainActivity 中一樣註冊 MethodChannel,經過 MethodChannel 收到方法調用的消息和參數後,基於本平臺實現相應邏輯,並返回執行結果,此處僅以 Android 平臺爲例:app

// 註冊 MethodChannel 接收 Flutter 的調用
import io.flutter.app.FlutterActivity;
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;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "FlutterWebRTC";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
		// 註冊 MethodChannel,ChannelName 應與以前 Flutter 中註冊的同名
        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
        		// 提供各個方法的具體實現
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // TODO
                    }
                });
    }
}
複製代碼
// 具體實現
@Override
public void onMethodCall(MethodCall call, Result result) {
	// 若是方法名爲 getUserMedia
	if (call.method.equals("getUserMedia")) {
	    // Android 實現 getUserMedia 方法
	    // ...
	
	    // 成功則返回相關信息
	    // result.success(// 相關信息);
	
	    // 若是失敗則拋出異常
	    // result.error(// 報錯信息);
	
	} else {
	    result.notImplemented();
	}
}
複製代碼

更多詳細的信息能夠參考 Flutter 官方示例與解釋框架

實現音視頻 SDK 的思路

瞭解上述 Flutter 調用原平生臺方法的原理後,咱們就有兩種思路來實現一個音視頻 SDK。異步

1. 先在原平生臺實現音視頻 SDK,後 Flutter 經過 MethodChannel 直接調用 SDK 提供的方法。

具體的方案爲直接經過 MethodChannel 調用已有的聲網Agora SDK,並在 Flutter 層抹去可能存在的差別,諸如參數不一樣、部分方法名不一樣。

這種作法的主要優勢在於能夠最大程度複用已有的 SDK,相似於創建了一層橋接。

2. 先基於原平生臺實現 WebRTC 標準,而後在 Flutter 層經過 MethodChannel 調用 WebRTC 接口,再實現音視頻 SDK 邏輯。

這種方案先利用原平生臺實現 WebRTC 標準(前一節實現的getUserMedia就是此標準的一部分),而後在 Flutter 層註冊爲 WebRTC Plugin。在這個 Flutter WebRTC Plugin 的基礎上參照聲網音視頻 SDK,鏈接到 Agora SD-RTN™ 全球虛擬通信網絡。

這種方案相比前一點,至關於實現一個全新的 Dart 語言的 SDK,須要用到更多 Dart 的標準庫(諸如math、io、convert之類)與第三方生態(如(flutter_webrtc)。假設要支持更多的平臺時(好比 Windows),只須要該平臺實現 WebRTC 標準就能夠直接使用。

熟悉 WebRTC 的同窗們可能知道在實現瀏覽器 WebRTC 應用的時候有一個Adapter 的概念,目的就是爲了掩藏幾大主流瀏覽器 WebRTC 接口的些許差別,和本方案的思路是相似的,只不過適配的平臺從 Firefox/Chrome/Safari 變爲了 Windows/iOS/Android 等。

最終出於調研的目的,同時也是爲了更加迎合 Flutter 一套代碼,多平臺通用的思想(理論上 SDK 就是一層設計完備的客戶端邏輯,在 WebRTC 受良好支持的狀況下,工做的內容就變爲:如何使用 Dart 語言在 WebRTC 的標準上實現音視頻通訊邏輯),咱們選擇採用這個方案,所以讀者可能會發現這個 Flutter SDK 總體上很多概念上更接近於聲網 Web 平臺的音視頻 SDK 一些。

SDK的結構

SDK 的主要功能大體包含了音視頻採集與播放,與 Agora Gateway 創建 P2P鏈接並管理,以及與 Gateway 之間的消息交換和處理。

雖然 Flutter 社區相對較新,可是 Dart 的標準庫能夠算得上是很是完備了,同時也已經有很多優秀的第三方 Plugin 。

代碼能夠主要拆分爲如下模塊:

基於 dart:io 中 Websocket 相關的方法實現與 Gateway 之間的消息通訊(好比publish/subscribe這類消息和回覆)

基於開源社區的 flutter_webrtc 項目實現音視頻採集以及 p2p 鏈接等 WebRTC 相關功能

基於 dart Stream 對象或是簡單的 Map 來實現 EventEmitter 這些 SDK 所需的輔助類(固然也能夠直接採用 Dart 的 Stream/Sink 概念進行替代)。

這些模塊完成後,在此之上就能夠實現相似聲網 Web SDK 中的 Client 與 Stream 對象。

其中值得一說的是視頻流的播放,能夠藉助 flutter_webrtc plugin 中的 RTCVideoView 對象來實現,想要深刻了解具體原理的能夠學習一下 Flutter 外接紋理 (Texture) 相關概念。

到此 SDK 就已經基本造成了,以後即是 UI 層的開發,Flutter 這一部分很大程度上受到了 React 框架的啓發,熟悉該框架的 Web 開發者能夠基於此 SDK 輕鬆的實現一個可運行在 Android/iOS 平臺的視頻通話 App。咱們此前分享過的 demo 已經成功和已有的聲網 Android/iOS/Web SDK 進行互通,相應的代碼也許將在不久將來進行開源。

總結

儘管 Flutter 社區仍然很年輕,可是已經逐漸有很多優秀的第三方插件涌現出來,加上 Dart 相對全面的標準庫,實現這樣一個音視頻 SDK 或是相似的功能並不須要本身大量地去造輪子,加上 Flutter 自己環境搭建/構建/調試都很是的方便,所以整個開發過程當中幾乎沒有遇到什麼坑。

此外在應用層的開發過程當中,風格很是接近於使用 React 進行 Web 開發,加上 Flutter 亞秒級的 Hot Reload 等特性,在開發體驗與效率上相比原生開發確實有着不小的優點。

再考慮到逐漸完善的跨平臺特性(桌面端的 flutter-desktop-embedding 項目與瀏覽器端的 humming bird 項目)以及可能會到來的谷歌新操做系統 Fuchsia,對於不管是想要接觸到原生開發的 Web 開發者,仍是追求更高的開發效率和更好的開發體驗的原生開發者來講,Flutter 都是一個很是適宜的切入角度,值得在新的一年裏加入本身的技術棧中。


想學習更多技術團隊的 Flutter 開發經驗?3月23日,RTC Dev Meetup 北京站 邀請LeanCloud、聲網、大麥網、美團點評技術團隊的工程師與你分享更多點擊瞭解詳情

相關文章
相關標籤/搜索