Flutter多Engine之間的通訊

realze發表於2021-03-05

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')註解來保證不會在編譯之後方法被優化了

相關文章