探究 react-native-reanimated 的双线程机制

2025-03-01

从 react-native-reanimated、微信小程序的 skyline, 到今年字节跳动开源的 Lnyx,实实在在地说明了双线程能优化 JS 开发和原生交互的体验。那么,为了探究如何实现这种双线程的效果,我选择通过 react-native-reanimated 来了解。

以下全部分析均来自于 3.5.4 版本的 react-native-reanimated。

先来一段代码,来看看 runOnUI 和 runOnJS 有什么区别。

typescript

运行后的结果,可以看到他们打印出来的_WORKLET不一样。

babel 插件识别 UI 线程

通过文档,我们可以知道 RNA 通过 babel 查找到 UI 线程的函数,并在其中插入一些变量,RNA把这些函数称作 worklet。所谓 worklet ,在计算机图形学里面表示一段运行在渲染管线里面的函数。而在这里主要指的是在 JS 中定义,然后运行在 UI 线程的函数。

下面分析插件的工作机制。

插件入口

入口主要做了这么两件事:

  1. 添加全局量定义,让这些全局量能够在 UI 线程中进行调用。
  2. 处理 worklet 调用表达式。
typescript

Worklet 识别机制

Babel 插件通过以下两种方式识别 worklet。

第一,直接声明:

typescript

第二,自动识别:

typescript

babel 处理流程

processForCalleesWorklets 中,会对代码中的符合条件的函数进行捕捉并转换。

typescript

例如这样,转换前的代码:

javascript

转换后:

javascript

如何实现双线程

从上面的 babel 分析我们知道了在JS端那些跑在 UI 线程的函数的一些实现逻辑。为了窥探双线程机制,通过runOnUI就能很好的看到具体的实现流程和作者的思路。接下来,我们来看看 runOnUI 是如何实现的。

JS侧具体实现

先来看看 JS 侧的实现。runOnUI 函数返回一个新的函数,这个函数会将 worklet 和参数添加到队列中。

  1. 使用 queueMicrotask 调度,确保不会阻塞优先级高的操作。
  2. 使用 NativeReanimatedModule.scheduleOnUI 将队列中的所有 worklet 执行。
  3. makeShareableCloneRecursive 函数用于把数据放入共享内存里,这样在 UI 线程就能读取到从 JS 线程发送过来的。

看看具体源码:

typescript

调度器

上面的调度函数NativeReanimatedModule.scheduleOnUI实际上对应的是c++代码中的NativeReanimatedModule.scheduleOnUI

cpp

这里,uiScheduler_->scheduleOnUI需要根据不同平台去执行相应的UI线程调度工作, ios 对应REAIOSUIScheduler.scheduleOnUI,而 Android 则AndroidUIScheduler.scheduleOnUI(由于我个人对Android不太熟悉,所以下面关于Native部分的代码,仅分析 iOS 的部分)。 REAIOSUIScheduler.scheduleOnUI的主要功能内容是,判断当前线程,如果是主线程,那就直接执行,如果不是,那么就放入主线程调用。

cpp

uiWorkletRuntime_.runGuarded 则是通过 UI Runtime 直接执行对应的JS函数。

cpp

下面将详细说明,UI Runtime 和 RN Runtime 都分别做了什么。

UI Runtime 和 RN Runtime

UI Runtime 在 RNA 中的具体类是 WorkletRuntime,他的实现主要做了这么些事情。

  1. 通过 ReanimatedRuntime::make, 获取UI线程下的运行时。
cpp
  1. 调用 WorkletRuntimeDecorator::decorate 给对应的当前的 JS Runtime 增加全局对象,实际上就是标记其为 UI Runtime。
  • global._WORKLET = true
  • global._LABEL = "Reanimated UI runtime"
  • global._toString 函数
  • global._log 函数
  • global._makeShareableClone 函数
  • global._scheduleOnJS 函数

WorkletRuntime 具体做的事情已经结束。

对应的源码:

cpp

接下来,为了对已有的 RN Runtime 做出区分,调用 RNRuntimeDecorator::decorate,给 RN Runtime 增加全局对象。

  • global._WORKLET = false
  • global._WORKLET_RUNTIME = UI Runtime 的具体指针
  • global.__reanimatedModuleProxy = Native侧的 NativeReanimatedModule 实例

所以,这里就解释了,为什么开头的代码global._WORKLET在不同的函数里面会不一样。

runOnJS 实现

最后,runOnJS的实现也大概能猜到了,就是调用 UI Runtime 提供的 global._scheduleOnJS

typescript

总结

好了到这里就要结束了,本文主要介绍了 React Native Reanimated 如何实现其双线程架构。

  1. 自动识别机制:使用 Babel 插件自动识别和转换运行在UI线程的函数。
  2. 线程通信:通过 runOnUI,runOnJS,以及共享内存机制。
  3. 运行时隔离:维护独立的 UI Runtime 和 RN Runtime,通过 _WORKLET 标识区分执行环境。

最后,喜欢本文可以评论,点赞,收藏。