shit happens

Outlines

  • Weex_SDK(Android)
    • SDKManager
      • BriageManager(如何通信)
      • DomManager (如何操作)
      • RenderManger (如何渲染)
    • 任务管理 ?
    • 加载流程
      • 页面加载
      • 页面切换
      • 页面点击处理
  • Weex_JS
    • Mvvm模式
    • Vue数据绑定模型
    • 调试方法

前言

Weex作为集团开源框架,同学们平时可能更多的是对于其应用、开发。但对于做移动端的同学,在这三端统一的需求更加强烈的时代,我们需要的不仅仅是使用,更多需要认真理解其中的原理。在学习和工作的过程中,扩展自身的技术栈。

首先我们先要了解Weex是什么?

Weex的目标是一套构建高性能、可扩展的原生应用跨平台解决方案。Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发环境到云端部署和分发的整个链路。开发者首先可以在本地像撰写 web 页面一样撰写一个 app 的页面,然后编译成一段 JavaScript 代码,形成 Weex 的一个 JS bundle;在云端,开发者可以把生成的 JS bundle 部署上去,然后通过网络请求或预下发的方式传递到用户的移动应用客户端;在移动应用客户端里,WeexSDK 会准备好一个 JavaScript 引擎,并且在用户打开一个 Weex 页面时执行相应的 JS bundle,并在执行过程中产生各种命令发送到 native 端进行的界面渲染或数据存储、网络通信、调用设备功能、用户交互响应等移动应用的场景实践;同时,如果用户没有安装移动应用,他仍然可以在浏览器里打开一个相同的 web 页面,这个页面是使用相同的页面源代码,通过浏览器里的 JavaScript 引擎运行起来的。

outlines

以上是官方给出的业务流程图以及文字介绍,我们通过上面的内容,结合实际开发经验可以总结出。Weex能够通过编写以.we.vue结尾的文件,打包成JSBundle,即一堆.js文件。客户端通过本地或者远程读取JSBundle,各自再使用SDK进行渲染,同时也可以与JSBundle进行逻辑上交互。

综上我们可以把Weex这套系统分为两个部分,WeexSDK和WeexJS。因为Weex系统较为复杂,而且部分原理的资料网络上可以获取的很多。笔者假设了几个问题,从问题出发去思考、理解。

  • WeexSDK是如何设计与实现的?
  • 客户端与JS是如何通信的?
  • WeexSDK在一个场景下的加载流程是什么?
  • WeexJS端是如何设计与实现的?
  • 如何做到数据绑定的?

下面,笔者通过以上五个问题作为索引,带着问题去理解Weex到底做了些什么。

WeexSDK(Android)

首先笔者去获得一份WeexSDK的Android源代码。笔者使用的是github上开源版本的Weex。下载完成后,我们去观察其文件夹的结构,如下图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
├── adapter #适配器的接口
├── appfram #适配器的实现
├── annotation #注解
├── bridge #通信交互相关
├── common #公共组建
├── dom #dom相关
├── http #网络请求
├── ui #渲染相关、组件的实现
├── utils #工具类
├── IWXActivityStateListener.java
├── IWXRenderListener.java #渲染的生命周期监听
├── IWXStatisticsListener.java #SDK对外的监听
├── InitConfig.java #config构造器
├── RenderContainer.java #渲染单元最外层的容器
├── WXEnvironment.java
├── WXGlobalEventModule.java
├── WXGlobalEventReceiver.java
├── WXRenderErrorCode.java
├── WXSDKEngine.java #SDK加载管理
├── WXSDKInstance.java #渲染单元
└── WXSDKManager.java #SDK管理类

从上面的图片中,我们可以看出来,weex的文件夹分类还是非常清晰的。WXSDKEngine负责一系列的初始化工作,其中比较重要的两点功能为:初始化So库和InitConfig构造器的配置,即实现了appfram文件夹下的一系列Adapter的协议。值得一提的是,对于Adapter的设计主要为了方便 服务化的实现。减小了具体功能实现的替换难度。

这时有一个问题,什么样子的服务需要通过Adapter实现呢?我们整理一下,看看有什么样子的Adapter接口需要实现。如下图我们可以看出,需要掉到原生功能,并且替换性很强的模块均采用了Adapter处理。

1
2
3
4
5
6
7
8
├── IDrawableLoader.java
├── IWXDebugAdapter.java #Debug
├── IWXHttpAdapter.java #网络请求
├── IWXImgLoaderAdapter.java #图片加载
├── IWXJSExceptionAdapter.java #异常处理
├── IWXSoLoaderAdapter.java #So加载
├── IWXUserTrackAdapter.java #采集器
└── URIAdapter.java #地址转换

作为最重要的类WXSDKManager,从名称上可以看出其作为基础的管理类,起到承上启下的关键作用。为什么说承上启下呢?

承上:指的是WXBridgeManagerWXRenderManagerWXDomManager三个管理器与Adapter的管理工作。通信渲染Dom操作作为最核心的三件事情,均可以通过WXSDKManager获取调用。以上4个Manager均是采用单例的设计模式,这样在处理多个WXSDKInstance的场景时,可以依赖具体Manager的职责进行任务管理。启下:指的是对于WXSDKInstance,作为渲染实例,如果需要向上调用具体的方法,可以通过WXSDKManager反过来调用具体方法。

引用别人画的一张图
sdkmanager

WXBridgeManager

WXBridgeManager从命名上很明显可以看出来,是用来管理通信的,负责JS与Native端的交流。我们考虑从如何通信、有哪些通信这两个方面去考虑,WXBridgeManager做了什么工作。

如何通信

WXBridgeManager作为通信管理类,是负责从JS到Native来回的双向的通信。其中WXBridge为实际通信的实例,每一个WXBridgeManager仅有一个WXBridge,这就意味着app也只有一个通信实例。后面我们会提到Briage的任务管理会提到。我们分别看下具体做了哪些工作。

Native到JS:

1
mWXBridge.execJS(instanceId, namespace, function, args);

所有的对JS通信都是通过上面这个方法执行的,即Native到JS的通信主要是全部由WXBrige进行发送给V8。如果有结果再统一通过callNative返回。其中JS对Native方法参数function如下图。一共有11种调用方法通知JS端。

1
2
3
4
5
6
7
8
9
10
11
12
public static final String METHOD_CREATE_INSTANCE = "createInstance";
public static final String METHOD_DESTROY_INSTANCE = "destroyInstance";
public static final String METHOD_CALL_JS = "callJS";
public static final String METHOD_SET_TIMEOUT = "setTimeoutCallback";
public static final String METHOD_REGISTER_MODULES = "registerModules";
public static final String METHOD_REGISTER_COMPONENTS = "registerComponents";
public static final String METHOD_FIRE_EVENT = "fireEvent";
public static final String METHOD_CALLBACK = "callback";
public static final String METHOD_REFRESH_INSTANCE = "refreshInstance";
public static final String METHOD_NOTIFY_TRIM_MEMORY = "notifyTrimMemory";
public static final String METHOD_NOTIFY_SERIALIZE_CODE_CACHE =
"notifySerializeCodeCache";

其中值得注意的是argsWXJSObject[]类型,不同方法对应不同的数组长度,我建议把WXJSObject[]理解为一个json数组,其中WXJSObject里有两个key值分别是type、data。
我们拿传输方法createInstance举例,看看到底具体数据传输是如何进行的。

1
2
3
4
5
6
7
8
[
{"data":"7","type":2},//通知instanceId为7的渲染实例,type=2为String
{"data":"/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/....","type":2},//JSBundle通知给V8引擎
{"data":{"bundleUrl":"http://30.27.52.104:12580/examples/build/index.js",
"codeCachePath":"/data/user/0/com.alibaba.weex/files/v8/"},"type":3},
//需要的参数参数 type=3为Json格式
{"data":{},"type":3}//自定义的参数
]

以上就是一个createInstance的创建传输args参数具体的内容。我们大概明白了SDK对JS到底传输了什么内容。

JS到Native:

1
2
3
4
5
6
7
public static final String COMPONENT = "component";
public static final String REF = "ref";
public static final String MODULE = "module";
public static final String METHOD = "method";
public static final String ARGS = "args";
private static final String NON_CALLBACK = "-1";
private static final String UNDEFINED = "undefined";

在上面createInstance通知V8创建完成之后,V8同步在JS线程里回调方法。主要是包括如下几个方法。标星号的重点看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
public int callNative(String instanceId, String tasks, String callback) {} #调用原生方法 *
public Object callNativeModule(String instanceId, String module, String method, JSONArray arguments, Object options) {} #调用模块方法 *
public int callAddElement(String instanceId, String ref, byte[] dom, String index, String callback) {} #增加新的元素 *
public Object callNativeComponent(String instanceId, String componentRef, String method, JSONArray arguments, Object options) {} #调用组件的方法
public void reportJSException(String instanceId, String func, String exception) {} #错误通知
public void setTimeoutNative(String callbackId, String time) {} #设置超时事件
public void setJSFrmVersion(String version) {} #V8库加载完成回调版本号

有哪些通信

参考

参考:Weex SDK Android 源码解析