爱奇艺RND框架技术探索——架构与实现

2019 年 9 月 01 日

爱奇艺RND框架技术探索——架构与实现

前言


RND,全称 React Node Desktop,起源于 RN 在爱奇艺 PC 端的实现,采用 React JS framework +Node.js runtime + native UI engine 架构,目标是成为最轻量的 JS 开发桌面应用的跨平台方案。之前我们还分享了一篇关于 RND 的 Native API 注入技术的文章《搭建连接JS与C++的桥梁 - 爱奇艺RND框架之JS自动绑定》,大家可以参考。


目前 RND 已经在爱奇艺 PC 客户端大量应用,在最新的爱奇艺客户端中,除了播放视窗和窗口边框等部分采用 C++开发外,其余频道页均是基于 RND 框架采用 Java Script 开发的,RND 无缝地将 Native 部分与 Java Script 开发的 UI 模块融合到一起,用户从体验上无法感知哪部分是 C++开发,哪部分是 Java Script 开发的。RND 的应用有效降低了开发难度和成本,提高了开发效率,同时也为产品迭代提供了更加多样化的解决方案。


下面是 RND 在爱奇艺客户端上应用的几个场景:


  • 基于RND的频道页:



  • 融合Native播放器的RND页面:



  • 播放器侧边栏信息区:



本文接下来将从技术选型线程模型JS 运行时Bridge资源管理调试支持等几个方面对 RND 的实现作一个简单的梳理,以期能够让大家对 RND 在 Windows 上的实现方案有一个较全面的了解。


技术选型与架构设计


RN 最核心的部件当然是 JavaScript 引擎,毋庸置疑 V8 是 RND 的首选。对于 UI 部分的实现,在移动端,无论是安卓还是 iOS,RN 对接的都是原生的 UI 组件,熟悉 Windows 客户端的开发者都知道,由于一些众所周知的原因,现在基于 Windows 的互联网产品的 UI 几乎不会选择原生的 UI 控件,很多大厂都开发了自己的 UI 库。爱奇艺也有自己的 UI 渲染引擎——Lyra,Lyra 是一套十分优秀的异步渲染引擎,目前支持 Windows 和 MAC OS 两个平台,RND 所有的 UI 渲染都是以 Lyra 作为基础,并且 RND 还整合了 Yoga 布局系统来实现 UI Component 的 flex 布局计算。


在 Native 能力方面,因 RND 集成了 Node 运行时,这使得基于 RND 的 JS 开发者拥有了 Node 强大的 Native 基础能力,开发者可以按需引入 Node 模块来扩展应用的 Native 能力;本地缓存方面,RND 采用了高性能的本地存储系统 LevelDB,LevelDB 是一款性能十分优秀的 Nosql 数据库,支持 key/value 形式的数据存储;在调试方面,RND 除了支持 Chrome 外,还支持 VSCode、Electron 调试,方便开发者按自己的需求进行调试。



RND 架构图


RND 的线程模型


RND 采用了 UI 线程和 JS 线程的双线程模式,其中渲染命令和布局都是在 UI 线程中完成的,RN JS 的代码则是执行在 JS 线程中。RNJS 框架生成渲染命令后,通过注入的交互函数发送到 UI 线程中,UI 线程收到命令进行解析,完成 JS 组件的创建、布局和渲染。


当在 UI 线程中存在复杂任务时,RND 的渲染仍然能保证界面绘制的流畅度,这要归功于 UI 采用的异步渲染引擎——Lyra,Lyra 执行的双线程渲染模式,UI 线程会将 paint 消息发送到独立的 render 线程执行,避免 UI 线程上的耗时计算阻塞界面绘制,因此,RND 有着非常出色的渲染能力。



RND 线程模型


JS 运行时的实现


在 Windows 平台上,RND 采用 V8 作为 JavaScript 引擎。JS 运行时是对 JS 运行环境的抽象描述:隔离了不同平台 JS 引擎的接口差异,为上层提供了统一的访问接口,并向 JS 引擎注入了必要的 Native API 以支持 RN 的运行。同时,JS 运行时还对外暴露了 ReactJS 的事件监听接口和 Native API 注入框架。


封装 V8



RND 对 JS 运行时的抽象和封装


考虑到内存与性能开销,在同一进程中,所有 RootView 实例共享 V8 的同一个 Isolate 对象,而每一个 RootView 独立拥有一个 V8 的 Context,保证其运行环境的隔离。因共享 Isolate 的原因,所有的 RootView 共享同一个 JS 执行线程。共享线程也可以有效降低线程竞争。开发者也可以改变这一策略,根据实际资源使用情况来决定 Isolate 的数量。


Snapshot 助力加速启动过程


V8 还有一个非常有用的特性为 RND 所用——Snapshot 运行模式:Snapshot 是将 V8 运行期某一时刻的运行快照转储到磁盘文件中,当程序再次启动时可以直接从存储好的快照直接恢复到当时的运行状态,这一特性可以有效帮助 RND 提升应用的启动速度。



使用 snapshot 启动速度加快近 300ms


整合 Node 运行时,引入 Node 生态


因为开源项目 Node 同样使用了 V8 引擎,这样我们做很少的工作就可以将 Node 引入到 RND 中了。RN 开发者不但具有了本地文件、网络等基础 Native 访问能力,而且还可以将 Node 的开发包集成到开发环境中来。



RND 同时支持 RN 和 node 的 modules


一个典型的例子,在 RND 中 Web Socket 接口就是由 Node 来支持的。在 Chrome、DevTools 的调试实现中都用到了 Web Socket。


Bridge 实现


Bridge 的实现大概是 RN 原理介绍相关的文章中读者最关心的部分了,与 RN 移动版本的实现不同,因 RND 基于 C 开发,而 V8 亦是以 C 向 Native 提供接口的,因此在实现 Bridge 时,RND 不需要额外处理异构语言交互的问题。


交互实现


单接口、异步交互的模式是 RN 实现 Bridge 的最大特点。RND 对 Bridge 部分作了一些改良和扩充,对于 UI 以外的处理,RND 将其从 messageQueue 中“拆了出来”,提供了直接的 API 来完成,比如 timer、本地缓存访问、网络请求等 Native 模块。这些接口在 Native 端基本都是异步接口,因此不会造成阻塞,但在执行逻辑处立刻调用会加速并发能力,以便定时器更加精确或网络请求更早将请求发送出去。



Bridge 实现


Yoga 布局


Lyra 系统目前并不支持 Flexbox 布局规范,因此 RND 并没有使用 Lyra 的布局系统,而是与 RN 一样采用了 Facebook 的 Yoga 布局系统,Yoga 支持 Flexbox 规范,是一款非常优秀的跨平台布局系统。相较于之前的版本,Yoga 优化了 Dirty 算法,只对发生位置或大小变化的相关控件进行 Dirty,大大提高了绘制效率。



RND 应用界面布局


RootView 内部的控件接受 Yoga 布局,其自身则与其他 Lyraui 控件接受 Lyra 的布局。


RND 的资源管理与热更新


模块化资源管理



RND 资源管理


每一个 Module 对应了一个资源配置(ResourceConfig),这个资源配置映射了一个资源配置管理对象,通过这个资源配置管理对象便可获取到当前要使用的资源类型和资源内容。


进行资源模块化,其实是 Lyra ui 本身的使用要求,因此,RND 的资源模块化实际上是使用了 Lyra ui 的资源管理策略。


RND 中,JS 业务代码,图片文件都被视为资源,在发布版本中,都被打包到 zip 中,在开发模式下,是以文件形式存放在代码目录中的,为了统一组织不同模式下的资源加载,RND 抽象了 ResLoader 对象,负责加载不同类型的资源,同时,ResLoader 还会调用热更新模块进行资源 zip 包的升级。


图片资源自动加载机制



图片加载与显示


RND UI 组件的图片资源有 3 种来源:网络、本地缓存和 zip 资源包,存放在 zip 资源包的图片资源一般都是较少更新的、较固定的图片资源。大部分图片是动态更新的,JS 通过 URL 的形式来设置图片资源属性,RND 根据 URL 的格式来区别图片来自网络还是 zip 包,来自网络的图片 URL 是一个 http 的 URL,而 zip 包中的图片则是一个文件名的形式。因此 RND 很容易区分这两种图片来源,当判断是来自网络时,RND 会首先检查是否存在缓存文件,如果存在则直接读取缓存图片。


可灵活配置的热更新


RootView 在初始化时 ResLoader 会通过 ConfigManager 获取资源更新对象,调用 UpdateResource 函数进行资源更新,当资源有更新时便会直接加载新的资源包执行,如果不存在更新则加载默认的资源包。



RootView 资源热更新


RND 支持两种资源更新策略,一种直接通过 URL 携带请求参数的方式请求升级资源包,另一种则是先拉取一个升级策略文件,再通过读取升级策略进行升级,可以通过 ConfigManager 由宿主程序指定。


用户也可以实现自己的资源更新策略,在配置对象中指定,RND 会优先使用自定义更新策略来更新资源。


调试支持


工欲善其事必先利其器,在调试方面,我们投入了大量的精力来完善增强 RND 的工具链,一方面我们比 RN 有更丰富的调试工具;另一方面,我们创新性地整合了调试与目标应用的 JS 运行时,调试环境就是运行环境,所有与 Native 的调用都是真实调用,而不是委托给 Web Socket 实现的“伪调用”,这完美解决了在调试模式下 Native 接口同步调用的问题,也提高了调试的便利性,开发者可以在 JS 代码中同步跟踪 Native 接口调用。


多样化调试支持


除了 Chrome 外,RND 还整合了 Electron、VSCode 等调试工具,开发者可以按照不同的需求或喜好可以选择不同的调试工具,RND 推荐使用 Electron 或 VSCode 来调试,Electron 完全支持 Chrome 所有的调试和性能分析功能,而 VSCode 则是 JS 开发者首选的 IDE,在开发中进行调试、热更新都非常的方便。用户可以通过调试配置文件选择使用哪一种调试工具作为默认调试器。



RND 调试工具链


Native 接口同步调试支持


在 Chrome 调试模式下,RN JS 并不是运行在自身的 JS 引擎中,而是被运行在 Chrome 的 JS 引擎中,对 Native Api 的调用也是通过 Web Socket 来代理实现的,因此,在这种场景下,如果想要执行同步 Native Api 的调用是无法实现的。在 RND 中,因 RN JS 需要同步获取宿主窗口位置大小等信息,Native 提供了对应的同步接口,在调试模式下对这类同步接口的调试就无法实现了。


RND 在 Electron 和 VSCode 调试模式中将 JS 运行时整合到了一起,让 RN JS 与宿主应用的 Native 部分运行在同一个进程中,这样就实现了 Native API 的同步调用,调试模式和运行模式的交互方式保持了一致。


调试效果图样


  • 通过DevTools查看页面布局:



  • 通过VSCode进行调试:



  • 通过Chrome进行调试:



小结


本文着重从 Native 框架的角度介绍了 RND 的技术选型和关键模块的实现原理,关于 RN JS 框架部分本文并未提及,后续会有专门的一篇文章《爱奇艺 RND 框架之 JS Framework 解析》来介绍,敬请关注。由于篇幅关系,RND 的动画系统、性能分析系统等很多技术文中并未涉及,希望后续有时间能与大家一起分享。


RND 在爱奇艺客户端的成功实践表明,RN 同样适用于以运营内容为主的、迭代周期密集的互联网桌面应用,JS 非常适合 UI 和业务逻辑的快速开发。随着各大 JS 引擎性能不断的优化,很多大厂都推出了基于 JS 语言的轻量级高性能 App 应用框架,可以预测在不久的将来,以内容运营为主的桌面产品上,JS 很快会成为最受开发者欢迎的语言之一


本文转载自公众号爱奇艺技术产品团队(ID:iQIYI-TP)


原文链接


https://mp.weixin.qq.com/s?__biz=MzI0MjczMjM2NA==&mid=2247485367&idx=1&sn=2676351993f652a8d269cb3d71f9cfd5&chksm=e9769b94de0112826412e8847a359199a6776a0d9a0f94e2487e8c96d8e0f1b0649c0813747f&scene=27#wechat_redirect


2019 年 9 月 01 日 08:005016

评论 1 条评论

发布
用户头像
where is code
2019 年 09 月 02 日 09:45
回复
没有更多评论了
发现更多内容
爱奇艺RND框架技术探索——架构与实现-InfoQ