最右JS2Flutter框架——通信机制

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"1、概述"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"通信包括异步和同步两种方式,异步可根据是否关注返回结果再细分成Request-Reply和One-Way两种。JS2Flutter框架的通信机制也是在不断的迭代中逐步完善,年初发的文章"},{"type":"link","attrs":{"href":"https://www.infoq.cn/article/MIa5AN2JE51uor4JeiPG","title":""},"content":[{"type":"text","text":"Flutter动态化在最右App中的实践"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[1]中还是通过对JSWorkThread的阻塞,去实现Client侧到Host侧的单向同步,如今已经完成了Client和Host之间真正意义上的双向同步直连。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"2、异步"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"2.1 One-Way"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"对於单向无需关注返回结果的调用,实现起来很简单,在"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/acee65b914dc4d0e32a5561a1","title":""},"content":[{"type":"text","text":"最右JS2Flutter框架——开篇"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[2]中,我们讲述了Hello World实现的例子,这就是一个最简单的One-Way调用,Client和Native之间可以通过建立通信,Native和Host之间可以通过PlatformChannel建立通信,这样就完成Client和Host之间的通信。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"2.2 Request-Reply"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"对于One-Way的调用,很容易就可以实现,假如我们需要一个返回结果呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"举个例子,开发者想要获取剪切板数据,调用了Clipboard.getData,在运行阶段这个函数是在JS运行的,它必须向Host侧真实的Clipboard.getData发起调用,Host侧在拿到结果之后,通知Client侧,Client侧把结果给调用者。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"很容易想到Client侧通过JSCore向Native注册回调函数,Native通过MethodChannel设置MethodCallHandler,当Native收到Host侧的回调时,Native调用Client侧的回调函数。JS2Flutter框架就是这样去实现的,但却又不止于此,仅仅使用回调函数来做异步的话,当遇到大量的异步回调场景时,很可能陷入\"回调地狱\",其实Dart为了避免类似的问题,引入了Future,JS也引入了Promise。回调函数只作为Native回调Client侧的一个句柄,真正在框架中发挥威力的是通过Future和Completer去实现的callFlutterWithReply。"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Map _completerMap = new Map();\n\nFuture callFlutterWithReply(String method, { dynamic key, String action, dynamic data }) async {\n int callbackId = generateGlobalId();\n invokeFlutter(method, _buildBody(key, action, data, callbackId));\n Completer completer = Completer();\n _completerMap[callbackId] = completer;\n T typedResult = await completer.future;\n return typedResult;\n}\n\n//当收到Native回调\n...\n Completer completer = _completerMap[callbackId];\n if(completer != null) {\n completer.complete(data);\n _completerMap.remove(callbackId);\n }\n..."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"至此,Client侧面向Host侧的异步调用便建立起来了,同理,可建立Host侧面向Client侧的异步调用,这样就完成了双向的异步通信机制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"3、同步"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"同步的需求场景也很多,我们在"},{"type":"link","attrs":{"href":"https://www.infoq.cn/article/MIa5AN2JE51uor4JeiPG","title":""},"content":[{"type":"text","text":"Flutter动态化在最右App中的实践"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[1]中也有列举,比如你要通过TextPainter来测量文字的高度,是需要同步机制才能保证正确性的,当时我们是通过阻塞JSWorkThread去实现Client侧的同步,因为这是一个与UI绘制无关的线程,所以它的阻塞并不影响渲染和事件接收,但后来我们发现同步机制不仅是Client侧需要的,Host侧也有可能需要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"举个例子,我们在实现ListView控件时,如果是通过builder方式去构建,当遇到大量数据的时候,因为每一项都是实时构建,需要先询问Client侧该位置的子树并立即还原,如果是异步去刷新的话,很可能后几项先显示出来,从而造成视觉上的错乱。所以,在Host侧我们也需要建立同步机制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"那在Host侧是否可以采用同样的方案建立同步机制呢?当然也是可行的,但最右没有采用这种方案,试想一下,本来JS到Flutter就需要经历JSWorkThread、MainThread、Flutter UIThread,再来一层循环阻塞,效率肯定是大打折扣。我们采用了更高效的方式,让JS运行在Flutter的UI线程里,构建一套Client和Host之间直连的通信渠道,这样就能实现真正意义上的双向同步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"这个过程中我们需要解决两个问题:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"1:如何让代码块儿能在Flutter UI线程执行?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"2:怎样才能建立直连通信渠道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"要解决第一个问题,我们需要了解Flutter UI线程Work的方式,这里直接借用Gityuan的博客——"},{"type":"link","attrs":{"href":"http://gityuan.com/2019/06/15/flutteruidraw/","title":""},"content":[{"type":"text","text":"Flutter渲染机制——UI线程"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[3],Engine是通过UITaskRunner提交任务,从而让任务在UI线程执行。理解了这一点,我们就可以扩充FlutterEngine的能力,增加一个postTaskOnUIThread函数,XCJSRuntime在启动时,让JS运行在Flutter UI线程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"第二个问题相对要复杂的多,牵涉到的面也比较广,我们先看看系统是如何建立通信渠道的,不了解的同学可以看看Gityuan的博客——"},{"type":"link","attrs":{"href":"http://gityuan.com/2019/08/10/flutter_channel/","title":""},"content":[{"type":"text","text":"深入理解Flutter的Platform Channel机制"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[4],理解了系统的流程(博客以Android为例,iOS侧同理)之后,首先要思考的就是我们与系统不一样的地方,Platform Channel是为了在Flutter和Platform之间建立通信渠道,它们在不同的线程,而我们是在同一个线程,所以我们不需要再去转线程。举个例子:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewDispatchPlatformMessage(\n fml::RefPtr message) {\n FML_DCHECK(is_setup_);\n FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n task_runners_.GetUITaskRunner()->PostTask(\n [engine = engine_->GetWeakPtr(), message = std::move(message)] {\n if (engine) {\n engine->DispatchPlatformMessage(std::move(message));\n }\n });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewDispatchDirectMessage(\n fml::RefPtr message) {\n FML_DCHECK(is_setup_);\n FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n engine_->DispatchDirectMessage(std::move(message));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"有了这个基础之后,我们就可以在MethodChannel上拓展一个invokeDirectMethod函数,参照系统的实现方式,修改Dart层和Engine层,修改的路线也基本就是MethodChannel的调用流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"这里我摘取了_DefaultBinaryMessenger中增加直连的修改,其后续的实现步骤基本上都是跟随着PlatformChannel的流程,增加直连的函数。"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"class _DefaultBinaryMessenger extends BinaryMessenger {\n const _DefaultBinaryMessenger._();\n\n // Handlers for incoming messages from platform plugins.\n // This is static so that this class can have a const constructor.\n static final Map _handlers =\n {};\n\n static final Map _directHandlers =\n {}; \n\n // Mock handlers that intercept and respond to outgoing messages.\n // This is static so that this class can have a const constructor.\n static final Map _mockHandlers =\n {};\n\n Future _sendPlatformMessage(String channel, ByteData message) {\n final Completer completer = Completer();\n // ui.window is accessed directly instead of using ServicesBinding.instance.window\n // because this method might be invoked before any binding is initialized.\n // This issue was reported in #27541. It is not ideal to statically access\n // ui.window because the Window may be dependency injected elsewhere with\n // a different instance. However, static access at this location seems to be\n // the least bad option.\n ui.window.sendPlatformMessage(channel, message, (ByteData reply) {\n try {\n completer.complete(reply);\n } catch (exception, stack) {\n FlutterError.reportError(FlutterErrorDetails(\n exception: exception,\n stack: stack,\n library: 'services library',\n context: ErrorDescription('during a platform message response callback'),\n ));\n }\n });\n return completer.future;\n }\n\n ByteData _sendDirectMessage(String channel, ByteData message) {\n return ui.window.sendDirectMessage(channel, message);\n }\n\n\n @override\n Future handlePlatformMessage(\n String channel,\n ByteData data,\n ui.PlatformMessageResponseCallback callback,\n ) async {\n ByteData response;\n try {\n final MessageHandler handler = _handlers[channel];\n if (handler != null) {\n response = await handler(data);\n } else {\n ui.channelBuffers.push(channel, data, callback);\n callback = null;\n }\n } catch (exception, stack) {\n FlutterError.reportError(FlutterErrorDetails(\n exception: exception,\n stack: stack,\n library: 'services library',\n context: ErrorDescription('during a platform message callback'),\n ));\n } finally {\n if (callback != null) {\n callback(response);\n }\n }\n }\n\n\n @override\n void handleDirectMessage(\n String channel,\n ByteData data,\n ui.DirectMessageResultCallback callback,\n ) async {\n ByteData result;\n try {\n final DirectMessageHandler handler = _directHandlers[channel];\n if (handler != null)\n result = handler(data);\n } catch (exception, stack) {\n FlutterError.reportError(FlutterErrorDetails(\n exception: exception,\n stack: stack,\n library: 'services library',\n context: ErrorDescription('during a direct message callback'),\n ));\n } finally {\n callback(result);\n }\n }\n\n\n @override\n Future send(String channel, ByteData message) {\n final MessageHandler handler = _mockHandlers[channel];\n if (handler != null)\n return handler(message);\n return _sendPlatformMessage(channel, message);\n }\n\n @override\n ByteData sendDirect(String channel, ByteData message) {\n return _sendDirectMessage(channel, message);\n }\n\n @override\n void setMessageHandler(String channel, MessageHandler handler) {\n if (handler == null)\n _handlers.remove(channel);\n else\n _handlers[channel] = handler;\n ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {\n await handlePlatformMessage(channel, data, callback);\n });\n }\n\n @override\n void setDirectMessageHandler(String channel, DirectMessageHandler handler) {\n if (handler == null)\n _directHandlers.remove(channel);\n else\n _directHandlers[channel] = handler;\n }\n\n ...\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"4、结束语"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"本⽂阐述了最右JS2Flutter框架的通信机制,基本上都是参照了Flutter的实现⽅式,对于同步直连的实现过程,涉及到的环节比较多,也只阐述了思想,对细节实现感兴趣的同学可追溯源码一探究竟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":14}},{"type":"strong"}],"text":"5、参考文献"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[1]:Flutter动态化在最右App中的实践 "},{"type":"link","attrs":{"href":"https://www.infoq.cn/article/MIa5AN2JE51uor4JeiPG","title":""},"content":[{"type":"text","text":"https://www.infoq.cn/article/MIa5AN2JE51uor4JeiPG"}],"marks":[{"type":"size","attrs":{"size":12}}]},{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[2]:JS2Flutter框架——开篇 "},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/acee65b914dc4d0e32a5561a1","title":null},"content":[{"type":"text","text":"https://xie.infoq.cn/article/acee65b914dc4d0e32a5561a1"}],"marks":[{"type":"size","attrs":{"size":12}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[3]:Flutter渲染机制——UI线程 "},{"type":"link","attrs":{"href":"http://gityuan.com/2019/06/15/flutter_ui_draw/","title":null},"content":[{"type":"text","text":"http://gityuan.com/2019/06/15/flutter_ui_draw/"}],"marks":[{"type":"size","attrs":{"size":12}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"[4]:深入理解Flutter的Platform Channel机制 "},{"type":"link","attrs":{"href":"http://gityuan.com/2019/08/10/flutter_channel/","title":null},"content":[{"type":"text","text":"http://gityuan.com/2019/08/10/flutter_channel/"}],"marks":[{"type":"size","attrs":{"size":12}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章