Android 整合 Flutter 及通訊互動詳解

Songlcy發表於2019-05-24

前不久開源了用Flutter開發的一個音視訊類App客戶端,歡迎大家star, fork。  

⚠️ 專案地址:github.com/songxiaolia…

⚠️ 本篇部落格涉及的原始碼全部開源在Github,地址:github.com/songxiaolia…

目前現有的跨平臺開發解決方案基本分為兩種開發模式: 

 (1)純跨平臺語言開發模式 

 (2)Native + Hybrid 混合開發模式  

在純跨平臺開發方案帶來的高效開發效率的同時,混合開發也為原生App提供了更方便更快捷的功能實現。例如,React Native 熱更新,跨平臺雙端UI檢視等。

瞭解React Native的朋友,對 Android & RN 的開發模式肯定不會陌生,在之前的文章中,我也介紹瞭如何將RN實踐在Android原生App中,詳細內容大家可以檢視《Android 整合 React Native、原生檢視載入RN元件模組分析》。本篇部落格將圍繞跨平臺開發框架 Flutter,詳細介紹如何整合在現有Android原生專案,以及雙端的通訊實現流程。

效果圖

Android 整合 Flutter 及通訊互動詳解

一、Android 整合 Flutter 實現流程

閒魚、頭條 在 Android 整合 Flutter 模組都有自己的實現方案:閒魚團隊方案 & 頭條團隊方案

我們以官方方案方式為主。

1)建立 flutter module 模組

官方提供瞭如下命令,用來建立 flutter module:

flutter create -t module flutter_module (module名稱)複製程式碼

2)將 flutter module 模組新增到當前專案

開啟專案根目錄的 settings.gradle 檔案,新增如下程式碼片段:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        "flutter_in_android/.android/include_flutter.groovy"
))複製程式碼

開啟 app / build.gradle 檔案,在 dependencies 下新增 flutter 依賴:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
 
    ......
 
    implementation project(':flutter')
}複製程式碼

以上配置完成後,Flutter就已經整合在當前Android工程專案中了。

二、Flutter 檢視介面展示

Flutter的檢視展示有兩種實現方式:

(1)建立 FlutterView 檢視元件,以 View 的方式新增到當前原生檢視佈局

官方依賴庫中提供了 createView 的方法,方便開發者快速建立 Flutter 檢視元件,並嵌入在當前原生布局:

/**
 * 此 Activity 中向 Flutter 端傳送訊息
 * create by Songlcy 2019-02-15
 */
public class FlutterContainerActy extends AppCompatActivity {
 
    private ViewGroup.LayoutParams layoutParams;
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flutter);
        // 1. 通過Flutter.createView建立FlutterView元件方式
        FlutterView flutterView = Flutter.createView(this, getLifecycle(), "flutterView");
        layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        // 2. 將Flutter 檢視新增到原生布局中
        addContentView(flutterView, layoutParams);
    }
}複製程式碼

上述程式碼中,我們通過 Flutter.createView 建立 Flutter 檢視元件 FlutterView,createView方法接收三個引數

@NonNull final Activity activity: Activity例項
@NonNull final Lifecycle lifecycle: 定義具有Android生命週期的物件
final String initialRoute: 初始化的檢視路由名稱複製程式碼

所以我們可以根據 initialRoute 來動態載入不同的 Flutter 檢視元件:

import 'dart:ui'; // 引入後可以使用window物件
 
@override
Widget build(BuildContext context) {
  switch(window.defaultRouteName) {
 
    case "flutterView":     
        return Scaffold(...);
 
    ......
 
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}複製程式碼

(2)啟動 FlutterActivity 介面

在很多場景下,我們需要從原生介面跳轉到Flutter檢視介面,所以我們可以直接啟動FlutterActivity來實現:

Intent intent = new Intent(MainActivity.this, FlutterActy.class);startActivity(intent);複製程式碼

三、Android 與 Flutter 通訊方式

React Native跨平臺開發框架是通過 RCTBatchedBridge 實現 js 與 Native 的互動,Flutter與Native的通訊機制與RN的實現比較相似,只是沒有了Bridge的橋接層,通過Channel直接與原生互動。官方在Channel通訊的實現上同樣採用了以字串為唯一協議的方式,來同時構建通訊互動訊號。實現的具體方式和RN也同樣類似,Native | Flutter 端實現監聽回撥,註冊即可。

(1)MethodChannel

使用場景:Flutter端向Native端傳送通知

實現方式:

Native端

new MethodChannel(getFlutterView(), "com.xxx").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        // methodCall.method 對應 Flutter端invokeMethod方法的第一個引數
        if(methodCall.method.equals("123")) {
            // 獲取Flutter傳遞的引數
            String msg = methodCall.<String>argument("msg");
            // 回傳給Flutter
            result.success(msg);
        }
    }
});複製程式碼

上述程式碼中我們建立了MethodChannel例項,並呼叫 setMethodCallHandler 註冊監聽回撥。從原始碼中,可以看到MethodChannel建構函式接收兩個引數

public MethodChannel(BinaryMessenger messenger, String name) {
     this(messenger, name, StandardMethodCodec.INSTANCE);
}
 
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
 ......
}複製程式碼

name 就是雙發通訊的唯一標識,我們可以簡單理解為鑰匙即可。

MethodCodec有兩種實現:

  • JSONMethodCodec
JSONMethodCodec的編解碼依賴於JSONMessageCodec,當其在編碼MethodCall時,會先將MethodCall轉化為字典{"method":method,"args":args}。其在編碼呼叫結果時,會將其轉化為一個陣列,呼叫成功為[result],呼叫失敗為[code,message,detail]。再使用JSONMessageCodec將字典或陣列轉化為二進位制資料。
  • StandardMethodCodec
MethodCodec的預設實現,StandardMethodCodec的編解碼依賴於StandardMessageCodec,當其編碼MethodCall時,會將method和args依次使用StandardMessageCodec編碼,寫入二進位制資料容器。其在編碼方法的呼叫結果時,若呼叫成功,會先向二進位制資料容器寫入數值0(代表呼叫成功),再寫入StandardMessageCodec編碼後的result。而呼叫失敗,則先向容器寫入資料1(代表呼叫失敗),再依次寫入StandardMessageCodec編碼後的code,message和detail。

Flutter端

import 'package:flutter/services.dart';
 
static const methodPlugin = const MethodChannel('com.xxx');
String callbackResult = await methodPlugin.invokeMethod('123', { "msg": "456" });複製程式碼

在Flutter同樣需要建立MethodChannel例項,並將通訊鑰匙作為引數傳入,要與原生端保持一致。然後呼叫invokeMethod方法向原生端傳送通訊請求。第一個參數列示要呼叫原生端的哪個方法,第二個引數為可選引數,即傳遞給Native端的資料引數。

(2)EventChannel

使用場景:Native端向Flutter端傳送通知

實現方式:

Native端

new EventChannel(getFlutterView(), "com.xxx").setStreamHandler(new EventChannel.StreamHandler() {
 
    @Override
    public void onListen(Object o, EventChannel.EventSink eventSink) {
        eventSink.success("msg");
    }
 
    @Override
    public void onCancel(Object o) {
        // 做一些登出操作
    }
});複製程式碼

和 MethodChannel 類似,EventChannel 也是通過 new 建立物件例項,並設定 StreamHandler 型別的監聽回撥。其中 onCancel 代表對面不再接收,這裡我們可以做登出的邏輯操作。onListen 代表通訊已經建立完畢,Native可以向Flutter傳送資料。onListen 方法中攜帶了 EventSink 引數,後續Native傳送資料都是經過 EventSink 的 success、error 方法。

Flutter端 

import 'package:flutter/services.dart';
 
static const eventPlugin = const EventChannel('com.xxx');
 
@override
void initState() {
  super.initState();
  _streamSubscription = eventPlugin.receiveBroadcastStream()
      .listen(_onData, onError: _onError, onDone: _onDone, cancelOnError: true);
}
 
void _onData(Object event) {
  // 接收資料
  setState(() {
    eventVal = event;
  });
}
 
void _onError(Object error) {
  // 發生錯誤時被回撥
  setState((){
    eventVal = "錯誤";
  });
}
 
void _onDone() {
  //結束時呼叫
}
 
@override
void dispose() {
  super.dispose();
  if(_streamSubscription != null) {
    _streamSubscription.cancel();
  }
}複製程式碼

同樣與 MethodChannel 類似,首先是建立 EventChannel 例項,然後在 initState 生命週期中呼叫 receiveBroadcastStream方法的listen。listen 返回的是 StreamSubscription 物件。此處有點類似Android中的BroadcastReceiver廣播。listen方法原始碼如下:

StreamSubscription<T> listen(void onData(T event),
      {Function onError, void onDone(), bool cancelOnError});複製程式碼

可以看到,onData 為必需引數,onError、onDone、cancelOnError 為可選。顧名思義,onData 即為收到原生端傳送資料的回撥,onError為接收資料失敗,onDone為接收資料結束,cancelOnError是一個bool型別引數,標識在發生錯誤時,時候自動取消通訊。以上即可實現Native端向Flutter傳送通知。

總結

Platform Channel 作為原生端與 Flutter 端建立通訊渠道的方式,在混合開發模式中起到了至關重要的作用,很多地方都會涉及,例如編寫 Plugin 等等。不僅能夠幫助我們更深入的瞭解 Flutter 與 Native 之間的互動流程,在效能優化、問題分析上都可以得到延伸。


相關文章