Weex Android SDK原始碼分析
前言
最近開始試水Weex開發,使用這麼長一段時間,感覺寫Weex還是非常方便的。作為一個Android開發,免不了要追查一下weex的sdk原始碼。今天,就以Weex SDK for Android為例,分析SDK的
認識Weex SDK
整體分析下拉,按照js檔案的渲染過程,繪製出了下面架構圖:
WEEX檔案渲染過程
為了更加詳細的說明整個渲染過程,我對原始碼進行了分析。並結合示例,進行了日誌分析;比如,我們要開發如下一個簡單的元件(紅色方框內):
那麼,我們的wxc-title.we原始碼為:
<!-- wxc-title.we created by mochuan.zhb-->
<template>
<div class="container">
<image class="image" src="{{item.pic}}"></image>
<text class="text">{{item.name}}</text>
</div>
</template>
<style>
.container {
position: relative;
flex-direction: row;
width : 750px;
height: 60px;
align-items: center;
}
.image {
margin-left: 100px;
width: 45px;
height: 45px;
}
.text {
margin-left: 10px;
font-size: 28px;
color: #444444;
}
</style>
<script>
module.exports = {
data: {
item: {
pic: '//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png' ,
name: '當地玩樂'
}
},
methods: {
}
}
</script>
上述.we檔案經過weex編譯之後,生成的js檔案,經過格式化如下:
...
([function (module, exports) {
module.exports = {
"type": "div",
"classList": [
"container"
],
"children": [
{
"type": "image",
"classList": [
"image"
],
"attr": {
"src": function () {
return this.item.pic
}
}
},
{
"type": "text",
"classList": [
"text"
],
"attr": {
"value": function () {
return this.item.name
}
}
}
]
}
}, function (module, exports) {
module.exports = {
"container": {
"position": "relative",
"flexDirection": "row",
"width": 750,
"height": 60,
"alignItems": "center"
},
"image": {
"marginLeft": 100,
"width": 45,
"height": 45
},
"text": {
"marginLeft": 10,
"fontSize": 28,
"color": "#444444"
}
}
}, function (module, exports) {
module.exports = function (module, exports, __weex_require__) {
'use strict';
module.exports = {
data: function () {
return {
item: {
pic: '//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png',
name: '當地玩樂'
}
}
},
methods: {}
};
}
}
]);
上述分別使用了三個function,對template、style和script進行了封裝;那麼,這個檔案是怎麼被weex sdk執行並解析,最終生成結構化的View的呢?
渲染過程
時序圖1:
從掃碼開始,首先經歷如下過程;依次經過readerPage,createInstance,一直到WXBridge的exeJs方法;也就是說,最終,Java通過呼叫native的exeJs方法,來執行js檔案的。
時序圖2:
緊接著時序圖1:執行到JNI層的Java_com_taobao_weex_bridge_WXBridge_execJS方法;
然後js通過native呼叫WXBridge的callNative方法,達到js呼叫Java程式碼的目的;
JNI層的部分程式碼如下:
jint Java_com_taobao_weex_bridge_WXBridge_execJS(JNIEnv *env, jobject this1, jstring jinstanceid,
jstring jnamespace, jstring jfunction,
jobjectArray jargs) {
v8::HandleScope handleScope;
v8::Isolate::Scope isolate_scope(globalIsolate);
v8::Context::Scope ctx_scope(V8context);
v8::TryCatch try_catch;
int length = env->GetArrayLength(jargs);
v8::Handle<v8::Value> obj[length];
jclass jsObjectClazz = (env)->FindClass("com/taobao/weex/bridge/WXJSObject");
for (int i = 0; i < length; i++) {
jobject jArg = (env)->GetObjectArrayElement(jargs, i);
jfieldID jTypeId = (env)->GetFieldID(jsObjectClazz, "type", "I");
jint jTypeInt = env->GetIntField(jArg, jTypeId);
jfieldID jDataId = (env)->GetFieldID(jsObjectClazz, "data", "Ljava/lang/Object;");
jobject jDataObj = env->GetObjectField(jArg, jDataId);
if (jTypeInt == 1) {
jclass jDoubleClazz = (env)->FindClass("java/lang/Double");
jmethodID jDoubleValueId = (env)->GetMethodID(jDoubleClazz, "doubleValue", "()D");
jdouble jDoubleObj = (env)->CallDoubleMethod(jDataObj, jDoubleValueId);
obj[i] = v8::Number::New((double) jDoubleObj);
env->DeleteLocalRef(jDoubleClazz);
} else if (jTypeInt == 2) {
jstring jDataStr = (jstring) jDataObj;
obj[i] = jString2V8String(env, jDataStr);
} else if (jTypeInt == 3) {
v8::Handle<v8::Value> jsonObj[1];
v8::Handle<v8::Object> global = V8context->Global();
json = v8::Handle<v8::Object>::Cast(global->Get(v8::String::New("JSON")));
json_parse = v8::Handle<v8::Function>::Cast(json->Get(v8::String::New("parse")));
jsonObj[0] = jString2V8String(env, (jstring) jDataObj);
v8::Handle<v8::Value> ret = json_parse->Call(json, 1, jsonObj);
obj[i] = ret;
}
env->DeleteLocalRef(jDataObj);
env->DeleteLocalRef(jArg);
}
env->DeleteLocalRef(jsObjectClazz);
const char *func = (env)->GetStringUTFChars(jfunction, 0);
v8::Handle<v8::Object> global = V8context->Global();
v8::Handle<v8::Function> function;
v8::Handle<v8::Value> result;
if (jnamespace == NULL) {
function = v8::Handle<v8::Function>::Cast(global->Get(v8::String::New(func)));
result = function->Call(global, length, obj);
}
else {
v8::Handle<v8::Object> master = v8::Handle<v8::Object>::Cast(
global->Get(jString2V8String(env,jnamespace)));
function = v8::Handle<v8::Function>::Cast(
master->Get(jString2V8String(env,jfunction)));
result = function->Call(master, length, obj);
}
if (result.IsEmpty()) {
assert(try_catch.HasCaught());
ReportException(globalIsolate, &try_catch, jinstanceid, func);
env->ReleaseStringUTFChars(jfunction, func);
env->DeleteLocalRef(jfunction);
return false;
}
env->ReleaseStringUTFChars(jfunction, func);
env->DeleteLocalRef(jfunction);
return true;
}
}
時序圖3:createBody&generateComponentTree
接著上面的時序圖,開始做頁面的建立;關鍵的程式碼在WXRenderStatement中的createBodyOnDomThread,該方法通過建立跟佈局的mGodComponent,通過遞迴generateComponentTree生成Component的邏輯樹結構;然後,在WXRenderStatement的createBody方法中,生成View,繫結屬性和資料;具體如下圖所示:
時序圖4:addElement
時序圖5:callNative呼叫Module
呼叫過程日誌記錄
以上面的weex頁面為例:使用PlayGround掃碼之後的呼叫過程中的日誌為:
12-04 15:51:04.705: D/weex(30188): ###render in WXSDKInstance. pageName = WXPageActivity,template = /******/ (function(modules) { // webpackBootstrap
12-04 15:51:04.705: D/weex(30188): ###createInstance in WXSDKManager code = /******/ (function(modules) { // webpackBootstrap
12-04 15:51:04.710: D/weex(30188): ###createInstance in WXBrideManager instanceId = 3,template = /******/ (function(modules) { // webpackBootstrap
12-04 15:51:04.710: D/weex(30188): ###invokeCreateInstance in WXBrideManager instanceId = 3,template = /******/ (function(modules) { // webpackBootstrap
12-04 15:51:04.725: D/weex(30188): ###execJS instanceId = 3,namespace = null,function = createInstance,args = [{"data":"3","type":2},{"data":"/******/ (function(modules) { // webpackBootstrap\n .......
12-04 15:51:04.740: D/weex(30188): ###callNative in WXBridge instanceId = 3,tasks = [{"module":"dom","method":"createBody","args":[{"ref":"_root","type":"div","attr":{},"style":{"position":"relative","flexDirection":"row","width":750,"height":60,"alignItems":"center"}}]}],callback = -1
12-04 15:51:04.740: D/weex(30188): ###callDomMethod to create component...task = {"args":[{"attr":{},"ref":"_root","style":{"alignItems":"center","flexDirection":"row","height":60,"position":"relative","width":750},"type":"div"}],"method":"createBody","module":"dom"}
12-04 15:51:04.745: D/weex(30188): ###callDomMethod task = {"args":[{"attr":{},"ref":"_root","style":{"alignItems":"center","flexDirection":"row","height":60,"position":"relative","width":750},"type":"div"}],"method":"createBody","module":"dom"}
12-04 15:51:04.745: D/weex(30188): ###createBody element = {"attr":{},"ref":"_root","style":{"alignItems":"center","flexDirection":"row","height":60,"position":"relative","width":750},"type":"div"}
12-04 15:51:04.745: D/weex(30188): ###handleMessage in WXDomHandler...what = 0,obj = {"args":[{"attr":{},"ref":"_root","style":{"alignItems":"center","flexDirection":"row","height":60,"position":"relative","width":750},"type":"div"}],"instanceId":"3"}
12-04 15:51:04.750: D/weex(30188): ###createBodyOnDomThread in WXRenderStatement dom = layout: {left: 0.0, top: 0.0, width: 0.0, height: 0.0, direction: LTR}direction =INHERIT
12-04 15:51:04.750: D/weex(30188): ###createView in WXComponent className = WXDiv
12-04 15:51:04.755: D/weex(30188): ###generateComponentTree in WXRenderStatement component = WXDiv
12-04 15:51:04.755: D/weex(30188): ###callAddElement in WXBridge instanceId = 3,ref = _root,dom = {"ref":"153","type":"image","attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"style":{"marginLeft":100,"width":45,"height":45}},index=-1,callback = -1
12-04 15:51:04.755: D/weex(30188): ###callAddElement in WXBridgeManager instanceId = 3,ref = _root,dom = {"ref":"153","type":"image","attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"style":{"marginLeft":100,"width":45,"height":45}},index = -1,callback = -1
12-04 15:51:04.760: D/weex(30188): ###addElement parentRef = _root,element = {"attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"ref":"153","style":{"height":45,"marginLeft":100,"width":45},"type":"image"},index = -1
12-04 15:51:04.760: D/weex(30188): ###handleMessage in WXDomHandler...what = 3,obj = {"args":["_root",{"attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"ref":"153","style":{"height":45,"marginLeft":100,"width":45},"type":"image"},-1],"instanceId":"3"}
12-04 15:51:04.760: D/weex(30188): ###callAddElement in WXBridge instanceId = 3,ref = _root,dom = {"ref":"154","type":"text","attr":{"value":"當地玩樂"},"style":{"marginLeft":10,"fontSize":28,"color":"#444444"}},index=-1,callback = -1
12-04 15:51:04.765: D/weex(30188): ###callAddElement in WXBridgeManager instanceId = 3,ref = _root,dom = {"ref":"154","type":"text","attr":{"value":"當地玩樂"},"style":{"marginLeft":10,"fontSize":28,"color":"#444444"}},index = -1,callback = -1
12-04 15:51:04.765: D/weex(30188): ###addElement parentRef = _root,element = {"attr":{"value":"當地玩樂"},"ref":"154","style":{"color":"#444444","fontSize":28,"marginLeft":10},"type":"text"},index = -1
12-04 15:51:04.765: D/weex(30188): ###addDom in WXDomManager instanceId = 3,parentRef = _root,element = {"attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"ref":"153","style":{"height":45,"marginLeft":100,"width":45},"type":"image"},index = -1
12-04 15:51:04.765: D/weex(30188): ###callNative in WXBridge instanceId = 3,tasks = [{"module":"dom","method":"createFinish","args":[]}],callback = -1
12-04 15:51:04.770: D/weex(30188): ###addDom in WXDomStatement dom = {"attr":{"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"},"ref":"153","style":{"height":45,"marginLeft":100,"width":45},"type":"image"},parentRef = _root,index = -1
12-04 15:51:04.770: D/weex(30188): ###createComponentOnDomThread in WXRenderManager dom = layout: {left: 0.0, top: 0.0, width: 0.0, height: 0.0, direction: LTR}direction =INHERIT
12-04 15:51:04.770: D/weex(30188): ###createComponentOnDomThread in WXRenderStatement dom = layout: {left: 0.0, top: 0.0, width: 0.0, height: 0.0, direction: LTR}direction =INHERIT
12-04 15:51:04.770: D/weex(30188): ###generateComponentTree in WXRenderStatement component = WXImage
12-04 15:51:04.775: D/weex(30188): ###callDomMethod to create component...task = {"args":[],"method":"createFinish","module":"dom"}
12-04 15:51:04.775: D/weex(30188): ###handleMessage in WXDomHandler...what = 255,obj = null
12-04 15:51:04.775: D/weex(30188): ###callDomMethod task = {"args":[],"method":"createFinish","module":"dom"}
12-04 15:51:04.775: D/weex(30188): ###createFinish
12-04 15:51:04.775: D/weex(30188): ###handleMessage in WXDomHandler...what = 3,obj = {"args":["_root",{"attr":{"value":"當地玩樂"},"ref":"154","style":{"color":"#444444","fontSize":28,"marginLeft":10},"type":"text"},-1],"instanceId":"3"}
12-04 15:51:04.775: D/weex(30188): ###addDom in WXDomManager instanceId = 3,parentRef = _root,element = {"attr":{"value":"當地玩樂"},"ref":"154","style":{"color":"#444444","fontSize":28,"marginLeft":10},"type":"text"},index = -1
12-04 15:51:04.780: D/weex(30188): ###addDom in WXDomStatement dom = {"attr":{"value":"當地玩樂"},"ref":"154","style":{"color":"#444444","fontSize":28,"marginLeft":10},"type":"text"},parentRef = _root,index = -1
12-04 15:51:04.780: D/weex(30188): ###createComponentOnDomThread in WXRenderManager dom = layout: {left: 0.0, top: 0.0, width: 0.0, height: 0.0, direction: LTR}direction =INHERIT
12-04 15:51:04.780: D/weex(30188): ###createComponentOnDomThread in WXRenderStatement dom = layout: {left: 0.0, top: 0.0, width: 0.0, height: 0.0, direction: LTR}direction =INHERIT
12-04 15:51:04.785: D/weex(30188): ###generateComponentTree in WXRenderStatement component = WXText
12-04 15:51:04.785: D/weex(30188): ###handleMessage in WXDomHandler...what = 9,obj = {"instanceId":"3"}
12-04 15:51:04.790: D/weex(30188): ###handleMessage in WXDomHandler...what = 255,obj = null
12-04 15:51:04.820: D/weex(30188): ###createBody in WXRenderStatement component = WXDiv
12-04 15:51:04.820: D/weex(30188): ###createView in WXComponent className = WXDiv
12-04 15:51:04.820: D/weex(30188): ###applyLayoutAndEvent in WXComponent className = WXDiv
12-04 15:51:04.825: D/weex(30188): ###bindData in WXContainer
12-04 15:51:04.825: D/weex(30188): ###bindData in WXComponent = WXDiv
12-04 15:51:04.825: D/weex(30188): ###updateProperties in props = {"alignItems":"center","backgroundColor":"#ffffff","flexDirection":"row","height":60,"position":"relative","width":750}
12-04 15:51:04.825: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.825: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.825: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.825: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.825: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.830: D/weex(30188): ###setProperty in WXComponent = WXDiv
12-04 15:51:04.830: D/weex(30188): ###updateProperties in props = {}
12-04 15:51:04.830: D/weex(30188): ###addComponent in WXRenderManager instanceId = 3,component = WXImage,parentRef = _root,index = -1
12-04 15:51:04.830: D/weex(30188): ###addComponent in WXRenderStatement to start render the component to view...
12-04 15:51:04.835: D/weex(30188): ###createView in WXComponent className = WXImage
12-04 15:51:04.835: D/weex(30188): ###applyLayoutAndEvent in WXComponent className = WXImage
12-04 15:51:04.835: D/weex(30188): ###bindData in WXComponent = WXImage
12-04 15:51:04.835: D/weex(30188): ###updateProperties in props = {"height":45,"marginLeft":100,"width":45}
12-04 15:51:04.835: D/weex(30188): ###setProperty in WXComponent = WXImage
12-04 15:51:04.835: D/weex(30188): ###setProperty in WXComponent = WXImage
12-04 15:51:04.835: D/weex(30188): ###setProperty in WXComponent = WXImage
12-04 15:51:04.840: D/weex(30188): ###updateProperties in props = {"src":"//img.alicdn.com/tfs/TB1AEcQNXXXXXX8XXXXXXXXXXXX-50-50.png"}
12-04 15:51:04.840: D/weex(30188): ###addComponent in WXRenderManager instanceId = 3,component = WXText,parentRef = _root,index = -1
12-04 15:51:04.840: D/weex(30188): ###addComponent in WXRenderStatement to start render the component to view...
12-04 15:51:04.840: D/weex(30188): ###createView in WXComponent className = WXText
12-04 15:51:04.840: D/weex(30188): ###applyLayoutAndEvent in WXComponent className = WXText
12-04 15:51:04.840: D/weex(30188): ###bindData in WXComponent = WXText
12-04 15:51:04.840: D/weex(30188): ###updateProperties in props = {"color":"#444444","fontSize":28,"marginLeft":10}
12-04 15:51:04.845: D/weex(30188): ###setProperty in WXComponent = WXText
12-04 15:51:04.845: D/weex(30188): ###updateProperties in props = {"value":"當地玩樂"}
12-04 15:51:04.850: D/weex(30188): ###execJS instanceId = 3,namespace = null,function = callJS,args = [{"data":"3","type":2},{"data":"[{\"args\":[\"_root\",\"viewappear\",null,null],\"method\":\"fireEvent\"}]","type":3}]
12-04 15:51:04.850: D/weex(30188): ###callNative in WXBridge instanceId = 3,tasks = [{"module":"dom","method":"updateFinish","args":[]}],callback = -1
12-04 15:51:04.855: D/weex(30188): ###callDomMethod to create component...task = {"args":[],"method":"updateFinish","module":"dom"}
12-04 15:51:04.855: D/weex(30188): ###callDomMethod task = {"args":[],"method":"updateFinish","module":"dom"}
12-04 15:51:04.855: D/weex(30188): ###updateFinish
12-04 15:51:04.860: D/weex(30188): ###handleMessage in WXDomHandler...what = 11,obj = {"instanceId":"3"}
12-04 15:51:04.875: D/weex(30188): ###handleMessage in WXDomHandler...what = 255,obj = null
View繪製過程對比
首先,我們看一下Android原聲的View繪製過程:
主要是measure測量大小,layout確定位置。
其次,我們對比一下Weex的WXComponent的測量和佈局過程;
主要是通過CSSLayout進行測量,使用view的setLayoutParams來確定View在父ViewGroup中的位置。
核心程式碼如下:
Spacing parentPadding = mParent.getDomObject().getPadding();
Spacing parentBorder = mParent.getDomObject().getBorder();
Spacing margin = mDomObj.getMargin();
int realWidth = (int) mDomObj.getLayoutWidth();
int realHeight = (int) mDomObj.getLayoutHeight();
int realLeft = (int) (mDomObj.getLayoutX() - parentPadding.get(Spacing.LEFT) -
parentBorder.get(Spacing.LEFT));
int realTop = (int) (mDomObj.getLayoutY() - parentPadding.get(Spacing.TOP) -
parentBorder.get(Spacing.TOP));
int realRight = (int) margin.get(Spacing.RIGHT);
int realBottom = (int) margin.get(Spacing.BOTTOM);
if (mPreRealWidth == realWidth && mPreRealHeight == realHeight && mPreRealLeft == realLeft && mPreRealTop == realTop) {
return;
}
if (mParent != null) {
mAbsoluteY = (int) (mParent.mAbsoluteY + mDomObj.getLayoutY());
mAbsoluteX = (int) (mParent.mAbsoluteX + mDomObj.getLayoutX());
}
//calculate first screen time
if (!mInstance.mEnd && !(mHost instanceof ViewGroup) && mAbsoluteY + realHeight > mInstance.getWeexHeight() + 1) {
mInstance.firstScreenRenderFinished();
}
if (mHost == null) {
return;
}
MeasureOutput measureOutput = measure(realWidth, realHeight);
realWidth = measureOutput.width;
realHeight = measureOutput.height;
if (mHost instanceof WXCircleIndicator) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(realWidth, realHeight);
params.setMargins(realLeft, realTop, realRight, realBottom);
mHost.setLayoutParams(params);
return;
}
//fixed style
if (mDomObj.isFixed() && mInstance.getRootView() != null) {
if (mHost.getParent() instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) mHost.getParent();
viewGroup.removeView(mHost);
}
FrameLayout.LayoutParams params