目录

给你科普科普跨端

前言

鉴于好多人对于跨端技术觉得玄之又玄,我自己虽然没有实际从0到1的写过跨端框架,但是使用过 Yoga(React-Native 的布局引擎)做过一些简易的跨端的事且后来使用过 Weex,对于跨端研究过一阵,所以来科普一下。

在科普之前,首先你得知道,为什么需要跨端技术呢?我们平时说的 Weex、React-Native(本文统一简称为 RN) 算不算 跨端技术(单独没说 Flutter) 呢?

为什么需要跨端呢?

一套代码,多端运行,减少了人力成本?

其实不是。看似说 Android/iOS 本来要两个人,最终变成一个人了,我这缩减了一半的人力!

但是前提是,这个人力需要既会 Android、又会 iOS、还得会写 JavaScript,而且还都得比较懂,否则出了问题,怎么修?

所以想招这么个人,在中国互联网的环境下挺难的。大家都在研究 PPT 架构技术职场生存论30岁如何财富自由,哪有空拓展自己的技术栈呢?

动态化能力(这个我认为才是重点)

端上开发苦不堪言,动不动出个 Crash(应用崩溃、闪退) 又没法远程修复,只能等 下个版本 往应用市场推新的修复 Bug 的版本。

但是你推了新版本,用户也 不一定升级 啊。所以,很多公司研究了各种 热修复 框架,特别是 Android 平台上,热修复框架很多,主要是靠 DexClassLoader 来搞。

H5 的动态化能力

所以,很多活动页经常使用 H5 来做,H5 做的话只要更新下 js、css 等前端各种资源到服务器上,那么就一切 OK 了。而且还能在浏览器上运行,浏览器本身就是一个跨端的 APP,前端研发也不难招,多开心。

所以直至今日,很多 APP 依然是混合开发,也就是所谓的 Hybrid APP(就是通过各自平台提供的 WebView 来加载一个网页,通过更新网页源码来实现动态化)。

但是,最早的时候 WebView 问题很大,特别是 Android。而且加载网页嘛,肯定需要时间的,过程中会白屏啊等等。所以很多人围绕着这些做了很多优化,我个人感觉最有用的其实就是一个离线包。同时,每一代的 WebView 也在更新升级。然后一些实力强的公司都自己研制了所谓 浏览器内核,各种黑科技,怎么提升速度啊,各种特性支持啊等等,但是好像都没有开源🐶。

平时说的 Weex、RN、Hippy、MLN 等等,算不算跨端?

算也不算。这只是跨了 Android 和 iOS,不把我 PC 端当端?

其实浏览器才是跨端,每个平台上你都可以使用 Chrome(其他浏览器主要是想不想搞)!但是也有自己的问题,因为每家都有自己的浏览器且有不同的内核且分裂越来越大,Chrome(Blink)/Safari(Webkit)/Firefox(Gecko?) 等等,特别是对 css 的支持。

https://developer.mozilla.org/zh-CN/docs/Web/CSS 这个网址可以看一些浏览器的兼容性。比如 border-width 的兼容性如下:

/img/in-post/border-support.jpg

那么问题来了,为啥不直接用浏览器的渲染技术来做跨端呢?

其实也不是不可以啊,但是这么做就相当于直接面向 OpenGL 或者其他图形引擎编程了,而且要从底层一路撸到上层,自己搞一套 渲染机制,并且还要封装好各种基本的 UI 组件供 开发者使用,或者留下很多口子,供开发者自定义 UI,是很复杂的。但是其实 Flutter 就是这么干的,所以 Flutter2.0 又开始往桌面端发展了,已经不止于 Android/iOS 了,但是究竟能走多远,这个我也不知道。还有一些人在搞 React-Native-Skia,就用 js 代码来直接调 Skia(2D图形渲染引擎)?(没具体看过)

什么是 JavaScript Engine

为什么你写的 JavaScript 代码可以运行呢?靠的就是 JavaScript Engine

丢给它一段 js 代码(其实就是一段文本字符串),它就可以帮你算出结果,处理逻辑。

常见的 Weex、RN、Hippy 也是依赖于此(MLN 使用的是 Lua)来做逻辑处理的。

这个时候又会有很多概念

JavaScript Engine 有人也喜欢统说为 JavaScriptCore(为什么这么说我也不知道,可能是因为研究这些比较深入的都是 iOS 开发者,因为苹果的 JavaScript Engine 就叫做 JavaScriptCore,苹果的这个 JavaScriptCore 呢,很多人有喜欢叫 JSCore 或者 JSC)。所以,后来我看到这些个名词,我总要带入语境去感受他是想说 JavaScript Engine,还是想说苹果的 JavaScript Engine —— JavaScriptCore(JSCore/JSC)

下面来说说 JavaScript Engine 有哪些?

/img/in-post/js_engine.jpg

没错,有这么多!当然还有 JavaScriptCore(图里没有)。

其中最后一行是跑分,越多越好。带 JIT 的 V8 以 3w+ 吊打了一切。其中 QuikJS 特别小而且分数也很高,估计也有很多人会用 QuikJS 作为跨端的 JavaScript Engine 吧?Hermes 由 Facebook 搞出来的,似乎是 RN 目前 Android 使用的 JavaScript Engine 代替了之前用的 JavaScriptCore。为什么 RN 一直没有使用 V8?这个我也不清楚···

不过有很多人在搞 Android 的 V8 工程,Github 上也有一些开源项目。其次就是 iOS 因为不支持 JIT,而且有自己的 JavaScriptCore,所以换不换没有 JIT 的 V8 似乎意义不大。

Weex/RN 跨端原理

一个正常的跨端框架 最简单 的情况是这样的(后面会说这其中的问题,并逐步丰富):

/img/in-post/multi_platform_base.jpg

  1. 丢给端上一个 js 文件,端上启 JavaScript Runtime,将 js 文本传给它
  2. JavaScript Runtime 做好各种解析,在通过既定的消息机制发一个消息给客户端
  3. 客户端拿到消息根据既定的格式解析创建平台的视图(这其中有很多问题和细节,后面说)。

用一个简单的例子看

假设我的 js 文件中就是要 展示一个红色的 div 方块。那么首先,端会把这个文本传给 JavaScript Runtime,它解析完后形成一个约定的格式,比如如下的 JSON 格式(里面的值用来描述是一个100*100红色方块,我随便定义的)

{
    "name":"div",
    "width":"100",
    "height":"100",
    "background":"red"
}

通过 JavaScript Runtime 和 端(Android/iOS) 通信,把这个消息传回去。

端拿到了消息,发现要创建一个 100*100 的叫做 div 的东西,没有 div 啊!这就需要端上提前埋好代码,比如 Android 里有 FrameLayout,那么就有类似的注册代码

// 伪代码
register("div", FrameLayout.class);

然后端就知道了,oh!我需要创建一个长100宽100的正方形。

那如果没有这个 div 怎么办?

首先,这是框架设计提前思考好的,究竟要支持哪些基础组件,比如 image 、text 等等。而且一般这里都会开个口子,让开发者可以自己扩展组件,比如你需要一个横滑列表,没提供怎么办?看看 div 怎么注册的,按照它的过程注册一个列表就好了。这也可以 PPT 吹成:扩展跨端框架,其实 门槛比自定义 View 还要低

JavaScript Runtime 是什么?

前面说了 JavaScript Engine,这里咋又来了个 Runtime?

JavaScript Engine 能做什么?
什么都做不了,只能解析执行 js 代码

那么问题来了,我怎么去 描述 我写的 js 代码代表的 视图 呢?其实不用描述,js 代码只要在 内存中 维护好一个树形结构就好了,就是一个 Object,因为实体在具体的端上,怎么理解呢?

/img/in-post/multi_platform_base_2.jpg

左边只要在内存中维护好这样一个树形结构就好了,传递给客户端时,转为

{
    "name":"div",
    "children":[
        {
            "name":"image"
        },
        {
            "name":"div",
            "children":[]
        }
        // 等等
    ]
}

端上拿到消息,创建视图为右图中的结构即可。

如何维护好这个模型呢?调用什么 js 的方法发送消息呢?怎么给这些个 div 加上 css 来描述它的大小形状呢?等等更复杂的一系列的前端问题,都需要 写代码 来实现。

所以一般都会有个 core.js 或者 framework.js 类似的一堆 js 代码,就是用来处理这些事情,而这些代码同样依靠 JavaScript Engine 来执行。

从而所谓的 JavaScript Runtime,我觉得可以单纯的理解为 JavaScript Engine 自身的代码跑起来后的环境,也可以理解为 core.js 等被跨端框架所需要的、包含了各种逻辑的前端代码被加载运行后的环境。

怎么就能知道每个视图的位置和属性了?

当你用这些跨端框架的时候,你会发现他们只支持 css 子集,而且布局方式基本都是 flexbox(一种布局模型)

那么比如你写了一个横着容纳了三个小方块的大方块,你的前端 css 代码肯定要写成,flex-diretion:row,那么抛给端上的消息可能如下:

{
    "name":"div",
    "attribute":{
        // 使用布局
        "flex-diretion":"row"
    },
    "children":[
        {
            "name":"div"
        },
        {
            "name":"div"
        },
        {
            "name":"div"
        }
    ]
}

端上拿到这个消息,都不知道 flex-direction 是什么。当然,你可以自己写一个解析库来解,但是 Yoga 帮你做了这件事!

所以 RN 使用的是 Yoga 布局引擎(支持 flexbox,也是 Facebook 搞的)。

Weex 似乎一开始是用的 Yoga,后来自己写了一套?

这个地方就出现了一个名词 Layout Engine,它就是帮我们处理各种布局参数的,然后帮我们算好每个视图的坐标,然后端上拿到坐标后设置对应的视图的坐标,一个井井有条的视图便展示了出来。如果你觉得你写的布局解析算法超越了 Yoga 等等,那么你完全可以自己写一套。

其他问题

通过上面的一些阅读大概也有概念了,其实跨端,就是不断的两端进行通讯的过程。

比如从 JavaScript Runtime 处理完各种属性了,要渲染视图了!传了一段 JSON 给端。

端上手指点了一下这个视图,那也要封装成一个消息传递给 JavaScript Runtime,然后触发你之前写的 js 的监听代码,比如点击后弹一个弹窗,那就又要封装一个调用弹窗方法的消息给端。

就是这样来来回回。

所以两边都有自己的消息队列。

而且当你做动画还想监听动画过程的时候,肯定在短时间内发送了大量消息,这些过程肯定是 需要优化 的。

并且!据我个人用 Weex 的经验,有的 flexbox 属性两端都不统一(可能是 Weex 的 Bug,毕竟 KPI 项目,都不维护了)

我记得当时还开玩笑说,用了 Weex 终于领悟了跨端的真谛:

if(platform === 'Andoird') {
    // 差异化逻辑
} else if(platform === 'iOS') {
    // 差异化逻辑
}

跨端的代价就是,你 本以为 真的可以一套代码两端跑,后来发现真的有点做梦了(连 H5 有时候 Andoird/iOS 都不一致,因为用的内核都不是一个),代码里有不少的 if-else。

总结

所以经过上面的一系列科普,一个跨端框架成了这样:

/img/in-post/multi_platform_base_3.jpg

这其中一般是需要一个客户端、一个前端、一个懂 JavaScript Engine 会 C/C++ 的来分别开发。

我虽然没开发过,但是感觉会有很多问题。

比如 JavaScript Runtime 在另一个进程的话,跨进程通信?

比如消息通信过于频繁是不是就会有各种连锁反应,掉帧啊、事件响应不及时、动画不流畅啊,怎么优化?

比如 Hippy JS Binding 的细节?

结语

其实我本身一直自诩喜欢研究原理,但是直至今日我也没真的一行行看过跨端框架的源码,我知道的这些也未必是对的,只是之前做过 Weex 的一些工作稍微研究了一下,还是挺惭愧的。

既然你自称喜欢研究原理,为什么不看呢?

所以,我以后有时间会一点点看源码,如有错误,随时更新。