Weex总结系列(三):渲染过程分析

1,003 阅读4分钟

1、前言

不管是之前的初始化还是各种组件的注册,最终的目的都是将前端的js文件转换为客户端的Native视图,也就是渲染的过程。这一部分涉及的内容比较多,主要可以细分为js framework的加载和运行、js bundle的执行、事件的传递等

2、js framework的运行

系列一已经说到了SDK初始化js framework的过程,加载的就是weex-main-jsfm.js,是WeexSDK在生成aar时直接打包到assets目录下的。这一大坨字符串又是从哪来的?其实就是将weex源码工程的runtime目录下一堆js文件打包生成的:

runtime
  |-- api:冻结原型链,提供给原生调用的方法,如registerComponents
  |-- bridge:和客户端桥接的相关接口,如createBody等,指令最后会通过这里调用客户端
  |-- entries:客户端执行js framework 的入口文件
  |-- frameworks:核心文件,初始化js bundle实例、管理实例、返回错误码等
  |-- services:broadcast调度转换等
  |-- shared:抹平console、setTimeout等差异性的方法
  |-- vdom:重要文件,将VDOM转化成客户端能渲染的指令

加载是通过WXBridge的initFrameworkEnv完成的,最后当然还是js引擎负责去解析weex-main-jsfm.js,在0.28.0版本中该引擎并未包含在weex sdk中,需要注意下。加载的结果返回1说明js framework加载成功,后续才能正常的使用weex的功能。执行js framework的入口是runtime/entries/setup.js,来看一下都干了些什么事情:

/**
 * Setup frameworks with runtime.
 * You can package more frameworks by
 *  passing them as arguments.
 */
export default function (frameworks) {
  const { init, config } = runtime
  config.frameworks = frameworks
  const { native, transformer } = subversion

  for (const serviceName in services) {
    runtime.service.register(serviceName, services[serviceName])
  }

  runtime.freezePrototype()

  // register framework meta info
  global.frameworkVersion = native
  global.transformerVersion = transformer

  // init frameworks
  const globalMethods = init(config)

  // set global methods
  for (const methodName in globalMethods) {
    global[methodName] = (...args) => {
      const ret = globalMethods[methodName](...args)
      if (ret instanceof Error) {
        console.error(ret.toString())
      }
      return ret
    }
  }

主要完成的功能是:

  • 挂载原型链方法和全局属性方法
  • 创建客户端通信桥

挂载原型链方法和全局属性方法

初始化中最主要的部分是通过runtime/api/init.js完成的,这里对原型链方法和全局属性方法进行挂载,说白了也是和客户端交互的接口,在这里能看到熟悉的registerComponents等:

* init: 页面内部生命周期初始化
* registerComponents:注册 Component
* registerMoudles:注册 Module
* createInstance: 页面内部生命周期创建
* refreshInstance: 页面内部生命周期刷新
* destroyInstance: 页面内部生命周期销毁
* getRoot:获取页面节点
.....

registerComponents,就是前端缓存到map中,解析VDom的时候映射,然后发送指令给原生进行渲染。

创建客户端通信桥

既然是客户端要和前端交互,那肯定少不了桥。类似H5和客户端的交互,通信桥要实现的也是js调用native、native调用js。从api/init.jsbridge/TaskCenter.js能看到定义了一些供双方互相调用的方法。因为最终还是遵循的JS -> C++ -> Java的通信机制,所有桥的方法都定义在weexcore/Source/android/wrap/wx_brdige.h中。js调用native一个很明显的方法就是callNative,除此之外常用的还有callCreateBody、callAddElement等,native调用js一个最直接的方法就是execJS,其他常用的还有createInstanceContext、destroyInstance等。在上层都被定义在IWXBridge中,实现类是WXBridge

3、渲染过程

渲染过程涉及到java、c++、javascript,细节比较多,主要可以拆分成下面几个部分:

3.1、页面实例的初始化

js framework已经准备好了,可以准备开始渲染界面了。WXSDKInstance是weex渲染页面的基本单元,创建一个最基本的weex容器:

public class MainActivity extends AppCompatActivity implements IWXRenderListener {
  WXSDKInstance mWXSDKInstance;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mWXSDKInstance = new WXSDKInstance(this);
    mWXSDKInstance.registerRenderListener(this);
    
    mWXSDKInstance.renderByUrl(pageName, bundleUrl, null, null,WXRenderStrategy.APPEND_ASYNC);
  }
  
  @Override
  public void onViewCreated(WXSDKInstance instance, View view) {
    setContentView(view);
  }
  //其他回调

官方的示例中是传入url,从网络拉取js bundle然后加载执行,默认用SDK自带的通过DefaultWXHttpAdapter请求,内部使用了一个定长为3的FixedThreadPool,没有做过优化或处理,因此一般都是自己去处理网络请求这块,拿到js bundle然后通过WXSDKInstance#render去解析渲染。

3.2、渲染的准备阶段

总的来说,这一段从WXSDKInstance#render开始,经由WXBridge从UI线程切换到子线程,给当前页面生成一个唯一id,并且将js bundle以及设备相关信息通过createInstanceContext传递给js引擎,然后核心部分由底层代码处理。这里有两个关键部分:一是js bundle,这里被包装成Wson的数据结构,解析为RenderObject,而RenderPage则负责关键部分。二是相关指令,主要是创建根视图和添加子元素,最终都会抽象成java层的Action执行

3.3、主要Action类的UML图

4、事件传递过程

5、Weex的刷新机制