weex技术剖析

伪装狙击手 伪装狙击手     2022-12-04     594

关键词:

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-vue-framework向原生端发送渲染指令,最终渲染生成的是原生组件。WXBridge是weex实现的一种js和客户端通信的机制。客户端设计一套JSBridge,让native代码可以和JavaScript引擎相互通信,Weex源码转换成JSBundle,异步更新早期H5和Hybrid方... 查看详情

原创《weex面向未来的架构》

最近一直在做weex的调研工作,整理之后给公司做了一次技术分享。分享内容如下:1:Weex是什么?2: Weex目前能做什么?3: Weex如何调试4: 剖析一下Weex原理5: 跨平台通用组件6: Weex的未来发展 1:weex是什么?... 查看详情

vue.js作者尤雨溪加盟weex团队担任技术顾问

...阿里巴巴Weex团队(腾云科技ty300.com),尤雨溪称他将以技术顾问的身份加入Weex团队来做Vue和Weex的JavaScriptruntime整合,目标是让大家能用Vue的语法跨三端,Weex又壮大了!Weex是阿里巴巴今年6月底正式开源的一个项目,旨在提供拥... 查看详情

技术剖析-全托管dnsnvm

查看详情

weex和appcan的个人理解

appcan是浏览器技术,前端代码运行在webview上,而weex是原生引擎渲染,说白了就是把H5翻译成原生。weex的官网上说,在开发weex页面就像开发普通网页一样,在渲染weex页面时和原生页面一样。weex的标签,像<div>等在移动端渲... 查看详情

bat解密:互联网技术发展之路-开发层技术剖析

BAT解密:互联网技术发展之路(5)-开发层技术剖析1.开发框架在系列文章的第2篇“BAT解密:互联网技术发展之路(2)-业务怎样驱动技术发展”中我们深入分析了互联网业务发展的一个特点:复杂性越来越高。复杂性添加的典... 查看详情

weex入门指南

...,不能很好应对这种变化,正在此前提下热修复和热更新技术也有了发展的空间,不管热修复还是热更新,都是对app内容或者逻辑的变化作出像web网页更新类似的体验。weex阿里推出的热更新框架,已再内部应用在多款app上如淘... 查看详情

大叔最新课程~ef核心技术剖析

EF核心技术剖析介绍数据上下文(共享对象与实例对象的选择)自动初始化(Initializer初始化的几种方式)数据迁移(Migrations如何使用及其重要作用)实体关系映射(一对一,一对多,多对多)延时加载和include立即加载写SaveChanges解决... 查看详情

weex手机端安全键盘(代码片段)

github地址:weexSafeKeyboard效果图:技术依赖:框架:weex+vue弹出层:weex-ui图标:iconfont说明:1、如果不想用到weex-ui,可以把inputkey.vue文件里的wxc-popup组件去掉,按自己的弹窗实现即可;2、删除、大小写、空格图标用的是iconfont;如... 查看详情

weex开发初探

参考技术A1、DSL:weex文件;2、VirtualDOM(提升性能):简单的说就是:3、AndroidRenderEngine将输入VirtualDOM转换成输出的android原声控件;1.Weex和H5对比2.Weex和RN对比3.Weex优势4.Weex劣势1.使用本地图片<imageclass="setBtn"src='xcassets... 查看详情

reactnative技术剖析

...程中记录的笔记,结合RN源代码来分析RN框架里面的一些技术思路,这对于理解和更好地使用RN都是很有益处的。由于水平有限,肯定有一些理解不对的地方欢迎指正。今天主要讲一下,RN初始化过程是什么步骤,都做了哪些事情... 查看详情

docker技术剖析--镜像容器管理

防伪码:博观而约取,厚积而薄发                                查看详情

docker技术剖析--docker网络

防伪码:不经一番寒彻骨,怎得梅花扑鼻香。                               查看详情

docker技术剖析--dockerfileandregistry(构建容器和私有仓库)

...一段香          docker技术剖析--dockerfileandregistry(构建容器和私有仓库)一、根据Dockerfile构建出一个容器1、Dockfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对... 查看详情

简单剖析智能语音交互技术

机器学习和自然语言处理技术的进步为语音与人工智能的交互提供了可能。人们可以通过对话获得信息,并与机器互动,而机器将不再只存在于科幻小说中。语音交互是未来的发展方向。智能扬声器是语音交互着陆的第一代产品... 查看详情

muduo源码剖析:atomicintegert原子技术

//DOTO:该类是对整数的原子操作注:使用原子操作时,编译选项要加-march=cpu-type(cpu-type可以直接填写native自动获取本机系统的CPU类型) 查看详情

muduo源码剖析:atomicintegert原子技术

//DOTO:该类是对整数的原子操作注:使用原子操作时,编译选项要加-march=cpu-type(cpu-type可以直接填写native自动获取本机系统的CPU类型) 查看详情

2018-2019-220165314『网络对抗技术』final:反弹木马的剖析与实现

参考文献:木马攻击与隐蔽技术研究线程注入式无进程木马的实现原理木马技术研究及反弹木马系统的设计与实现 查看详情