前不久開源了用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 模組都有自己的實現方案:閒魚團隊方案 & 頭條團隊方案
我們以官方方案方式為主。
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 之間的互動流程,在效能優化、問題分析上都可以得到延伸。