flutter: 2.0.1
kraken: main@b5574e6
引擎初始化
接上一篇,第3步也只是创建了_KrakenRenderObjectElement对象,没有其他操作,所以所有的关键就在第2步,创建绘制对象这个方法了。
2 _KrakenRenderObjectWidget.createRenderObject
KrakenController()
2.1 KrakenNavigationDelegate()
2.2 KrakenViewController()
2.3 KrakenModuleController()
2.4 KrakenViewController.getRootRenderObject
第2步最重要的操作就是创建KrakenController这个对象无疑了,总体可以分为以上4个步骤,在KrakenController内部又创建3个对象,其中最重要的是KrakenViewController, 最后一步createRenderObject最终返回的也是KrakenViewController中的RootRenderObject. 展开调用栈,基本可以确定js引擎初始化就是在这个时机进行的了。
2 _KrakenRenderObjectWidget.createRenderObject
KrakenController()
2.1 KrakenNavigationDelegate()
2.2 KrakenViewController()
initBridge():Int (bridge.dart)
2.2.1 registerDartMethodsToCpp
2.2.2 Future.microtask
..SchedulerBinding.instance.addPersistentFrameCallback
2.2.3 initJSContextPool
_initJSContextPool
'initJSContextPool'
2.2.4 KrakenViewController.createViewport
RenderViewportBox()
2.2.5 ElementManager()
2.3 KrakenModuleController()
2.4 KrakenViewController.getRootRenderObject
KrakenViewController的构造方法里就3个重要方法: initBridge, createViewport以及创建ElementManager对象了,在initBridge里注册了cpp到dart的回调2.2.1, 接着监听绘图第一帧2.2.2, 最重要的是2.2.3直接通过ffi深入调用到cpp层,然后创建视口管理对象2.2.4,最后创建ElementManager对象2.2.5。
可以肯定js引擎是在创建KrakenViewController时通过ffi的initJSContextPool方法完成初始化的。所以第2个问题也解决了。但是问题并没有结束,在js侧必然有很多预先定义好的方法和函数,比如setTimeout,console.log;同时也存在可以直接引用的对象如document, window;那这些内置对象是什么时机创建好的呢?所以就必须再深入cpp侧了。
内置对象初始化
代码在名称上保持了一致性,非常容易找到initJSContextPool在cpp侧对应方法(bridge/kraken_bridge.cc:86)
initJSContextPool
JSBridge::JSBridge
context = binding::jsc::createJSContext()
bindKraken()
bindUIManager()
bindConsole()
bindDocument()
bindWindow()
从JSBridge构造方法内引用的名称上已经非常明了,就是在这个时机实现了诸如document,window等内置对象的初始化!以document为例:
void bindDocument(std::unique_ptr<JSContext> &context) {
auto document = JSDocument::instance(context.get());
JSC_GLOBAL_SET_PROPERTY(context, "Document", document->classObject);
auto documentObjectRef =
document->instanceConstructor(context->context(), document->classObject, 0, nullptr, nullptr);
JSC_GLOBAL_SET_PROPERTY(context, "document", documentObjectRef);
}
代码不复杂,先创建了cpp侧的JSDocument一个实例,再通过宏JSC_GLOBAL_SET_PROPERTY进行绑定,这个细节不用再细看,最终一定是调用js引擎提供的相关方法,等于在js侧声明了一个叫做Document的类型,最后又定义了一个document的对象。这个对象是由JSDocument::instanceConstructor()提供的于是继续深入:
bindDocument
JSDocument::instance
JSC_GLOBAL_SET_PROPERTY("Document",)
JSDocument::instanceConstructor
DocumentInstance::DocumentInstance
getDartMethod()->initDocument
_initDocument (kraken/lib/src/bridge/from_native.dart:327)
ElementManager.documentNativePtrMap
JSC_GLOBAL_SET_PROPERTY("document",)
DocumentInstance构造方法的内容有点多,其实就是再添加一个内置对象,同样的再通过引擎接口在js侧声明一个Body类型和body对象。但最重要的其实是最后一句initDocument,它又同步的调用回到了dart侧, cpp到dart的各种方法正是在步骤2.2.1中注册的!dar侧一个指向cpp中JSDocment的指针保存到了ElementManager的静态变量里;而ElementManager的初始化则是调用步骤2.2.5,显然创建好cpp中JSDocument的时候ElementManager还没有创建,需要把所依赖的变量保存起来。
这时候再看ElementManager的初始化再自然不过,首当其冲的就是一个BodyElement的初始化,body又被Document持有(kraken/lib/src/dom/element_manager.dart:121), 而Document对象创建的时候参数除了body对象,正是在此时引用了引擎初始化(内置对象初始化)时保存的cpp对象引用documentNativePtrMap!那这样基本就可以串起来了:
js侧的内置对象通过cpp侧创建,被dart侧的对象持有,这样以保证不同语言之间对象的一一对应关系。
在时序上也能明白这里的弯弯绕:Dart侧Document对象需要contextId,contextId必须得在js引擎初始化好才能获得,引擎初始化的过程中又创建了js侧的各种内置Document对象,内置对象本身又需要被dart侧Document对象持有(一一对应),所以只好先通过ffi回传给dart侧,先用全局变量持有。
于是有bodyNativePtrMap,documentNativePtrMap,windowNativePtrMap一干全局数据来保存这些对象引用(指针),为了要区分不同的js引擎实例用了map;这种方式没有任何设计可言,纯粹的来一个记一个这种,当前创建的明明就是单个实例,通过面向对象可以处理的,全局变量在任何时候都是要绝对避免。
以此类推,其他的如Element,Window对象都是通过这种方式初始化(js->cpp->dart)的. 于是js侧有个Document, cpp有个JSDocument和DocumentInstance,dart也有个Document. 虽然不同语言的对象在不同的细微时机创建,但都是js初始化这一个大过程中完成一一对应的, 可以说内置对象都是创建即绑定.
这一过程下来其实也体现了kraken的核心思想: 就是建立从js到c++再到dart的节点一一对应,于是js侧的节点变更就可反映到dart侧对应的节点对象的变更,最终引起视图变化。