Flutter多Engine之間的通訊
通過Channel走一遍原生
各個Engine都通過Channel與原生建立訊息通道,即訊息的傳遞是這樣的
Flutter->原生->Flutter
複製程式碼
優點:
- 開發比較方便,功能明確
缺點:
- 超過2個Engine之間的通訊的時候需要確定訊息的傳送方和接收方是在哪個Engine,當然可以通過給每個Engine分配id來解決這個問題
- 功能變動的時候,可能需要修改原生和Flutter的程式碼,比較麻煩
- 訊息需要走一遍原生,所以需要經過2次encode和2次decode,效率比較低
Flutter->encodeMethodCall->原生->decodeMethodCall->原生解析確定傳送到哪個Engine->encodeMethodCall->Flutter->decodeMethodCall->獲取到傳送的資料
複製程式碼
通過HttpServer
HttpServer在本地開啟一個網路服務,相當於一個本地伺服器,Java和Flutter通過http請求來訪問
優點:
- 訊息不需要經過原生一遍轉發,效率比較高
- 訊息通訊不需要確定傳送方和接收方,因為HttpServer開啟的時候繫結了埠,埠是系統唯一的
缺點:
- 需要佔用本地埠,所以埠繫結的時候需要檢測埠是否被佔用
例子(有點懶,所以在自動建立的demo上做了修改,點選增加數字的時候傳送請求)
- dart開啟HttpServer
void serviceStart() async {
HttpServer.bind(InternetAddress.anyIPv4, 4049, shared: true).then((service) {
print("service ${service.address} ${service.port}");
service.listen((request) async {
//HttpResponse物件用於返回客戶端
print(request.uri);
switch (request.method) {
case "POST":
var result = await request
.cast<List<int>>()
.transform(utf8.decoder)
.join()
.then(json.decode);
print("POST $request");
request.response
//獲取和設定內容型別(報頭)
..headers.contentType = ContentType.json
//通過呼叫Object.toString將Object轉換為一個字串並轉成對應編碼傳送到客戶端
..write(json.encode({
"id": "sssss",
"title": "response hello post",
}))
//結束與客戶端連線
..close();
break;
case "GET":
request.response
..headers.contentType = ContentType.json
..write(json.encode(
{"id": "idxxxxxx", "title": "GET response "}))
..close();
break;
}
});
});
}
複製程式碼
- 在另外的Engine中傳送訊息
void sendMessage() async {
var dio = Dio();
var response;
if (_counter % 2 == 0) {
response = await dio.get("http://127.0.0.1:4049");
} else {
response =
await dio.post("http://127.0.0.1:4049", data: {"dddd": "post 999"});
}
print("=====client response=====");
print(response.data);
}
複製程式碼
- 完整的例子
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
@pragma('vm:entry-point')
void service() {
serviceStart();
}
void serviceStart() async {
HttpServer.bind(InternetAddress.anyIPv4, 4049, shared: true).then((service) {
print("service ${service.address} ${service.port}");
service.listen((request) async {
//HttpResponse物件用於返回客戶端
print(request.uri);
switch (request.method) {
case "POST":
var result = await request
.cast<List<int>>()
.transform(utf8.decoder)
.join()
.then(json.decode);
print("POST $request");
request.response
//獲取和設定內容型別(報頭)
..headers.contentType = ContentType.json
//通過呼叫Object.toString將Object轉換為一個字串並轉成對應編碼傳送到客戶端
..write(json.encode({
"id": "sssss",
"title": "response hello post",
}))
//結束與客戶端連線
..close();
break;
case "GET":
request.response
//獲取和設定內容型別(報頭)
..headers.contentType = ContentType.json
//通過呼叫Object.toString將Object轉換為一個字串並轉成對應編碼傳送到客戶端
..write(json.encode({"id": "idxxxxxx", "title": "GET response "}))
//結束與客戶端連線
..close();
break;
}
});
});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void sendMessage() async {
var dio = Dio();
var response;
if (_counter % 2 == 0) {
response = await dio.get("http://127.0.0.1:4049");
} else {
response =
await dio.post("http://127.0.0.1:4049", data: {"dddd": "post 999"});
}
print("=====client response=====");
print(response.data);
}
void _incrementCounter() async {
setState(() {
_counter++;
});
sendMessage();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
複製程式碼
- 在Java層啟動兩個Engine
//繼承FlutterActivity,預設啟動的時候會執行main.dart中的main方法
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
service()
//延遲10秒執行,防止dart中的服務還沒開啟,懶得做個按鈕點選觸發了
Handler().postDelayed({
Thread {
//簡單的網路請求,當然可以換成okhttp來請求
var inp = URL("http://127.0.0.1:4049").openConnection().getInputStream()
val bis = BufferedInputStream(inp)
val buf = ByteArrayOutputStream()
var result = bis.read()
while (result != -1) {
buf.write(result)
result = bis.read()
}
var response = buf.toString("UTF-8")
buf.close()
bis.close()
inp.close()
Log.d("MainActivity", "response $response")
}.start()
}, 10000);
}
//啟動一個沒有UI的 Engine來啟動main.dart中的service方法
fun service(){
var flutterEngine = FlutterEngine(this);
flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint(
FlutterMain.findAppBundlePath(),
"service"
))
}
}
複製程式碼
結語
可以看出使用HttpServer可以讓通訊變成http請求,所以可以解耦多個業務之間的程式碼,將所有業務的資料管理儲存放在一個統一的地方。
後續優化:可以定義一個資料介面,各個需要對外暴露資料的業務實現這個介面,然後註冊到HttpServer中,通過路由管理介面的訪問。
額外知識點
- DartEntrypoint:可以定義載入的Flutter的so和dart的入口函式。
入口函式除了main之前的方法需要新增@pragma('vm:entry-point')註解來保證不會在編譯之後方法被優化了