Flutter 构建PlayGround极速方案

2022-07-11  本文已影响0人  程序员要多喝水

1. 背景介绍

最近无聊逛b站,发现企业微信关于Flutter的分享中提到了RPC服务解耦问题,详见

image.png image.png

此外还有哈罗出行公开也提到 类似方案

image.png

无独有偶整个方案最早应该追溯到我2020年曾经在闲鱼技术公众号上发布的一段相关文章 闲鱼对 Flutter-Native 混合工程解耦的探索

其实原理概括而言比较简答,建立一个playGround的壳工程,其服务如果壳工程未定义可以通过RPC方式去宿主APP去拿服务,保证所有的Channel数据可以正常获取即可,方式设计如下图:

[图片上传失败...(image-875cf6-1657469153187)]

当然此方案会有一些无法处理的,比如关于壳工程PlayGround内存级别信息以及个别手机相关服务,比如状态栏高度;当然这次写文章不是老生长谈把2020年时候已经发布公开的文章方案在拿出来给大家在分享一次,之前分享的方案主要是自己去修改Flutter FrameWork 关于Service Channel相关的代码,这次主要给大家分享一个我个人写的插件,无需修改Flutter具体代码,带上example来瞅瞅我是怎么做到的~

2. 具体方案

话不多说上具体代码地址: https://github.com/1036711153/flutter_mock_service

方案的问题关键点就是如何去Hook住Flutter Channel服务能力,其实Flutter Channel相关能力在
ServicesBinding中,熟悉Flutter同学都知道Flutter通过mixin能力将多种binding服务组合,因此我们可以通过类似方案实现无修改Flutter源码情况下实现类似效果:
我们定义一个ServiceFlutterBinding去继承ServicesBinding的混合服务,详情见代码:https://github.com/1036711153/flutter_mock_service/tree/main/lib/binding

mixin ServiceFlutterBinding on ServicesBinding {
  @override
  BinaryMessenger createBinaryMessenger() {
    // TODO: implement createBinaryMessenger
    return _DefaultBinaryMessenger._();
  }
}

class _DefaultBinaryMessenger extends BinaryMessenger {
  _DefaultBinaryMessenger._();

  MethodCodec codec = const StandardMethodCodec();

  
 @override
 Future<ByteData?> send(String channel, ByteData? message) {
  ...
  //这里hook代码
  ...
  return _sendPlatformMessage(channel, message);
 }

}

然后定义CustomServiceFlutterBinding去继承WidgetsFlutterBinding并且通过with方式将ServicesBinding相关的能力转交给ServiceFlutterBinding上,这样我们就可以完美的hook住Flutter系统的服务能力了,详情见代码:https://github.com/1036711153/flutter_mock_service/blob/main/lib/binding/custom_service_flutter_binding.dart

///hook住flutter-channel服务
class CustomServiceFlutterBinding extends WidgetsFlutterBinding with ServiceFlutterBinding {
  ...

  CustomServiceFlutterBinding() {
  }
}

这里第一步Hook服务能力是方案介绍完毕了,现在有了Hook如何建立RPC能力呢?
个人方式直接使用HTTP服务,在宿主APP中开启一个端口为18888的server服务建立任何Post请求,然后返回给壳工程服务能力,在Dart中开启一个server服务也比较简单,代码如下,具体代码:https://github.com/1036711153/flutter_mock_service/blob/main/lib/handler/channel_mock_handler.dart#L29

///指定端口启动服务
Future<void> initPortMockService(int port) async {
  HttpServer.bind(InternetAddress.anyIPv6, port, shared: true).then((server) {
    server.listen((HttpRequest request) async {
      String method = request.method;
      Map<String, dynamic> result = Map<String, dynamic>();
      if (method == 'POST') {
         ...
         ///channel数据处理逻辑
         ...
      }

      String printJson = const JsonCodec().encode(result);
      request.response.write(printJson);
      request.response.close();
    });
  });
}

在壳工程中通过Dio封装的HTTP服务然后就可以拿到服务了,代码如下:

Future<String?> getHttpDioResult(String params) async {
  if (sMockWay == MOCK_WAY.MOCK_JSON) {
    if (sMockJsonPluginResult.containsKey(params)) {
      return sMockJsonPluginResult[params];
    }
  }
  int startTime = Timeline.now;
  int usePort = mockPortNum;
  Dio dio = Dio();
  dio.options.contentType = 'application/json';
  dio.options.connectTimeout = 60000;
  dio.options.receiveTimeout = 60000;
  dio.options.method = 'post';
  dio.options.baseUrl = 'http://$serverHost:$usePort';

  Response response = await dio.post('', data: '$params').catchError((e) {
    if (e.toString().contains('SocketException: OS Error:')) {
      errorCallBack?.call(e);
    }
  });
  String? result;
  if (response != null && response.statusCode == HttpStatus.ok) {
    String serverResponse = response.data;
    int dif = Timeline.now - startTime;
    if (dif >= 3000000) {
      printLog('Warning Process CostTime == $dif , params == $params');
    }
    printLog('usePort = $usePort , params  = $params , serverResponse = $serverResponse ');
    result = response.data;
  } else {
    result = null;
  }
  if (sMockWay == MOCK_WAY.MOCK_JSON_HTTP_GENERATE) {
    sMockJsonPluginResult[params] = result;
  }
  return result;
}

有了上述的两个介绍,当我们启动如下代码,一个做宿主APP,一个做壳工程PlayGround运行即可:

void main() {
  ///宿主APP启动主工程服务
  startFlutterMockService();
  runApp(const MaterialApp(
    home: MyApp(),
  ));
}
void main() {
  ///壳工程启动Hook服务
  serverHost = '宿主APP的IP地址';
  CustomServiceFlutterBinding();
  runApp(const MaterialApp(
    home: MyApp(),
  ));
}

3. 延伸扩展

也许有人发现我发的github工程readme不是关于介绍壳工程 RPC 主工程服务的介绍,而是介绍了一个CI相关的事情,没错这个本方案后续的延伸关于如何低成本实现业务CI的方案,详情见下一篇文章关于CI的建设;

上一篇 下一篇

猜你喜欢

热点阅读