React Native 代码阅读(十四):UIManagerModule 与 ReactNativeRenderer

2019-04-09

前言

UIManagerModule 是 React Native 中视图展示的核心模块,通过挖掘 UIManagerModule,又发现了 JS 侧的 ReactNativeRenderer,最终将整个渲染流程串联起来。

创建

UIManagerModule 是如何创建的呢?为了回答这个问题,有许多相关的概念需要介绍。

我们在写 Native Modules 是都需要创建一个 Package。React Native 内部也是使用 Package 机制来构成的。在 Package 内部包含有 Native Modules 和 ViewManagers。

在这些内置 Package 中,最重要的一个是 CoreModulesPackage。它为 React Native 提供了核心支撑能力。

CoreModulesPackage 中一个非常重要的模块就是 UIManagerModule,

UIManagerModule 的创建就是在 CoreModulesPackage 的 createUIManager 方法中。

谁会获取 UIManagerModule

在上一节中我们知道了 UIManagerModule 是 CoreModulesPackage 中的一个 Module。

因此接下来的问题是,在 React Native 中,谁会获取 UIManagerModule 呢?

首先 UIManagerModule 中一个常量 NAME,作为这个模块的标识:

public static final String NAME = "UIManager";

我们可以通过这个线索入手。找到了这么几个地方:

  • Libraries/Renderer/oss/ReactFabric-dev.js
  • Libraries/Renderer/oss/ReactFabric-prod.js
  • Libraries/Renderer/oss/ReactNativeRenderer-dev.js
  • Libraries/Renderer/oss/ReactNativeRenderer-prod.js
  • com/facebook/react/bridge/NativeModuleRegistry.java

其中:

  • 大多都是 JS 侧的引用,只有一个是 Java 侧的引用
  • 同时可以看出,这个部分可能与 Fabric 重构有关,这引起了我的兴趣

ReactFabric

在上面的调用放中,最引起我注意的是 ReactFabric。

在本节中我们来看看它,首先它不是最原始的代码,而是经过了一次 build,这个文件总共接近 2w 行。

我们直接看最底部:

var fabric = ReactFabric$3.default || ReactFabric$3;

module.exports = fabric;

  })();
}

它对外导出了一个 fabric 对象,看起来跟 Fabric 重构有很大的关系。

第一个需要确认的是:ReactFabric 到底有没有执行呢?

最简单的方法是给它改改,我将上面代码加了一行 log:

var fabric = ReactFabric$3.default || ReactFabric$3;

console.log("maxiee -> run here!")

module.exports = fabric;

  })();
}

ReactNativeRenderer-dev.js

经过一番疯狂打 log 试探,我发现 ReactFabric 并没有走到,而是走到了 ReactNativeRenderer-dev.js。

我猜测 ReactNativeRenderer-dev.js 这个还是 Fabric 重构之前的,ReactFabric 还没有生效。

ReactFabric 我们就先不管它了,来看 ReactNativeRenderer。

UIManager 的 JS 映射

UIManager 既然是一个 Native Module,他应该有一个在 JS 侧的映射类。

在 ReactNativeRenderer 中导入 UIManager 通过下面方法:

var UIManager = require("UIManager");

这个映射类在哪里呢?它位于 Libraries/ReactNative/UIManager.js。

我们先不细看它内部的实现,只知道它在 JS 侧提供了 UIManager 的映射即可。

让我们回到 ReactNativeRenderer。

ReactNativeRenderer 跟 React 的关系

在这个文件的提交记录中,我发现大多都是:

React sync for revisions ...

莫非这个文件里包含了 React?看到其中一行就真相大白了:

var React = require("react");

ReactNativeRenderer 引用了 React,估计是在底层进行了桥接功能。

去网上搜索了一下,找到一篇非常好的文章:

粗略地阅读了一下,可以知道:

  • 这个文件与 React 有莫大的关系
  • React Native 中 Component 的 render 方法都是通过这个文件中的方法来创建的
  • ReactNativeRenderer 中的方法会进一步调用 UIManager 来进行 Native View 的创建
  • 我的前一篇文章覆盖了《「ReactNative」View创建过程浅析》后面的 Native 部分,本文相当于它的中间一小部分
  • 我会在后面的文章中逐渐吃透《「ReactNative」View创建过程浅析》,梳理出整个全流程

ReactNativeRenderer.createInstance

在这里我们先从简单的来,ReactNativeRenderer 引用了 UIManager,它都是怎么用的呢?

在 ReactNativeRenderer 里找了一圈,我找到一个吸引我的方法 —— createInstance。

它的方法签名如下:

function createInstance(
  type,
  props,
  rootContainerInstance,
  hostContext,
  internalInstanceHandle
) {

省略一些中间代码,我们直取核心。调用 UIManager.createView 创建视图:

UIManager.createView(
  tag, // reactTag
  viewConfig.uiViewClassName, // viewName
  rootContainerInstance, // rootTag
  updatePayload // props
);

这一步会最终调用到 Java 层 com.facebook.react.uimanager.UIManagerModule#createView。

在这里跟我们上一篇《React Native 代码阅读(十三):View 是如何创建的》 完美地串起来了。

在上一篇中,我们是通过 spy bridge 发现调用 com.facebook.react.uimanager.UIManagerModule#createView,而在这里发现是引用 Native Module 发现这个调用的,这说明什么呢?

这说明 Native Module 内部的机制是通过发送 bridge message 来告知 Java 层进行方法调用的。

这又能与更早的系列文章串起来,由点及线,说明我们要开始掌握了。

之后创建了一个 component:

var component = new ReactNativeFiberHostComponent(tag, viewConfig);

最终返回组件:

return component;

哪里调用了 createInstance?

让我们再进一步来看,哪里调用了 createInstance?

在 completeWork 方法中调用了它。

谁调用了 completeWork 呢? completeUnitOfWork

谁调用了 completeUnitOfWork 呢?renderRoot 和 performUnitOfWork

谁调用了 performUnitOfWork 呢?workLoop

谁调用了 workLoop 呢?renderRoot 和 replayUnitOfWork

结合《「ReactNative」View创建过程浅析》我们知道 Component 的 render 方法最终会调到 renderRoot

因此通过这番跟踪,摸清了 ReactNativeRenderer 的一条主线

ReactNativeRenderer 如何使用 React?

在上文中我们看到了 ReactNativeRenderer 引用了 React,那它是如何使用它的呢?

ReactNativeRenderer 虽然有两万行之多,但是实际上没几处直接使用 React 的地方,但是这一行吸引了我的注意:

var ReactSharedInternals =
  React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

其中:

  • 这个命名方式笑死我了
  • 看来是 React 开了一个口子啊
  • 我猜 React Native 就是通过它接管了 DOM 节点的渲染能力

__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 这个变量很重要,通过它作为关键字能够搜到很多关于 React 代码阅读的资料。

UIManagerModule 中的方法

有了前面这些 JS 侧的积累,下面我们再回到 Java 侧的 UIManagerModule 中,看看都提供了哪些 React Method:

  • getConstantsForViewManager
  • getDefaultEventTypes
  • removeRootView
  • createView
  • updateView
  • manageChildren
  • setChildren
  • replaceExistingNonRootView
  • removeSubviewsFromContainerWithID
  • measure
  • measureInWindow
  • measureLayout
  • measureLayoutRelativeToParent
  • findSubviewIn
  • viewIsDescendantOf
  • setJSResponder
  • clearJSResponder
  • dispatchViewManagerCommand
  • playTouchSound
  • showPopupMenu
  • dismissPopupMenu
  • configureNextLayoutAnimation
  • sendAccessibilityEvent

UIManagerModule 向 JS 侧提供了这些方法,供 React Native Renderer 调用。

通过这里,我也明白了为啥 React 要拆包拆出来 React 和 React DOM 两个包。

总结

在本文中,通过学习 ReactNativeRenderer 这个文件,终于将 React Native 的渲染流程给完整地串起来了。

经过这十四篇文章,我对 React Native 的整体框架由模糊变清晰了一些。在后续的文章中,我会让这个框架变得更加显化。

附录

以下是我在学习代码过程中总结,可能与主题相关不大,但是也有价值。

舍不得删,列举在下方:

ReactPackage

ReactPackage 是一个接口。它有详细的注释:

它是向 catalyst 框架提供额外功能的主要接口,通过以下几种方式:

  1. 注册新的 Native 模块
  2. 注册新的 JS 模块,可以在 Native 模块或者其他代码中访问
  • 依赖 JS 模块不会自动将其打包进 JS Bundle,因此还需要在 JS 侧有对应的代码来导入 JS 模块的实现到 Bundle 中
  1. 注册自定义 Native View(View Managers)和自定义事件类型
  2. 注册原生资源(比如图片)暴露给 JS

它包含两个接口方法:

  • createNativeModules:创建 Native Modules
  • createViewManagers:创建 ViewManagers

这个跟我们平时创建 NativeModule 的时候是一样的。

LazyReactPackage 与 TurboReactPackage

在学习代码的时候,我看到了两个类:LazyReactPackage 与 TurboReactPackage,他俩什么关系呢?

  • com.facebook.react.LazyReactPackage
  • com.facebook.react.TurboReactPackage

TurboReactPackage 是在 Fabric 重构中,基于 TurboModules 的新 ReactPackage,用于替换 LazyReactPackage。这个替换在最新的 React Native 代码中已经完成了,在实际运行时走的是 TurboReactPackage。

需要注意的是,他俩都实现了 ReactPackage。