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源码地址

上面遗留了两个方法的实现,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()

先来看看createBody的过程,如下图:

在这里插入图片描述

创建完body之后,如何添加元素呢,如下图:

在这里插入图片描述

以下是具体的绘制流程:

在这里插入图片描述

android原生绘制view和weex绘制的对比

先来看看Android的绘制过程:

在这里插入图片描述

再看看weex的绘制过程:

在这里插入图片描述

两者对比:

在这里插入图片描述

Logo

智屏生态联盟致力于大屏生态发展,利用大屏快应用技术降低开发者开发、发布大屏应用门槛

更多推荐