Weex技术剖析(1)

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值