Weex技术剖析

Posted 伪装狙击手

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Weex技术剖析相关的知识,希望对你有一定的参考价值。

Weex简介

2016年4月21日,阿里巴巴在Qcon大会上宣布开源跨平台移动开发工具Weex,Weex能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持ios、安卓、YunOS及Web等多端部署。

对于移动开发者来说,Weex主要解决了频繁发版和多端研发两大痛点,同时解决了前端语言性能差和显示效果受限的问题。开发者只需要在自己的APP中嵌入Weex的SDK,就可以通过撰写html/CSS/javascript来开发Native级别的Weex界面。Weex界面的生成码其实就是一段很小的JS,可以像发布网页一样轻松部署在服务端,然后在APP中请求执行。

与 现有的开源跨平台移动开放项目如Facebook的React Native和微软的Cordova相比,Weex更加轻量,体积小巧。因为基于web conponent标准,使得开发更加简洁标准,方便上手。Native组件和API都可以横向扩展,方便根据业务灵活定制。Weex渲染层具备优异的性 能表现,能够跨平台实现一致的布局效果和实现。对于前端开发来说,Weex能够实现组件化开发、自动化数据绑定,并拥抱Web标准。

突出特点:

  • 致力于移动端,充分调度 native 的能力

  • 充分解决或回避性能瓶颈

  • 灵活扩展,多端统一,优雅“降级”到 HTML5

  • 保持较低的开发成本和学习成本

  • 快速迭代,轻量实时发布

  • 融入现有的 native 技术体系

  • 轻量化,体积小巧,语法简单,方便接入和上手

  • 可扩展,业务方可去中心化横向定制组件和功能模块

  • 原生渲染、体验流畅

此系列文章主要面向对Weex感兴趣的前端同学,通过解读Weex的JS源码来剖析实现原理,也便于在使用Weex过程中发挥框架优势,同时尽量避开短板。
首先简单说一下weex文件的编译。

Weex 文件编译

Weex框架目前支持2种文件格式:.we 和 .vue。本小节主要简单讲一讲.we文件的编译过程。
.we文件的一个示例如下

<template>
    <div class="container">
      <div style="height: 2000; background-color: #ff0000;"></div>
      <text onclick="foo">x</text>
    </div>
</template>

<style>
  .container 
      flex-direction: column;
      flex: 1;
  
</style>
<script>
  var dom = require('@weex-module/dom')
  module.exports = 
    data: function () 
      return 
        x: 1
      
    ,
    methods: 
      foo: function (e) 
        dom.scrollToElement(this.$el('r'),  offset: 0 )
      
    
  
</script>

可以看到,.we文件主要由template(UI模板), style(样式)和script(脚本)三个部分组成,编译器分别对这3个部分单独处理,最后合并为一个单独的jsbundle文件。templdate部分采用html5解析器转换为AST,然后从AST中提取有用的信息生成JSON对象(可以看作为简化的AST);style部分同样也通过AST输出为JSON对象;script部分则会处理require语句,并对代码进行封装,方便被框架调用。

接下来详细讲一讲编译好的jsbundle文件是如何在框架中加载运行的。jsbundle文件在被加载运行之前,首先是Weex框架的初始化工作。

Weex 框架初始化

Weex App启动初始化过程

首先在Application的onCreate函数,设置自定义配置项,然后调用initialize函数完成Engine的初始化工作

    WXSDKEngine.addCustomOptions("appName", "WXSample");
    WXSDKEngine.addCustomOptions("appGroup", "WXApp");
    WXSDKEngine.initialize(this,
                           new InitConfig.Builder()
                               .setImgAdapter(new ImageAdapter())
                               .setDebugAdapter(new PlayDebugAdapter())
                               .build()
                          );

addCustomOptions函数将自定义配置保存在WXEnvironment类中静态Map中。initialize函数调用doInitInternal(application,config)完成初始化工作,并修改状态。
InitConfig对象用来管理Engine使用的各个适配器对象,例如Http网络请求、图片加载、本地存储等等。
doInitInternal函数中,主要的工作是调用WXSDKManager的initScriptsFramework函数在JS端完成JS框架初始化工作

WXBridgeManager.getInstance().post(new Runnable() 
      @Override
      public void run() 
        long start = System.currentTimeMillis();
        WXSDKManager sm = WXSDKManager.getInstance();
        if(config != null ) 
        sm.setIWXHttpAdapter(config.getHttpAdapter());
        sm.setIWXImgLoaderAdapter(config.getImgAdapter());
         sm.setIWXUserTrackAdapter(config.getUtAdapter());
         sm.setIWXDebugAdapter(config.getDebugAdapter());
         sm.setIWXStorageAdapter(config.getStorageAdapter());
          if(config.getDebugAdapter()!=null)
           config.getDebugAdapter().initDebug(application);
          
        
        WXSoInstallMgrSdk.init(application);
        boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null);
        if (!isSoInitSuccess) 
          return;
        
        sm.initScriptsFramework(config!=null?config.getFramework():null);
        WXEnvironment.sSDKInitExecuteTime = System.currentTimeMillis() - start;
        WXLogUtils.renderPerformanceLog("SDKInitExecuteTime", WXEnvironment.sSDKInitExecuteTime);
      
    );
    register();

initScriptsFramework函数实际上调用WXBridgeManager的initScriptsFramework函数发送消息异步完成JS框架初始化,其中 framework为使用的框架名,what为消息类型编号 。INIT_FRAMEWORK消息在WXBridgeManager.handleMessage函数中被处理,调用initFramework函数来完成框架初始化工作。

 Message msg = mJSHandler.obtainMessage();
    msg.obj = framework;
    msg.what = WXJSBridgeMsgType.INIT_FRAMEWORK;
    msg.setTarget(mJSHandler);
    msg.sendToTarget();
消息类型的定义为:
public static final int INIT_FRAMEWORK = 0x07;

initFramework真正执行的函数是在WeexV8中实现C++函数Java_com_taobao_weex_bridge_WXBridge_initFramework,这个函数的主要工作是获取android应用相关信息,在JSCore中设置全局变量,例如platform,deviceWidth等。并且调用java端的getOptions函数,将option信息写入jscore的全局变量

jint Java_com_taobao_weex_bridge_WXBridge_initFramework(
JNIEnv *env,                                        jobject object, jstring script,                                                   jobject params) 
     ...
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);
    ...

register函数完成所有组件、API模块和DOM对象的注册;为了节省开销,利用BatchOperationHelper来批处理执行;

      registerComponent(
        new SimpleComponentHolder(
          WXText.class,
          new WXText.Creator()
        ),
        false,
        WXBasicComponentType.TEXT
      );
      registerComponent(WXBasicComponentType.A, WXA.class, false);

      registerModule("modal", WXModalUIModule.class, false);
      registerModule("webview", WXWebViewModule.class, true);

      registerDomObject(WXBasicComponentType.TEXT, WXTextDomObject.class);
      registerDomObject(WXBasicComponentType.INPUT, BasicEditTextDomObject.class);

组件注册由registerComponent函数完成,UI组件类封装为SimpleComponentHolder,并赋予一个或多个key值,注册项由WXComponentRegistry类进行管理,每个组件同时注册到原生端和JS端;

          registerNativeComponent(type, holder);
          registerJSComponent(registerInfo);

registerNativeComponent函数调用SimpleComponentHolder的loadIfNonLazy函数来完成初始化工作,主要是生成该组件API的元信息Map

  try 
          registerNativeModule(moduleName, factory);
         catch (WXException e) 
          WXLogUtils.e("", e);
        
        registerJSModule(moduleName, factory);

registerNativeModule函数将模块信息和模块工厂类保存在sModuleFactoryMap中。
registerJSModule函数则调用WXBridgeManager.registerModules函数,在JS端注册模块信息,将模块信息转换为JSON字符串,通过JS函数registerModules完成注册,将模块信息保存在nativeModules中。同样,在注册时JSFramework尚未初始化完成,则放入mRegisterModuleFailList中,等创建App时在重新注册

DOM对象注册由registerDomObject函数完成,调用WXDomRegistry.registerDomObject函数完成注册工作,DOM对象信息保存在sDom中。

Activity页面渲染过程

一个Weex应用运行在AbstractWeexActivity容器中,AbstractWeexActivity实现了IWXRenderListener接口,

public interface IWXRenderListener 
  void onViewCreated(WXSDKInstance instance, View view);
  void onRenderSuccess(WXSDKInstance instance, int width, int height);
  void onRefreshSuccess(WXSDKInstance instance, int width, int height);
  void onException(WXSDKInstance instance, String errCode, String msg);

AbstractWeexActivity在onCreate函数中创建一个WXSDKInstance实例,并注册registerRenderListener为自身。

    createWeexInstance();
    mInstance.onActivityCreate();

AbstractWeexActivity的renderPage函数完成jsbundle的加载和渲染工作,将jsbundle的url地址设置为option参数,并通过WXSDKInstance的render函数来加载和渲染页面。

protected void renderPage(String template,String source,String jsonInitData)
    Map<String, Object> options = new HashMap<>();
    options.put(WXSDKInstance.BUNDLE_URL, source);
    mInstance.render(
      getPageName(),
      template,
      options,
      jsonInitData,
      ScreenUtil.getDisplayWidth(this),
      ScreenUtil.getDisplayHeight(this),
      WXRenderStrategy.APPEND_ASYNC);
  

WXSDKInstance的renderByUrl函数,首先判断URL类型,如果是本地文件,则直接通过render函数开始渲染,否则通过WXRequest去下载URL。

public void renderByUrl(String pageName, final String url, Map<String, Object> options, final String jsonInitData, final int width, final int height, final WXRenderStrategy flag) 

    pageName = wrapPageName(pageName, url);
    mBundleUrl = url;
    if (options == null) 
      options = new HashMap<String, Object>();
    
    if (!options.containsKey(BUNDLE_URL)) 
      options.put(BUNDLE_URL, url);
    

    Uri uri=Uri.parse(url);
    if(uri!=null && TextUtils.equals(uri.getScheme(),"file"))
      render(pageName, WXFileUtils.loadAsset(assembleFilePath(uri), mContext),options,jsonInitData,width,height,flag);
      return;
    
    IWXHttpAdapter adapter=WXSDKManager.getInstance().getIWXHttpAdapter();
    WXRequest wxRequest = new WXRequest();
    wxRequest.url = url;
    if (wxRequest.paramMap == null) 
      wxRequest.paramMap = new HashMap<String, String>();
    
    wxRequest.paramMap.put("user-agent", WXHttpUtil.assembleUserAgent(mContext,WXEnvironment.getConfig()));
    adapter.sendRequest(wxRequest, new WXHttpListener(pageName, options, jsonInitData, width, height, flag, System.currentTimeMillis()));
    mWXHttpAdapter = adapter;
  

render函数会创建一个唯一实例Id,然后调用WXSDKManager的createInstance创建App实例。

    mInstanceId = WXSDKManager.getInstance().generateInstanceId();
    WXSDKManager.getInstance().createInstance(this, template, options, jsonInitData);
    mRendered = true;

WXSDKManager的createInstance函数首先在WXRenderManager注册当前实例,然后通过WXBridgeManager的createInstance函数在JS端创建App实例,具体工作由 invokeCreateInstance函数来实现。

void createInstance(WXSDKInstance instance, String code, Map<String, Object> options, String jsonInitData) 
    mWXRenderManager.registerInstance(instance);
    mBridgeManager.createInstance(instance.getInstanceId(), code, options, jsonInitData);
  

invokeCreateInstance首先调用initFramework来初始化框架,然后调用JS函数createInstance创建一个App实例。

private void invokeCreateInstance(String instanceId, String template,
                                    Map<String, Object> options, String data) 
    initFramework("");
    if (mMock) 
      mock(instanceId);
     else 
     ...
      try 
        WXJSObject instanceIdObj = new WXJSObject(WXJSObject.String,
                instanceId);
        WXJSObject instanceObj = new WXJSObject(WXJSObject.String,
                                                template);
        WXJSObject optionsObj = new WXJSObject(WXJSObject.JSON,
                options == null ? ""
                        : WXJsonUtils.fromObjectToJSONString(options));
        WXJSObject dataObj = new WXJSObject(WXJSObject.JSON,
                data == null ? "" : data);
        WXJSObject[] args = instanceIdObj, instanceObj, optionsObj,
                dataObj;
        invokeExecJS(instanceId, null, METHOD_CREATE_INSTANCE, args);
       catch (Throwable e) 
      ...
      
    
  

initFramework函数从本地文件读取main.js获取框架名,然后调用WXBridge的initFramework函数完成指定框架的初始化,initFramework函数由C++实现,通过JNI调用(在libweexv8.so中).

private void initFramework(String framework)
    if (!isJSFrameworkInit()) 
      if (TextUtils.isEmpty(framework)) 
        if (WXEnvironment.isApkDebugable()) 
          WXLogUtils.d("weex JS framework from assets");
        
        framework = WXFileUtils.loadAsset("main.js", WXEnvironment.getApplication());
      
      ...
      try 
        long start = System.currentTimeMillis();
        if(mWXBridge.initFramework(framework, assembleDefaultOptions())==INIT_FRAMEWORK_OK)
          WXEnvironment.sJSLibInitTime = System.currentTimeMillis() - start;
          WXLogUtils.renderPerformanceLog("initFramework", WXEnvironment.sJSLibInitTime);
          WXEnvironment.sSDKInitTime = System.currentTimeMillis() - WXEnvironment.sSDKInitStart;
          WXLogUtils.renderPerformanceLog("SDKInitTime", WXEnvironment.sSDKInitTime);
          mInit = true;
          execRegisterFailTask();
          WXEnvironment.JsFrameworkInit = true;
          registerDomModule();
          commitAlert(IWXUserTrackAdapter.JS_FRAMEWORK,WXErrorCode.WX_SUCCESS);
        else
          ...
        
       catch (Throwable e) 
       ...
      
    
  

execRegisterFailTask函数尝试重新注册之前注册失败的模块和组件,

  private void execRegisterFailTask() 
    int moduleCount = mRegisterModuleFailList.size();
    if (moduleCount > 0) 
      for (int i = 0; i < moduleCount; ++i) 
        registerModules(mRegisterModuleFailList.get(i));
      
      mRegisterModuleFailList.clear();
    
    int componentCount = mRegisterComponentFailList.size();
    if (componentCount > 0) 
      for (int i = 0; i < componentCount; ++i) 
        registerComponents(mRegisterComponentFailList.get(i));
      
      mRegisterComponentFailList.clear();
    
  

registerDomModule函数

  private void registerDomModule() throws WXException 
    if (sDomModule == null)
      sDomModule = new WXDomModule();
    /** Tell Javascript Framework what methods you have. This is Required.**/
    Map<String,Object> domMap=new HashMap<>();
    domMap.put(WXDomModule.WXDOM,WXDomModule.METHODS);
    registerModules(domMap);
  

当框架初始化完成,组件和API模块都注册成功,调用invokeExecJS函数执行JS端的createInstance函数,创建一个App实例。

JS端的createInstance函数创建一个App对象,保存在instanceMap中,然后调用InitApp初始化对象。

export function createInstance (id, code, options, data) 
  resetTarget()
  let instance = instanceMap[id]
  /* istanbul ignore else */
  options = options || 
  let result
  /* istanbul ignore else */
  if (!instance) 
    instance = new App(id, options)
    instanceMap[id] = instance
    result = initApp(instance, code, data)
  
  else 
    result = new Error(`invalid instance id "$id"`)
  
  return result

initApp函数(也就是init函数)将jsbundle代码封装给一个函数,并定义上下文环境(包括 ‘define’,’require’,’weex_define‘, 等等),并执行模块函数。

export function init (app, code, data) 
  console.debug('[JS Framework] Intialize an instance with:\\n', data)
  let result

  // prepare app env methods
  const bundleDefine = (...args) => defineFn(app, ...args)
  const bundleBootstrap = (name, config, _data) => 
    result = bootstrap(app, name, config, _data || data)
    updateActions(app)
    app.doc.listener.createFinish()
    console.debug(`[JS Framework] After intialized an instance($app.id)`)
  
  const bundleVm = Vm
  /* istanbul ignore next */
  const bundleRegister = (...args) => register(app, ...args)
  /* istanbul ignore next */
  const bundleRender = (name, _data) => 
    result = bootstrap(app, name, , _data)
  
  /* istanbul ignore next */
  const bundleRequire = name => _data => 
    result = bootstrap(app, name, , _data)
  
  const bundleDocument = app.doc
  /* istanbul ignore next */
  const bundleRequireModule = name => app.requireModule(removeWeexPrefix(name))

  // prepare code
  let functionBody
  /* istanbul ignore if */
  if (typeof code === 'function') 
    // `function () ...` -> `...`
    // not very strict
    functionBody = code.toString().substr(12)
  
  /* istanbul ignore next */
  else if (code) 
    functionBody = code.toString()
  

  // wrap IFFE and use strict mode
  functionBody = `(function(global)"use strict"; $functionBody )(Object.create(this))`

  // run code and get result
  const  WXEnvironment  = global
  /* istanbul ignore if */
  if (WXEnvironment && WXEnvironment.platform !== 'Web') 
    // timer APIs polyfill in native
    const timer = app.requireModule('timer')
    const timerAPIs = 
      setTimeout: (...args) => 
        const handler = function () 
          args[0](...args.slice(2))
        
        timer.setTimeout(handler, args[1])
        return app.uid.toString()
      ,
      setInterval: (...args) => 
        const handler = function () 
          args[0](...args.slice(2))
        
        timer.setInterval(handler, args[1])
        return app.uid.toString()
      ,
      clearTimeout: (n) => 
        timer.clearTimeout(n)
      ,
      clearInterval: (n) => 
        timer.clearInterval(n)
      
    

    const fn = new Function(
      'define',
      'require',
      'document',
      'bootstrap',
      'register',
      'render',
      '__weex_define__', // alias for define
      '__weex_bootstrap__', // alias for bootstrap
      '__weex_document__', // alias for bootstrap
      '__weex_require__',
      '__weex_viewmodel__',
      'setTimeout',
      'setInterval',
      'clearTimeout',
      'clearInterval',
      functionBody
    )

    fn(
      bundleDefine,
      bundleRequire,
      bundleDocument,
      bundleBootstrap,
      bundleRegister,
      bundleRender,
      bundleDefine,
      bundleBootstrap,
      bundleDocument,
      bundleRequireModule,
      bundleVm,
      timerAPIs.setTimeout,
      timerAPIs.setInterval,
      timerAPIs.clearTimeout,
      timerAPIs.clearInterval)
  
  else 
    const fn = new Function(
      'define',
      'require',
      'document',
      'bootstrap',
      'register',
      'render',
      '__weex_define__', // alias for define
      '__weex_bootstrap__', // alias for bootstrap
      '__weex_document__', // alias for bootstrap
      '__weex_require__',
      '__weex_viewmodel__',
      functionBody
    )

    fn(
      bundleDefine,
      bundleRequire,
      bundleDocument,
      bundleBootstrap,
      bundleRegister,
      bundleRender,
      bundleDefine,
      bundleBootstrap,
      bundleDocument,
      bundleRequireModule,
      bundleVm)
  

  return result

以上是关于Weex技术剖析的主要内容,如果未能解决你的问题,请参考以下文章

Weex快速上手教程(Weex Tutorial)

Weex原理及架构剖析

Weex快速上手

Weex 快速上手教程

weex快速上手

weex环境配置快速上手