Weex源码分析
Weex是跨平台的一种实践,令到开发者可以使用web语言来构建Android、iOS、web应用,实现一处编写,处处运行的效果,可以极大的降低人力成本,目前比较火的跨平台实践有React Native以及Flutter,weex相对小众一点,但是在功能上面也能够满足大部分的需求,而且接入也会相对简单,weex使用vue.js开发,本着知其然也知其所以然的原则,现在就来学习一下weex的源码。
Weex sdk架构
首先来看看Weex SDK的整体架构图:
以上是Weex sdk的架构图,分为两部分,功能层SDK以及相关的view组件,应用层通过WXSDKInstance去调用Weex提供的API,weex根据上层传入的URL去加载对应的js文件,并且调用v8引擎解析成相应的Android view组件,渲染在界面上。
Weex从js到native的加载过程
先用一个非常简约的图来概括一下Weex的加载过程,后面再详细细化:
那么Weex是如何把一个vue编写的文件加载到native呢,来看看示例(开发工具使用WebStorm或者借助Weex提供的官方在线工具):
test.vue
首先写一个非常简单的例子,在界面上绘制一个文本,点击弹出一个toast提示:
<template>
<div>
<text class="text" @click="clickTest">Test Test Test</text> //创建一个text文本,类似于Android的TextView,并且添加click监听
</div>
</template>
<script>
const modal = weex.requireModule('modal')
export default {
methods:{
clickTest:function () { //点击text,弹出一个toast提示。
modal.toast({
message: 'This is a toast',
duration: 0.3
})
}
}
}
</script>
<style scoped>
.text {
color: #41B883;
text-align: center;
font-size: 100px;
}
</style>
那么我们利用weex提供的官方工具,就能够看到如下,就几行简单的代码, 就可以在iOS、Android、web应用上运行:
test.js
那么weex app是如何将一个test.vue加载到native,然后将其显示出来的呢?我们用webStorm编译test.vue文件,得到生成.js文件,.js文件主要工作在以下三个function:
//1.封装template
(function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('text', {
staticClass: ["testStyle"],
on: {
"click": _vm.clickTest
}
}, [_vm._v("Test Test Test")])
},staticRenderFns: []}
module.exports.render._withStripped = true
})
//2.封装style
(function(module, exports) {
module.exports = {
"testStyle": {
"width": "200",
"height": "100"
}
}
//3.封装script
(function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var modal = weex.requireModule('modal');
exports.default = {
name: "test.vue",
methods: {
clickTest: function clickTest() {
modal.toast({
message: 'This is a toast',
duration: 0.3
});
}
}
};
weex会通过V8引擎去解析上述js文件,并且将解析的结果通过WXBridge告知android去绘制和渲染,接下来再去看看js的渲染过程:
Weex sdk 渲染过程
WeexSdkInstance渲染js文件的过程(java层)
weex的渲染流程:
WXSDKInstance调用renderByUrl(String url)去请求指定URL下面的js文件,获取到结果之后,赋值给framework,同时实例化WXSDKManager和WXRenderManager以及WXBridgeManager等Manager类,通过WXBridgeManager桥梁去通知JNI进行Javascript环境的初始化工作,并将framwork以及app版本、weex版本、系统版本等相关信息传至JNI层,JNI初始化完成之后,进行DomModule的注册工作,在进行过程中,根据状态回调结果至WXSDKManager,以上是java层渲染js文件的大致流程,下面看一下在JNI层的大概流程:
WeexSdkInstance渲染js文件的过程(C层)
上面遗留了两个方法的实现,WXBridge的initFramework和exeJS方法,先看一下具体的代码实现:
/**
* init Weex
*
* @param framework assets/main.js
* @param params 默认参数包括app版本、weex版本、系统版本等等...
* @return
*/
int initFrameworkEnv(String framework, WXParams params);
C层初始化framwork函数initFramework的具体实现:
/**
* @param object : 当前实例
* @param script : framework
* 初始化WXEnvironment,获取Java层传入的相关信息,完成framwork初始化相关工作
*/
jint Java_com_taobao_weex_bridge_WXBridge_initFramework(JNIEnv *env,
jobject object, jstring script,
jobject params) {
jThis = env->NewGlobalRef(object);
// no flush to avoid SIGILL
// const char* str= "--noflush_code_incrementally --noflush_code --noage_code";
const char *str = "--noflush_code --noage_code --nocompact_code_space"
" --expose_gc";
//Sets V8 flags from a string.
v8::V8::SetFlagsFromString(str, strlen(str));
v8::V8::Initialize();
globalIsolate = v8::Isolate::GetCurrent();
v8::HandleScope handleScope;
//初始WXEnvironment环境
WXEnvironment = v8::ObjectTemplate::New();
jclass c_params = env->GetObjectClass(params);
//从参数params中获取当前平台、App版本,weex版本、设备宽高等信息,设置给WXEnvironment
jmethodID m_platform = env->GetMethodID(c_params, "getPlatform", "()Ljava/lang/String;");
jobject platform = env->CallObjectMethod(params, m_platform);
WXEnvironment->Set("platform", jString2V8String(env, (jstring) platform));
env->DeleteLocalRef(platform);
...
//对native方法和C++方法进行绑定
V8context = CreateShellContext();
const char *scriptStr = env->GetStringUTFChars(script, NULL);
if (scriptStr == NULL || !ExecuteJavaScript(globalIsolate, v8::String::New(scriptStr), true)) {
return false;
}
setJSFVersion(env);
env->ReleaseStringUTFChars(script, scriptStr);
env->DeleteLocalRef(c_params);
return true;
/**
* Creates a new execution environment containing the built-in functions.
*
*/
v8::Persistent<v8::Context> CreateShellContext() {
// Create a template for the global object.
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
// Bind the global 'callNative' function to the C++ callNative.
global->Set(v8::String::New("callNative"), v8::FunctionTemplate::New(callNative));
// Bind the global 'callAddElement' function to the C++ callNative.
global->Set(v8::String::New("callAddElement"), v8::FunctionTemplate::New(callAddElement));
// Bind the global 'setTimeoutNative' function to the C++ setTimeoutNative.
global->Set(v8::String::New("setTimeoutNative"), v8::FunctionTemplate::New(setTimeoutNative));
// Bind the global 'nativeLog' function to the C++ Print callback.
global->Set(v8::String::New("nativeLog"), v8::FunctionTemplate::New(nativeLog));
// Bind the global 'WXEnvironment' Object.
global->Set(v8::String::New("WXEnvironment"), WXEnvironment);
return v8::Context::New(NULL, global);
}
接着看一下通过WXBridge调用C层方法的实现:
/**
* Execute JavaScript function
*
* @param instanceId
* @param namespace default global
* @param function function string name
* @param args WXJSObject array
*/
public native int execJS(String instanceId, String namespace, String function, WXJSObject[] args);
//创建实例传参:
WXJSObject instanceIdObj = new WXJSObject(WXJSObject.String,
instance.getInstanceId());
WXJSObject instanceObj = new WXJSObject(WXJSObject.String,
template);
//null
WXJSObject optionsObj = new WXJSObject(WXJSObject.JSON,
options == null ? "{}"
: WXJsonUtils.fromObjectToJSONString(options));
//ua、account、uid...
WXJSObject dataObj = new WXJSObject(WXJSObject.JSON,
data == null ? "{}" : data);
WXJSObject[] args = {instanceIdObj, instanceObj, optionsObj,
dataObj};
instance.setTemplate(template);
invokeExecJS(instance.getInstanceId(), null, METHOD_CREATE_INSTANCE, args, false);
具体的C代码:
/**
* Called to execute JavaScript such as . createInstance(),destroyInstance ext.
*
*/
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];
//WXJSObject : {type,data},type:1--number,2--string,3--JSON,4--WSON
jclass jsObjectClazz = env->FindClass("com/taobao/weex/bridge/WXJSObject");
//遍历args,取出其中的data值,结果赋给obj[].
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);
//NUMBER == 1
if (jTypeInt == 1) {
if (jDoubleValueMethodId == NULL) {
jclass jDoubleClazz = env->FindClass("java/lang/Double");
jDoubleValueMethodId = env->GetMethodID(jDoubleClazz, "doubleValue", "()D");
env->DeleteLocalRef(jDoubleClazz);
}
jdouble jDoubleObj = env->CallDoubleMethod(jDataObj, jDoubleValueMethodId);
obj[i] = v8::Number::New((double) jDoubleObj);
//String == 2
} else if (jTypeInt == 2) {
jstring jDataStr = (jstring) jDataObj;
obj[i] = jString2V8String(env, jDataStr);
//JSON == 3
} 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)));
//调用指定name的function
result = function->Call(global, length, obj);
}
...
return true;
}
}
JNI通过WXBridge的callNative来调用原生的方法:
v8::Handle<v8::Value> callNative(const v8::Arguments &args) {
JNIEnv *env = getJNIEnv();
//instacneID args[0]
jstring jInstanceId = NULL;
if (!args[0].IsEmpty()) {
v8::String::Utf8Value instanceId(args[0]);
jInstanceId = env->NewStringUTF(*instanceId);
}
//task args[1]
jbyteArray jTaskString = NULL;
if (!args[1].IsEmpty() && args[1]->IsObject()) {
v8::Handle<v8::Value> obj[1];
v8::Handle<v8::Object> global = V8context->Global();
json = v8::Handle<v8::Object>::Cast(global->Get(v8::String::New("JSON")));
json_stringify = v8::Handle<v8::Function>::Cast(json->Get(v8::String::New("stringify")));
obj[0] = args[1];
v8::Handle<v8::Value> ret = json_stringify->Call(json, 1, obj);
v8::String::Utf8Value str(ret);
int strLen = strlen(ToCString(str));
jTaskString = env->NewByteArray(strLen);
env->SetByteArrayRegion(jTaskString, 0, strLen,
reinterpret_cast<const jbyte *>(ToCString(str)));
// jTaskString = env->NewStringUTF(ToCString(str));
} else if (!args[1].IsEmpty() && args[1]->IsString()) {
v8::String::Utf8Value tasks(args[1]);
int strLen = strlen(*tasks);
jTaskString = env->NewByteArray(strLen);
env->SetByteArrayRegion(jTaskString, 0, strLen, reinterpret_cast<const jbyte *>(*tasks));
// jTaskString = env->NewStringUTF(*tasks);
}
//callback args[2]
jstring jCallback = NULL;
if (!args[2].IsEmpty()) {
v8::String::Utf8Value callback(args[2]);
jCallback = env->NewStringUTF(*callback);
}
if (jCallNativeMethodId == NULL) {
jCallNativeMethodId = env->GetMethodID(jBridgeClazz,
"callNative",
"(Ljava/lang/String;[BLjava/lang/String;)I");
}
int flag = env->CallIntMethod(jThis, jCallNativeMethodId, jInstanceId, jTaskString, jCallback);
if (flag == -1) {
LOGE("instance destroy JFM must stop callNative");
}
env->DeleteLocalRef(jTaskString);
env->DeleteLocalRef(jInstanceId);
env->DeleteLocalRef(jCallback);
return v8::Integer::New(flag);
}
上面主要是JNI利用V8引擎进行javaScript的初始化以及js的解析流程,完成了加载js文件之后,JNI通过callNative()让WXBridgeManager进行UI的渲染操作,UI的渲染操作分为以下两个步骤:
-
createBody()
-
addElement()