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


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

无独有偶整个方案最早应该追溯到我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的建设;
网友评论