轻量级鸿蒙组件的绘制流程
鸿蒙组件的绘制流程
1.介绍
鸿蒙轻量级智能穿戴开发,提供了一种像 “微信小程序” 式的跨平台开发框架,通过 Toolkit 将应用代码编译打包成 JS Bundle,解析并生成原生 UI 组件。
一个轻量级鸿蒙的页面是由三部分:
- .hml 界面元素
- .css 界面的风格
- .js 用来编辑处理数据和逻辑
2.原理
通过鸿蒙的DevEcoStudio我们可以新建一个包含hello world 的项目,编译后会生成一个文件,位置:
build\intermediates\js\debug\normal\lite\assets\js\default\pages\index\index.js
index.hml 变成了创建函数:
module.exports = function (vm) { var _vm = vm || this; return _c( 'div', { 'staticClass' : [ "container"] } , [_c('text', { 'attrs' : { 'value' : function () { return decodeURI('Hello%20') + (_vm.title)}}, 'staticClass' : ["title"]} )] ) }
index.css 变成了json文件:
module.exports = {"classSelectors":{"container":{"display":"flex","justifyContent":"center","alignItems":"center","left":0,"top":0,"width":454,"height":454},"title":{"fontSize":30,"textAlign":"center","width":200,"height":100}}}
把 XML/CSS 转换成了 JS 代码和 JSON 对象
2.1控件的创建
src\core\modules\presets\render_module.cpp
调用js中的_C函数的时候,就会调用C++中的 RenderModule::CreateElement;
将控件的别名转换成控件的id:
uint16_t componentNameId = KeyParser::ParseKeyId(componentName, tagNameLength);
然后通过控件的id,由组件工厂去创建对应的C++组件:
Component *component = ComponentFactory::CreateComponent(componentNameId, options, children);
最后由 comment 去渲染控件,以及子控件(真正的实现)
bool renderResult = component->Render();
2.2控件的绘制
src\core\components\component.cpp
接上面组件的render自动触发相关函数的调用顺序:
- Component::Render()
- Component::ParseOptions()
- Component::ParseAttrs(attrs)
具体的相关实现方法可以看源代码
2.3页面的绘制过程
src\core\router\js_page_state_machine.cpp
第一个_c函数的绘制是在进入第一个页面的时候,调用StateMachine::RenderPage()函数,对应的代码如下:
jerry_value_t element = appContext_->Render(viewModel_);rootComponent_ = ComponentUtils::GetComponentFromBindingObject(element);// append scroll layer to the outermost viewscrollLayer_ = new ScrollLayer();if (scrollLayer_ != nullptr) { scrollLayer_->AppendScrollLayer(rootComponent_);}Component::HandlerAnimations();
这里调用了 js的 render 函数,对应的位置 和末尾处调用的 commponent 的 handleAnimations(),js 的render函数代码如下:
src\core\context\js_app_context.cpp
jerry_value_t JsAppContext::Render(jerry_value_t viewModel) const{if (jerry_value_is_error(viewModel)) { HILOG_ERROR(HILOG_MODULE_ACE, "Failed to render app cause by render error."); return UNDEFINED;}if (jerry_value_is_undefined(viewModel)) { HILOG_ERROR(HILOG_MODULE_ACE, "Nothing to render as it is undefined."); return UNDEFINED;}jerry_value_t renderFunction = jerryx_get_property_str(viewModel, ATTR_RENDER);if (jerry_value_is_undefined(renderFunction)) { ACE_ERROR_CODE_PRINT(EXCE_ACE_ROUTER_REPLACE_FAILED, EXCE_ACE_PAGE_NO_RENDER_FUNC); return UNDEFINED;}jerry_value_t nativeElement = CallJSFunction(renderFunction, viewModel, nullptr, 0);if (jerry_value_is_undefined(nativeElement)) { ACE_ERROR_CODE_PRINT(EXCE_ACE_ROUTER_REPLACE_FAILED, EXCE_ACE_PAGE_RENDER_FAILED);}jerry_release_value(renderFunction);return nativeElement;}
这里面传入了一个viewModel对象,得到了一个 renderFunction,那么renderFunction是什么函数,要去看看ViewModel类的构造,代码如下:
packages\runtime-core\src\core\index.js
export function ViewModel(options) {startTracing(PHASE_FWK_EVAL);if (!(this instanceof ViewModel)) {return new ViewModel(options);}const vm = (this._vm = this);if (Object.prototype.toString.call(options) === '[object Object]') {Object.keys(options).forEach(key => { const value = options[key]; if (key === 'render') { vm.$render = value; } else if (key === 'data') { initState(vm, value); } else if (key === 'styleSheet') { initStyleSheet(value); } else if (typeof value === 'function') { vm[key] = value.bind(vm); } else { // do nothing }});}stopTracing();}
主要代码是通过遍历options的对应key的节点去知晓对应的参数解析,如果是数据,则由 initState(vm, value);去管理,如果是 styleSheet,对应的是CSS代码,则由initState(vm, value);中的AppStyleManager 和相关的类去处理属性相关的代码,最后,函数相关的则统一通过绑定到vm上,那么options是怎么来的,我们来看看上面所提到的编译后的js代码:
var $app_template$ = __webpack_require__()var $app_style$ = __webpack_require__()var $app_script$ = __webpack_require__(")var options=$app_script$ if ($app_script$.__esModule) { options = $app_script$.default; } options.styleSheet=$app_style$ options.render=$app_template$; module.exports=new ViewModel(options);
到此,一个控件的完整绘制流程就很清晰了。
本文参考:
小程序原理
鸿蒙驱动