weex的初始化一般从app启动的时候进行。weex的初始化主要完成三件事情:
初始化js framework——initFramework();
注册公共Component和Module——registerModulesAndComponents();
注册上层DSL框架,如Rax——loadRaxFramework();
initFramework涉及的Native C++类关系图如下:
从Java层WXBridge调用InitFramework,进入C++层的WXBridge类,从WXBridge.cpp的initFramework,最后运行到WeexRuntime的initFramework方法。
weex的Java调用逻辑如下:
// 1. 初始化weex sdk实例
this.mWXSDKInstance = new AliWXSDKInstance(getContext(), WeexPageFragment.FRAGMENT_TAG);
// 2. 创建一个weex页面容器,并设置到weex sdk对象的RenderContainer属性中,后续会将weex页面挂到该容器下面
this.mRenderContainer = new RenderContainer(mCurrentActivity);
this.mWXSDKInstance.setRenderContainer(this.mRenderContainer);
// 3. 把创建出来的容器加入到当前业务功能的父节点上
this.mWeexContentView.addView(this.mRenderContainer);
// 4. 通过URL地址开始渲染
this.mWXSDKInstance.renderByUrl(mPageName, mUrl, options, getArguments().getString(PARAM_WEEX_INIT_DATA), WXRenderStrategy.APPEND_ASYNC);
通过上面的步骤之后,我们主要分析renderByUrl之后的流程,流程中会涉及weex的众多可优化的点,会一并列出来。RenderByUrl之后的渲染流程如下图所示:
weex渲染涉及的性能指标参数如下:这些参数也是我们需要重点关注,需要优化处理的点:
weex耗时消耗相关的参数表示例如下:首屏时间: 1678.67 ms(资源请求耗时=725.46ms 首屏渲染耗时=953.21ms)
jsBundle大小 |
449.15 KB |
jsBundle大小直接影响bundle下载网络耗时 建议值: < 250KB
|
资源请求耗时 |
725.46 ms |
和用户实际网络情况相关 在2g、3g情况下下载时间超长 考虑使用zcache
|
数据请求耗时 |
563.73 ms |
-
|
处理bundle耗时 |
19.04 ms |
sdk处理bundle的时间 受jsBundle大小影响,控制bundle大小
|
首屏时间内创建component个数 |
46.3 个 |
调整渲染时序,屏幕外的组件会延后屏幕内的渲染时间 建议:减少打底view次数 建议:减少打底view的个数
|
首屏时间内创建component耗时 |
- ms |
-
|
首屏时间内屏幕(instance)内渲染view次数 |
18.97 个 |
建议:减少不必要的屏幕内节点
|
首屏时间调用js耗时 |
23.22 ms |
建议:减少calljs调用
|
首屏时间调用js次数 |
27.36 次 |
建议:减少calljs调用
|
首屏时间调用native module耗时 |
22.62 ms |
建议:减少native module的调用次数(重复的,不必要的) 使用dev-too、analzyer排查详细信息
|
weex内存相关的参数示例表如下:
大图个数(最多那次) |
1 个 |
大图定义:图片大小>1080*720 建议:减少大图投放
|
图片和view大小不匹配个数 |
1 个 |
定义:投放的图片尺寸大于实际view的的大小的图片数量 建议:裁剪图片,减少不必要的内存占用
|
持有组件个数(最多那次) |
135.4 个 |
建议:减少不必要的节点
|
内容不回收的cell组件个数 |
- 个 |
建议:开启cell上数据的回收,防止内存占用过大
|
没有开启复用cell的个数 |
24.11 个 |
建议:开启cell复用机智,防止大列表引起内存占用过大
|
未开启图片自动回收imgview的个数 |
0 个 |
imageview没有开启图片自动回收机制,内存非常容易oom
|
使用scroller个数 |
1 个 |
scroller是没有view回收机制的,长列表内存容易oom
|
step1. renderByUrlInternal的代码如下(WXSDKInstance.java):
private void renderByUrlInternal(String pageName, String url, Map options, String jsonInitData, WXRenderStrategy flag) {
this.ensureRenderArchor();
pageName = this.wrapPageName(pageName, url);
this.mBundleUrl = url;
this.mRenderStrategy = flag;
if (WXSDKManager.getInstance().getValidateProcessor() != null) {
this.mNeedValidate = WXSDKManager.getInstance().getValidateProcessor().needValidate(this.mBundleUrl);
}
Map renderOptions = options;
if (options == null) {
renderOptions = new HashMap();
}
if (!((Map)renderOptions).containsKey("bundleUrl")) {
((Map)renderOptions).put("bundleUrl", url);
}
this.getWXPerformance().pageName = pageName;
this.mApmForInstance.doInit();
this.mApmForInstance.setPageName(pageName);
Uri uri = Uri.parse(url);
if (uri != null && TextUtils.equals(uri.getScheme(), "file")) {
this.mApmForInstance.onStage("wxStartDownLoadBundle");
String template = WXFileUtils.loadFileOrAsset(this.assembleFilePath(uri), this.mContext);
this.mApmForInstance.onStage("wxEndDownLoadBundle");
this.render(pageName, (String)template, (Map)renderOptions, jsonInitData, flag);
} else {
boolean is_wlasm = false;
if (uri != null && uri.getPath() != null && uri.getPath().endsWith(".wlasm")) {
is_wlasm = true;
}
if (is_wlasm) {
flag = WXRenderStrategy.DATA_RENDER_BINARY;
}
IWXHttpAdapter adapter = WXSDKManager.getInstance().getIWXHttpAdapter();
WXRequest wxRequest = new WXRequest();
wxRequest.url = this.rewriteUri(Uri.parse(url), "bundle").toString();
if (wxRequest != null && !TextUtils.isEmpty(wxRequest.url)) {
requestUrl = wxRequest.url;
} else {
requestUrl = pageName;
}
if (wxRequest.paramMap == null) {
wxRequest.paramMap = new HashMap();
}
wxRequest.instanceId = this.getInstanceId();
wxRequest.paramMap.put("user-agent", WXHttpUtil.assembleUserAgent(this.mContext, WXEnvironment.getConfig()));
wxRequest.paramMap.put("isBundleRequest", "true");
WXHttpListener httpListener = new WXHttpListener(this, pageName, (Map)renderOptions, jsonInitData, flag, System.currentTimeMillis());
httpListener.setSDKInstance(this);
this.mApmForInstance.onStage("wxStartDownLoadBundle");
adapter.sendRequest(wxRequest, httpListener);
}
}
首先会检查renderContainer是否不为空,为空会创建一个RenderContainer,然后到if-else语句,这里也是其中一个优化点,如果进入if语句,则可以直接从asset文件中加载 ,这样省去网络加载的时间,否则加载weex bundle包,因此这里涉及两种情况,若在条件允许,业务允许,不需要动态化的情况下,可以把weex bundle直接放在asset下,减少网络加载的时间,然而,weex的出现本质就是为了除了打平两端之外,更多的还在于动态化上,业务扩展上。通过网络加载weex bundle对weex页面首屏出现的影响如下图所示:
从上表可知:请求资源对首屏时间(首屏时间=资源请求时间+渲染时间)的影响是很大的,因此,有必要对weex bundle的大小进行控制。
step2. 这里我们进入到weex bundle的请求,sendRequest的代码如下(WXHttpAdapter.java 实现自IWXHttpAdapter),这里去掉了部分代码:
public void sendRequest(final WXRequest wxRequest, final OnHttpListener onHttpListener) {
if (onHttpListener != null && wxRequest != null) {
onHttpListener.onHttpStart();
final WXResponse response = new WXResponse();
if (response.extendParams == null) {
response.extendParams = new HashMap();
}
if (TextUtils.isEmpty(wxRequest.url)) {
...
onHttpListener.onHttpFinish(response);
} else {
this.recordRequestState("..", wxRequest.url, wxRequest, (WXResponse)null, (Map)null);
WXSDKInstance instance = (WXSDKInstance)WXSDKManager.getInstance().getAllInstanceMap().get(wxRequest.instanceId);
if (null != instance && instance.isPreDownLoad() && Looper.myLooper() == Looper.getMainLooper()) {
WVThreadPool.getInstance().execute(new Runnable() {
public void run() {
...
(new Handler(Looper.getMainLooper())).post(new Runnable() {
public void run() {
WXHttpAdapter.this.processResponse(wxRequest, response, onHttpListener, networkTracker);
}
});
}
});
} else {
...
this.processResponse(wxRequest, response, onHttpListener, networkTracker);
}
}
}
}
在这一步中,可以通过ZCache来快速得到weex bundle,这就是ZCache命中率指标。请求成功之后,onHttpListener(WXHttpListener.java)回调调用onSucess方法,进而开始进行渲染。
step3. 请求成功之后,从WXSDKInstance的render方法进入,直到调用到C++层的WXBridgeManager的createinstance方法中;
static jint CreateInstanceContext(JNIEnv* env, jobject jcaller,
jstring instanceId, jstring name,
jstring function, jobjectArray args) {
if (function == NULL || instanceId == NULL) {
LOGE("native_createInstanceContext function is NULL");
return false;
}
int length = 0;
if (args != NULL) {
length = env->GetArrayLength(args);
}
if (length < 4) {
LOGE("native_createInstanceContext jargs format error");
return false;
}
auto arg1 = std::unique_ptr(
new WXJSObject(env, base::android::ScopedLocalJavaRef(
env, env->GetObjectArrayElement(args, 1))
.Get()));
auto jscript = arg1->GetData(env);
auto opts = base::android::ScopedLocalJavaRef(
env, getJsonData(env, args, 2));
// init jsonData
auto initData = base::android::ScopedLocalJavaRef(
env, getJsonData(env, args, 3));
auto arg4 = std::unique_ptr(
new WXJSObject(env, base::android::ScopedLocalJavaRef(
env, env->GetObjectArrayElement(args, 4))
.Get()));
// get render strategy
auto render_strategy = std::unique_ptr(
new WXJSObject(env, base::android::ScopedLocalJavaRef(
env, env->GetObjectArrayElement(args, 5))
.Get()));
auto japi = arg4->GetData(env);
auto extraOptionString = base::android::ScopedLocalJavaRef(
env, getJsonData(env, args, 6));
ScopedJStringUTF8 scoped_id(env, instanceId);
ScopedJStringUTF8 scoped_func(env, function);
ScopedJStringUTF8 scoped_opts(env, opts.Get());
ScopedJStringUTF8 scoped_init_data(env, initData.Get());
ScopedJStringUTF8 scoped_api(env, static_cast(japi.Get()));
ScopedJStringUTF8 scoped_render_strategy(
env, static_cast(render_strategy->GetData(env).Release()));
ScopedJStringUTF8 scoped_extra_option(env, extraOptionString.Get());
const std::string input = scoped_extra_option.getChars();
std::vector params;
if(input.length() > 0) {
std::string err;
const json11::Json &json = json11::Json::parse(input, err);
const std::map &data = json.object_items();
auto it = data.begin();
while (it != data.end()) {
INIT_FRAMEWORK_PARAMS *param = nullptr;
const std::string &string = it->second.string_value();
param = WeexCore::genInitFrameworkParams(it->first.c_str(),it->second.string_value().c_str());
params.push_back(param);
it++;
}
}
// If strategy is DATA_RENDER_BINARY, jscript is a jbyteArray, otherwise jstring
// TODO use better way
if (scoped_render_strategy.getChars() != nullptr
&& strcmp(scoped_render_strategy.getChars(), "DATA_RENDER_BINARY") == 0) {
JByteArrayRef byte_array(env, static_cast(jscript.Get()));
return WeexCoreManager::Instance()
->getPlatformBridge()
->core_side()
->CreateInstance(scoped_id.getChars(), scoped_func.getChars(),
byte_array.getBytes(), byte_array.length(), scoped_opts.getChars(),
scoped_init_data.getChars(), scoped_api.getChars(), params,
scoped_render_strategy.getChars());
} else {
ScopedJStringUTF8 scoped_script(env, static_cast(jscript.Get()));
return WeexCoreManager::Instance()
->getPlatformBridge()
->core_side()
->CreateInstance(scoped_id.getChars(), scoped_func.getChars(),
scoped_script.getChars(), strlen(scoped_script.getChars()),
scoped_opts.getChars(),
scoped_init_data.getChars(), scoped_api.getChars(), params,
scoped_render_strategy.getChars());
}
}
上面的C++代码中,先解析参数,如bundle的jsscript等,最后执行
WeexCoreManager::Instance()
->getPlatformBridge()
->core_side()
->CreateInstance()
在weex的initFramework部分涉及到相关的C++类,这里
WeexCoreManager::Instance()->getPlatformBridge()->core_side()就进入到CorsideInPlateForm类的CreateInstance方法中:
int CoreSideInPlatform::CreateInstance(const char *instanceId, const char *func,
const char *script, int script_length,
const char *opts,
const char *initData,
const char *extendsApi, std::vector& params,
const char *render_strategy) {
// First check about DATA_RENDER mode
if (render_strategy != nullptr) {
std::function exec_js =
[instanceId = std::string(instanceId), func = std::string(func),
opts = std::string(opts), initData = std::string(initData),
extendsApi = std::string(extendsApi)](const char *result, const char *bundleType) {
std::string error;
auto opts_json = json11::Json::parse(opts, error);
std::map &opts_map =
const_cast &>(
opts_json.object_items());
opts_map["bundleType"] = bundleType;
std::vector params;
WeexCoreManager::Instance()
->script_bridge()
->script_side()
->CreateInstance(instanceId.c_str(), func.c_str(), result,
opts_json.dump().c_str(), initData.c_str(),
strcmp("Rax", bundleType) ? "\0" : extendsApi.c_str(),
params);
};
if (strcmp(render_strategy, "DATA_RENDER") == 0) {
auto handler = EagleBridge::GetInstance()->data_render_handler();
if(handler){
handler->CreatePage(script, instanceId, render_strategy, initData, exec_js);
}
else{
WeexCore::WeexCoreManager::Instance()->getPlatformBridge()->platform_side()->ReportException(
instanceId, "CreatePageWithContent",
"There is no data_render_handler when createInstance with DATA_RENDER mode");
}
return true;
} else if (strcmp(render_strategy, "DATA_RENDER_BINARY") == 0) {
...
}
auto handler = EagleBridge::GetInstance()->data_render_handler();
if(handler){
handler->CreatePage(script, static_cast(script_length), instanceId, option, env_str, initData, exec_js);
}
else{
WeexCore::WeexCoreManager::Instance()->getPlatformBridge()->platform_side()->ReportException(
instanceId, "CreatePageWithContent",
"There is no data_render_handler when createInstance with DATA_RENDER_BINARY mode");
}
return true;
}
if(strcmp(render_strategy, "JSON_RENDER") == 0){
JsonRenderManager::GetInstance()->CreatePage(script, instanceId, render_strategy);
return true;
}
}
return WeexCoreManager::Instance()
->script_bridge()
->script_side()
->CreateInstance(instanceId, func, script, opts, initData, extendsApi, params);
}
同样,参考上方的C++类图,进入到ScriptSide类的CreateInstance中,
int ScriptSideInQueue::CreateInstance(const char *instanceId,
const char *func,
const char *script,
const char *opts,
const char *initData,
const char *extendsApi,
std::vector ¶ms) {
...
CreateInstanceTask *task = new CreateInstanceTask(char2String(instanceId),
script, params);
task->addExtraArg(char2String(func));
task->addExtraArg(char2String(opts));
task->addExtraArg(char2String(initData));
task->addExtraArg(char2String(extendsApi));
auto pQueue = taskQueue(instanceId, true);
pQueue->addTask(task);
if (!pQueue->isInitOk) {
pQueue->init();
}
return 1;
}
进而加入到执行队列等待执行,CreateInstanceTask的run方法实现如下:
void CreateInstanceTask::run(WeexRuntime *runtime) {
if (extraArgs.size() < 4)
return;
runtime->createInstance(instanceId, extraArgs.at(0), this->script, extraArgs.at(1), extraArgs.at(2),
extraArgs.at(3), initExtraArgs->params);
}
进而进入到WeexRuntime执行createInstance。
Step4. 回到step3的createInstance中,这里我们以render_stragy不为空来处理,为空的情况实际是使用jsengine执行jsbundle,然后也是走到createbody中进行处理的,原理类似,这其中都涉及到解析jsbundle这一步。我们这里分析render_stragy不为空的情况,进入到RenderManager.cpp的CreatePage方法中。
bool RenderManager::CreatePage(const std::string& page_id, const char *data) {
#if RENDER_LOG
wson_parser parser(data);
LOGD("[RenderManager] CreatePage >>>> pageId: %s, dom data: %s",
pageId.c_str(), parser.toStringUTF8().c_str());
#endif
std::string targetName = RenderTargetManager::getRenderTargetName(page_id);
if (!targetName.empty()) {
if (RenderTargetManager::sharedInstance()->getAvailableTargetNames().count(targetName) == 0) {
// cannot find the target, degress
targetName = "";
}
}
if (!targetName.empty()) {
RenderPageCustom* pageCustom = CreateCustomPage(page_id, targetName);
WsonGenerate(data, "", 0, [=](const std::string& ref,
const std::string& type,
const std::string& parentRef,
std::map* styles,
std::map* attrs,
std::set* events,
int index) {
if (parentRef.empty()) {
pageCustom->CreateBody(ref, type, styles, attrs, events);
}
else {
pageCustom->AddRenderObject(ref, type, parentRef, index, styles, attrs, events);
}
});
return true;
}
else {
RenderPage *page = new RenderPage(page_id);
pages_.insert(std::pair(page_id, page));
initDeviceConfig(page, page_id);
int64_t start_time = getCurrentTime();
RenderObject *root = Wson2RenderObject(data, page_id, page->reserve_css_styles());
page->ParseJsonTime(getCurrentTime() - start_time);
return page->CreateRootRender(root);
}
}
这里if-else语句本质流程差不多,我们看else执行语句,主要有两个重点,一个是通过Wson2RenderObject方法对data的jscript数据进行解析,最后形成RenderObject树,RenderObject继承自IRenderObject,IRenderObject继承WXCoreLayoutNode,可以看出,实际也是一个Layout Node结点。
一个是调用page->CreateRootRender(root),代码如下:
bool RenderPage::CreateRootRender(RenderObject *root) {
if (root == nullptr) return false;
set_is_dirty(true);
SetRootRenderObject(root);
if (isnan(this->render_root_->getStyleWidth())) {
this->render_root_->setStyleWidthLevel(FALLBACK_STYLE);
if (is_render_container_width_wrap_content())
this->render_root_->setStyleWidthToNAN();
else
this->render_root_->setStyleWidth(
WXCoreEnvironment::getInstance()->DeviceWidth(), false);
} else {
this->render_root_->setStyleWidthLevel(CSS_STYLE);
}
PushRenderToRegisterMap(root);
SendCreateBodyAction(root);
return true;
}
总一个RenderPage的创建精华都在这几句中。首先通过SetRootRenderObject(root)设置为Root RenderPageObject,然后设置style、css、宽度等等,接着调用SendCreateBodyAction(root)
void RenderPage::SendCreateBodyAction(RenderObject *render) {
if (render == nullptr) return;
RenderAction *action = new RenderActionCreateBody(page_id(), render);
PostRenderAction(action);
int i = 0;
for (auto it = render->ChildListIterBegin(); it != render->ChildListIterEnd();
it++) {
RenderObject *child = static_cast(*it);
if (child != nullptr) {
SendAddElementAction(child, render, i, true);
}
++i;
}
if (i > 0 && render->IsAppendTree()) {
SendAppendTreeCreateFinish(render->ref());
}
}
这个方法中,主要做三件事,一,通过RenderActionCreateBody创建一个renderpage容器,如Android中比如用FrameLayout View作为container,实际上接下来分析也是创建具体的View容器;二,遍历子节点,添加Element节点,对应java层就是在parent_root上创建并添加child view;三,遍历完成之后,调用SendAppendTreeCreateFinish告知创建结束。接下来我们分析这三步。
发送RenderActionCreateBody action,ExecuteAction执行方法如下:
void RenderActionCreateBody::ExecuteAction() {
WeexCoreManager::Instance()->getPlatformBridge()->platform_side()->CreateBody(
this->page_id_.c_str(), this->component_type_.c_str(), this->ref_.c_str(),
this->styles_, this->attributes_, this->events_, this->margins_,
this->paddings_, this->borders_);
}
从一开始说的类图知道,plateform_side为AndroidSide类,进入到该类的CreateBody方法:
int AndroidSide::CreateBody(const char *page_id, const char *component_type,
const char *ref,
std::map *styles,
std::map *attributes,
std::set *events,
const WXCoreMargin &margins,
const WXCorePadding &paddings,
const WXCoreBorderWidth &borders) {
JNIEnv *env = base::android::AttachCurrentThread();
if (env == nullptr)
return -1;
int flag =
wx_bridge_->CreateBody(env, page_id, component_type, ref, styles,
attributes, events, margins, paddings, borders);
if (flag == -1) {
LOGE("instance destroy JFM must stop callCreateBody");
}
return flag;
}
进入到WXBirdge方法:
int WXBridge::CreateBody(JNIEnv* env, const char* page_id,
const char* component_type, const char* ref,
std::map* styles,
std::map* attributes,
std::set* events,
const WXCoreMargin& margins,
const WXCorePadding& paddings,
const WXCoreBorderWidth& borders) {
auto jni_pageId = base::android::ScopedLocalJavaRef(env, env->NewStringUTF(page_id));
auto jni_ref = base::android::ScopedLocalJavaRef(env, env->NewStringUTF(ref));
auto styles_map = std::unique_ptr(new WXMap);
styles_map->Put(env, *styles);
auto attributes_map = std::unique_ptr(new WXMap);
attributes_map->Put(env, *attributes);
auto events_set = std::unique_ptr(new HashSet);
events_set->Add(env, *events);
float c_margins[4];
float c_paddings[4];
float c_borders[4];
c_margins[0] = margins.getMargin(kMarginTop);
c_margins[1] = margins.getMargin(kMarginBottom);
c_margins[2] = margins.getMargin(kMarginLeft);
c_margins[3] = margins.getMargin(kMarginRight);
c_paddings[0] = paddings.getPadding(kPaddingTop);
c_paddings[1] = paddings.getPadding(kPaddingBottom);
c_paddings[2] = paddings.getPadding(kPaddingLeft);
c_paddings[3] = paddings.getPadding(kPaddingRight);
c_borders[0] = borders.getBorderWidth(kBorderWidthTop);
c_borders[1] = borders.getBorderWidth(kBorderWidthBottom);
c_borders[2] = borders.getBorderWidth(kBorderWidthLeft);
c_borders[3] = borders.getBorderWidth(kBorderWidthRight);
auto jni_margins =
0 == c_margins[0] && 0 == c_margins[1] && 0 == c_margins[2] &&
0 == c_margins[3]
? base::android::ScopedLocalJavaRef()
: base::android::JNIType::NewFloatArray(env, 4, c_margins);
auto jni_paddings =
0 == c_paddings[0] && 0 == c_paddings[1] && 0 == c_paddings[2] &&
0 == c_paddings[3]
? base::android::ScopedLocalJavaRef()
: base::android::JNIType::NewFloatArray(env, 4, c_paddings);
auto jni_borders =
0 == c_borders[0] && 0 == c_borders[1] && 0 == c_borders[2] &&
0 == c_borders[3]
? base::android::ScopedLocalJavaRef()
: base::android::JNIType::NewFloatArray(env, 4, c_borders);
jstring jni_component_type = getComponentTypeFromCache(component_type);
if (jni_component_type == nullptr) {
jni_component_type = putComponentTypeToCache(component_type);
}
int flag = Java_WXBridge_callCreateBody(
env, jni_object(), jni_pageId.Get(), jni_component_type, jni_ref.Get(),
styles_map->jni_object(), attributes_map->jni_object(),
events_set->jni_object(), jni_margins.Get(), jni_paddings.Get(),
jni_borders.Get());
return flag;
}
该方法会设置好一个view中会涉及到的如margin、padding、layout、css、style等,然后通过调用Java_WXBridge_callCreateBody、调用到Jave层WXBridegeManager的callCreateBody方法中:
public int callCreateBody(String pageId, String componentType, String ref, HashMap styles, HashMap attributes, HashSet events, float[] margins, float[] paddings, float[] borders) {
if (!TextUtils.isEmpty(pageId) && !TextUtils.isEmpty(componentType) && !TextUtils.isEmpty(ref)) {
if (WXEnvironment.isApkDebugable()) {
}
if (this.mDestroyedInstanceId != null && this.mDestroyedInstanceId.contains(pageId)) {
return -1;
} else {
try {
WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(pageId);
if (instance != null) {
BasicGraphicAction action = new GraphicActionCreateBody(instance, ref, componentType, styles, attributes, events, margins, paddings, borders);
WXSDKManager.getInstance().getWXRenderManager().postGraphicAction(action.getPageId(), action);
}
} catch (Exception var12) {
WXLogUtils.e("[WXBridgeManager] callCreateBody exception: ", var12);
WXExceptionUtils.commitCriticalExceptionRT(pageId, WXErrorCode.WX_KEY_EXCEPTION_INVOKE_BRIDGE, "callCreateBody", WXLogUtils.getStackTrace(var12), (Map)null);
}
return 1;
}
} else {
WXLogUtils.d("[WXBridgeManager] call callCreateBody arguments is null");
WXExceptionUtils.commitCriticalExceptionRT(pageId, WXErrorCode.WX_RENDER_ERR_BRIDGE_ARG_NULL, "callCreateBody", "arguments is empty, INSTANCE_RENDERING_ERROR will be set", (Map)null);
return 0;
}
}
这里通过postGraphicAction发送执行action命令,实际上是走到UI线程中进行接下来的操作。那么之前的操作在哪个线程进行呢,可以看上面执行流水线图,之前的操作是在WX Thread线程进行的。
分析GraphicActionCreateBody这个action之前,先贴出一张UML图:
GraphicActionCreateBody类和GraphicActionAddElement类都继承自GraphicActionAbstractAddElement,那么在GraphicActionCreateBody初始化的时候,会调用creatComponent创建WXComponent,WXComponent创建由工厂类WXComponentFactory来创建,那么工厂类WXComponentFactory从哪里获得资源,从WXComponentRegistry中,WXComponentRegistry在weex初始化的时候注册了相关的Component Holder,Holder的实现类有三种,如SimpleComponentHolder。weex初始化的时候,在WXSDKEngine的静态方法register中,进行了相关的注册:
private static void register() {
BatchOperationHelper batchHelper = new BatchOperationHelper(WXBridgeManager.getInstance());
try {
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXText.class, new Creator())), false, "text");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXDiv.class, new Ceator())), false, "container", "div", "header", "footer");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXImage.class, new com.taobao.weex.ui.component.WXImage.Creator())), false, "image", "img");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXScroller.class, new com.taobao.weex.ui.component.WXScroller.Creator())), false, "scroller");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXSlider.class, new com.taobao.weex.ui.component.WXSlider.Creator())), true, "slider", "cycleslider");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXSliderNeighbor.class, new com.taobao.weex.ui.component.WXSliderNeighbor.Creator())), true, "slider-neighbor");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXCell.class, new com.taobao.weex.ui.component.list.WXCell.Creator())), true, "cell");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXListComponent.class, new com.taobao.weex.ui.component.list.WXListComponent.Creator())), true, "list", "vlist", "recycler", "waterfall");
registerComponent((IFComponentHolder)(new SimpleComponentHolder(WXRichText.class, new com.taobao.weex.ui.component.richtext.WXRichText.Creator())), false, "richtext");
String simpleList = "simplelist";
registerComponent(SimpleListComponent.class, false, simpleList);
registerComponent(WXRecyclerTemplateList.class, false, "recycle-list");
registerComponent(HorizontalListComponent.class, false, "hlist");
...
registerComponent("header", WXHeader.class);
registerModule("modal", WXModalUIModule.class);
registerModule("instanceWrap", WXInstanceWrap.class);
registerModule("animation", WXAnimationModule.class);
registerModule("webview", WXWebViewModule.class);
...
WXLogUtils.e("[WXSDKEngine] register:", var2);
}
if (RegisterCache.getInstance().enableAutoScan()) {
AutoScanConfigRegister.doScanConfig();
}
batchHelper.flush();
}
接下来我们分析createComponent方法
protected WXComponent createComponent(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) {
long createComponentStart = System.currentTimeMillis();
if (basicComponentData != null) {
basicComponentData.addStyle(this.mStyle);
basicComponentData.addAttr(this.mAttributes);
basicComponentData.addEvent(this.mEvents);
basicComponentData.addShorthand(this.mMargins, TYPE.MARGIN);
basicComponentData.addShorthand(this.mPaddings, TYPE.PADDING);
basicComponentData.addShorthand(this.mBorders, TYPE.BORDER);
}
WXComponent component = WXComponentFactory.newInstance(instance, parent, basicComponentData);
WXSDKManager.getInstance().getWXRenderManager().registerComponent(this.getPageId(), this.getRef(), component);
if (this.mStyle != null && this.mStyle.containsKey("transform") && component.getTransition() == null) {
Map animationMap = new ArrayMap(2);
animationMap.put("transform", this.mStyle.get("transform"));
animationMap.put("transformOrigin", this.mStyle.get("transformOrigin"));
component.addAnimationForElement(animationMap);
}
instance.onComponentCreate(component, System.currentTimeMillis() - createComponentStart);
return component;
}
从上面UML图的分析中,我们知道,进入到WXComponentFactory创建WXComponent,然后根据component的type类型获得Component的holder实例IFComponentHolder,如进入到SimpleComponentHolder的instance中进行创建:
public synchronized WXComponent createInstance(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) throws IllegalAccessException, InvocationTargetException, InstantiationException {
WXComponent component = this.mCreator.createInstance(instance, parent, basicComponentData);
component.bindHolder(this);
return component;
}
如我们这里在写weex文件的时候,最外层为div,则从上面register中可以知道,会进入到WXDiv类中执行相关的创建,WXDiv最终是返回FrameLayout的hostview回去的,这我们接下来分析到。然后每次创建完component实例,都会执行instance.onComponentCreate统计,这个大小也是我们要尽量去优化减少的,从上面的性能表格知道,首屏的Component大小也是我们要优化的点。
创建实例之后,接着执行action操作,GraphicActionCreateBody的execution操作如下所示:
public void executeAction() {
super.executeAction();
try {
this.component.createView();
this.component.applyLayoutAndEvent(this.component);
this.component.bindData(this.component);
WXSDKInstance instance = this.getWXSDKIntance();
if (this.component instanceof WXScroller) {
WXScroller scroller = (WXScroller)this.component;
if (scroller.getInnerView() instanceof ScrollView) {
instance.setRootScrollView((ScrollView)scroller.getInnerView());
}
}
instance.onRootCreated(this.component);
if (instance.getRenderStrategy() != WXRenderStrategy.APPEND_ONCE) {
instance.onCreateFinish();
}
} catch (Exception var3) {
WXLogUtils.e("create body failed.", var3);
}
}
首先创建component对应的view,这个进入到具体Component的initCoponentHostView中去创建,如WXDiv,则返回FrameLayout。接着绑定对应的View时间Event,如我们设置了OnClick时间,则需要还原设置原生组件的事件。然后设置为weex页面的root节点,并且加入到我们一开始分析的renderContainer容器中:
public void onRootCreated(WXComponent root) {
this.mRootComp = root;
this.mRootComp.mDeepInComponentTree = 1;
this.mRenderContainer.addView(root.getHostView());
this.setSize(this.mRenderContainer.getWidth(), this.mRenderContainer.getHeight());
}
接着上面的第二件事情,添加element节点,这个和执行GraphicActionCreateBody类似,也是根据element节点的type类型进行原生组件的创建,并挂载到对应的父节点上,同时设置好布局、属性、事件响应等操作。
最后所有的element添加都完成之后,执行callAppendTreeCreateFinish操作。
以上即为weex的整个渲染过程。
从上面分析过程指导,可交互涉及下面步骤:
weex涉及的可交互性能是衡量weex bundle好坏的其中一个重要指标。Android开发中,我们经常使用systrace或者profile查看性能,其中火焰图我们应该是很熟悉不过了。接下来我们以火焰图的形式来描述下可交互所经历的哪些步骤。
上图为某个weex bundle可交互的时间火焰图(类似)。一般情况下,有两种情况下统计首屏的信息,一种是create finish之后就统计到了,但是这种可能并不能很好的描述实际的问题,如首屏是需要加载数据的情况,此时即使是已经创建了view了,但是实际还没看到真实的内容,另外一种情况就是,即使view创建出来了,但是还达不到可交互的情况,于是就有上图中统计到的可交互时长要相对加载完数据之后还要长一些。weex SDK在处理是否可交互是加上了两种条件,view已经渲染完成且树已经稳定。
我们知道,weex支持rax和vue,以rax为例,当组件的state改变的时候,是如何触发,然后通知native 更新view的呢?
如上图,JS framework的刷新工作原理。通过JS Bundle解析生成DOM树之后,当某个数据发生Setter操作之后,会Notify Js Framework的Watcher,从而Wacher发起Trigger,re-render 。re-render的时候从Getter获取新的数据,通过调用callNative发起更新。这里涉及到Render的主要的6个步骤,如下图所示:
Build Tree: 将Json格式的数据还原成一个树形结构的数据;
Compute Style:将样式应用到上一步的树形结构中;
CSS Layout: 通过Layout计算Flex布局;
Create View:创建Type对应的Native View, 如Div对应WXDiv,对应WXFrameLayout,集成自FrameLayout;
Update Frame: 根据计算出来的布局去更新View的宽高等;
Set View props:应用view的样式属性以及事件等。
上面六个步骤中,我们知道,Android每隔16ms触发一次VSYNC,需要在16ms完成上面6个步骤,如果把6个步骤都放在UI线程上处理,显然肯定不合适的所以我们看到在weex的渲染过程中,前三个步骤放在了异步子线程进行,这样可以提高渲染性能。