flutter: 2.0.1
kraken: main@b5574e6
现在只是知道了一些预先声明的类类型和类对象的定义(绑定),之前一笔带过提出过问题:js脚本常用的方法如setTimeout,console.log,这些方法是在什么时机,又是如何加载的?
先梳理一下:kraken在创建RenderObject的时机作初始化,主要是js引擎初始化,这里的主要工作就是绑定一些内置对象;接着在创建_KrakenRenderObjectElement节点的时机作了加载指定脚本的工作,那么显然,在加载指定脚本之前,js内置的一切环境就必须作好了准备;那么这些内置方法的初始化必定在创建_KrakenRenderObjectElement节点之前!
以最普遍使用的console.log为例;文本输出虽然是每种语言内置的最基本功能,但它却不简单,因为需要和平台结合起来,可能要调用平台api才能达到控制台输出的效果。不难找到方法在js侧声明的地方(bridge/polyfill/src/kom/console.ts:197),最终调用的是printer方法,当前我们关心的是这坨js代码连同其他用js定义的js方法是在什么时机,通过什么形式加载的。
显然和预置的js相关的代码都在bridge/polyfill目录下。不熟悉js及包管理的人可能找不到入口,一般可以看package.json文件,它声明了依赖还有构建脚本。
"mainToC": "node scripts/js_to_c.js -s ../dist/main.js -o ../dist",
可知在../dist目录下有编译输出:入口文件main.js, 还有cpp文件polyfill.cc, cpp文件非常简单,仅仅生成了一个cpp方法initKrakenPolyFill:
void initKrakenPolyFill(kraken::JSBridge *bridge) {
bridge->evaluateScript(jsCode, "internal://", 0);
}
这一切是如此的显而易见,polyfill目录这一坨js都被编译成main.js,它的内容直接作为文本字符串被kraken::JSBridge加载执行,查看这个方法的引用,正是紧接着内置对象的初始化,在那一坨bind方法之后(bridge/bridge_jsc.cc:109)! (但是把这一大坨常量字符串打包进so库总感觉怪怪的)
initKrakenPolyFill这个生成方法是通过编译期脚本任务compile-polyfill(scripts/tasks.js:183)将bridge/polyfill目录下的代码编译成可被c++调用的生成代码,在生成动态链接库的时候再编译这个文件,于是专门作内置脚本预加载
的方法initKrakenPolyFill就准备好了。
节点对应
聚焦最后一个问题:js生成节点如何与dart控件对应, 也就是说在js侧的代码var text1 = document.createTextNode('Hello World!');最终如何传递并反映到dart侧的变化的. 因为document是cpp侧声明和定义的对象,所以直接找到对应的cpp代码JSValueRef JSDocument::createTextNode(bridge/bindings/jsc/DOM/document.cc:87)调用序列如下:
document.createTextNode //js
JSDocument::createTextNode //cpp
JSTextNode::instance
JSTextNode()
JSTextNode::instanceConstructor
TextNodeInstance()
UICommandTaskMessageQueue::instance()->registerCommand
生成TextNode这个操作被UICommandTaskMessageQueue保存了起来, 在dart侧被打包取出批量进行操作:
flushUICommand
_getUICommandItems
foundation::UICommandTaskMessageQueue::instance //cpp
KrakenViewController.createTextNode
ElementManager.createTextNode
TextNode()
所有的节点操作在dart侧都被预先定义成枚举UICommandType(kraken/lib/src/bridge/to_native.dart:199), 通过数值方式的完成了cpp到dart的一一对应, 于是最终生成了一个dart侧TextNode对象.