从 ReactRootView 角度理解React-Native源码

众所周知,React-Native 是一款优秀的 JavaScript 框架,它将 React 和原生相结合,将 React 组件渲染为原生平台的组件,这意味着性能和原生控件几无二别。它可以用JSX,混编js、css、html,这大大提高了开发的效率,开发者只关心如何用 JavaScript 构造页面。而它的高效之处在于独创的 Virtual DOM,Virtual DOM是存在于内存中的 JavaScript 对象,它和 DOM 是一一对应的关系,得益于高效的 DOM-diff 算法,当界面发生变化的时候,Virtual DOM 会高效的更改 DOM 从而避免了频繁的绘制,提高了性能。

但是,在现实的使用场景之中,我们常常混合开发,而不会纯粹的使用 React-Native,这是由于,虽然 FaceBook 官方提供了大量的组件,但是毕竟还是有很多业务上的或者是代码逻辑上的需求是无法满足的,这就需要两端另外去开发对应的组件,反而增大了开发成本。除此之外,作为至关重要的列表,FlatList 的表现差强人意,只能够展现相对简单的列表,而图片过多,或者内容相对复杂,就会导致明显的掉帧和卡顿。

下面将从 ReactRootView 的角度来理解 React-Native 的加载过程。
我们在使用一个 React-Native 页面的时候,有两种实现方式,一种是继承官方的 ReactActivity ,另一种是把 ReactRootView 添加进布局。
第一种方式相对简单。这两种发方法大同小异,只不过第二种方法相对灵活,但是需要自己去挂载和卸载组件。当我们跟踪 ReactActivityDelegate 的代码,会发现实现挂载的调用最终走向了这个方法:

public void loadApp(String appKey) {
if (mReactRootView != null) {
  throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
    getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);}    

mReactRootView.startReactApplication 方法即是用来启动JS应用,并且渲染由js组件。继续往下看:

public void startReactApplication(
  ReactInstanceManager reactInstanceManager,
  String moduleName,
  @Nullable Bundle initialProperties,
  @Nullable String initialUITemplate) {
try {
  UiThreadUtil.assertOnUiThread();
  mReactInstanceManager = reactInstanceManager;
  mJSModuleName = moduleName;
  mAppProperties = initialProperties;
  mInitialUITemplate = initialUITemplate;
  mReactInstanceManager.createReactContextInBackground();
  attachToReactInstanceManager();

} finally {
  Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

这里去掉了冗余的代码,只留下关键性代码,createReactContextInBackground() 方法是在子线程中创建 ReactContext ,之后会调用 recreateReactContextInBackgroundInner() 方法,这里需要用到强大的 ReactInstanceManager 类,ReactInstanceManager 是一个比较核心的类,它管理着 ReactRootView 的生命周期,管理着 js 的加载,管理着Native和js 的交互,管理着初始化的参数等等。

这里我们只关心 js 的加载和View 的挂载。一路追踪下去,执行的是
recreateReactContextInBackgroundFromBundleLoader()方法,这段关键代码是: recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
而 mJavaScriptExecutorFactory 和 mBundleLoader 是在 类,ReactInstanceManager 初始化的时候已经准备就绪了,这两个类就是用来执行js文件的,JavaScriptExecutor 的作用是调用Native方法initHybrid()初始化C++层RN与JSC通信的框架,JSBundleLoader 是通过不同的场景去创建不同的加载器,用以加载js文件,这两个类会封装为 ReactContextInitParams 传递给核心的方法: runCreateReactContextOnNewThread(),在这这个线程中有两个关键的部分,一个是创建 ReactContext, 一个是 setupReactContext。
首先看下createReactContext():

 private ReactApplicationContext createReactContext(
  JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
FLog.d(ReactConstants.TAG, "ReactInstanceManager.createReactContext()");
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);

NativeModuleCallExceptionHandler exceptionHandler =
    mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);


CatalystInstanceImpl.Builder catalystInstanceBuilder =
    new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
final CatalystInstance catalystInstance;
try {
  catalystInstance = catalystInstanceBuilder.build();
} finally {
  Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
}

reactContext.initializeWithInstance(catalystInstance);

// TODO(T46487253): Remove after task is closed
FLog.e(
    ReactConstants.TAG,
    "ReactInstanceManager.createReactContext: mJSIModulePackage "
        + (mJSIModulePackage != null ? "not null" : "null"));

if (mJSIModulePackage != null) {
  catalystInstance.addJSIModules(
      mJSIModulePackage.getJSIModules(
          reactContext, catalystInstance.getJavaScriptContextHolder()));

  // TODO(T46487253): Remove after task is closed
  FLog.e(
      ReactConstants.TAG,
      "ReactInstanceManager.createReactContext: ReactFeatureFlags.useTurboModules == "
          + (ReactFeatureFlags.useTurboModules == false ? "false" : "true"));

  if (ReactFeatureFlags.useTurboModules) {
    JSIModule turboModuleManager =
        catalystInstance.getJSIModule(JSIModuleType.TurboModuleManager);

    // TODO(T46487253): Remove after task is closed
    FLog.e(
        ReactConstants.TAG,
        "ReactInstanceManager.createReactContext: TurboModuleManager "
            + (turboModuleManager == null ? "not created" : "created"));

    catalystInstance.setTurboModuleManager(turboModuleManager);

    TurboModuleRegistry registry = (TurboModuleRegistry) turboModuleManager;

    // Eagerly initialize TurboModules
    for (String moduleName : registry.getEagerInitModuleNames()) {
      registry.getModule(moduleName);
    }
  }
}
if (mBridgeIdleDebugListener != null) {
  catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
  catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
}
ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START);
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
catalystInstance.runJSBundle();
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);

return reactContext;
}

在这个方法中,主要是 ReactContet 的初始化,而ReactContet 仅仅继承自 ContextWrapper 而已,但是经过官方的扩展,它承担了 JS 和原生沟通的桥梁。
NativeModuleRegistry 通过 processPackages()方法 整合了项目中所有的的封装组件或者是交互类,即 NativeModule , 按照官方的写法,封装成为 ReactPackage, 交由 CatalystInstance 去绑定。

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

而原生和 js 的交互正是由此而来,比如这样的一段代码:

const TextInANest = () => {
    return (
<Text style={styles.baseText}>
  <Text style={styles.titleText} onPress={onPressTitle}>
    {titleText}
    {"\n"}
    {"\n"}
  </Text>
  <Text numberOfLines={5}>{bodyText}</Text>
</Text>
 );
};

Text 组件拥有自己的属性和方法,我们知道,所有的 React 组件最终会映射为原生组件,那么在原生和 js 之间必然维护了一个映射关系,当我们去查看 ReactTextViewManager 和它的父类 ReactTextAnchorViewManager 就会发现几乎所有的属性。首先,React Native将代码由JSX转化为JS组件,启动过程中利用instantiateReactComponent将ReactElement转化为复合组件ReactCompositeComponent与元组件ReactNativeBaseComponent,利用
ReactReconciler对他们进行渲染。UIManager.js利用C++层的Instance.cpp将UI信息传递给UIManagerModule.java,并利用UIManagerModule.java构建UI。UIManagerModule.java接收到UI信息后,将UI的操作封装成对应的Action,放在队列中等待执行。各种UI的操作,例如创建、销毁、更新等便在队列里完成,UI最终得以渲染在屏幕上。而这个 ReactTextViewManager 最终是通过 ReactPackage 传递给NativeModuleRegistry 的作用就是把这些封装的View或者交互的 Moudules 进行注册,销毁,刷新等工作,与之相对应的js管理类是 JavaScriptModuleRegistry,这样一来,就为下一步二者之间的交互搭建好基础。

CatalystInstanceImpl 类在初始化的过程中,通过 ReactInstanceManager 把相应的参数带了进来,并初始化出三个 MessageQueueThread ,MessageQueueThread 只是一个接口,方便接受 Runable 进行调用,之后通过 CatalystInstance 的 runJSBundle() 方法加载 js 文件,在 CatalystInstanceImpl 实现类中调用 mJSBundleLoader.loadScript(CatalystInstanceImpl.this) 方法,

public void initializeMessageQueueThreads(ReactQueueConfiguration queueConfig) {
if (mUiMessageQueueThread != null
    || mNativeModulesMessageQueueThread != null
    || mJSMessageQueueThread != null) {
  throw new IllegalStateException("Message queue threads already initialized");
}
mUiMessageQueueThread = queueConfig.getUIQueueThread();
mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
mJSMessageQueueThread = queueConfig.getJSQueueThread(); }     

这里初始化三个调度Runable 的类,分别是本地线程,js线程,ui线程,以方便交互。 至此,js已经加载,而且Native 和js 的桥梁也搭建完成。
回头看 setupReactContext 方法,它的主要作用是管理ReactRootView 的生命周期,在一个项目中,可能存在多个 ReactRootView,这里通过遍历的方式,执行 attachRootViewToInstance(reactRoot)方法,同时在ReactContext 创建完成的回调中,初始化一些插件,主要内容在 ReactNativeFlipper 当中,暂不赘述,关键的代码在 attachRootViewToInstance 当中:

private void attachRootViewToInstance(final ReactRoot reactRoot) {
// TODO: downgrade back to FLog.d once T62192299 is resolved.
FLog.e(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance");

@Nullable
UIManager uiManager =
    UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType());

// If we can't get a UIManager something has probably gone horribly wrong
if (uiManager == null) {
  throw new IllegalStateException(
      "Unable to attach a rootView to ReactInstance when UIManager is not properly initialized.");
}

@Nullable Bundle initialProperties = reactRoot.getAppProperties();

final int rootTag =
    uiManager.addRootView(
        reactRoot.getRootViewGroup(),
        initialProperties == null
            ? new WritableNativeMap()
            : Arguments.fromBundle(initialProperties),
        reactRoot.getInitialUITemplate());
reactRoot.setRootViewTag(rootTag);
if (reactRoot.getUIManagerType() == FABRIC) {
  // Fabric requires to call updateRootLayoutSpecs before starting JS Application,
  // this ensures the root will hace the correct pointScaleFactor.
  uiManager.updateRootLayoutSpecs(
      rootTag, reactRoot.getWidthMeasureSpec(), reactRoot.getHeightMeasureSpec());
  reactRoot.setShouldLogContentAppeared(true);
} else {
  reactRoot.runApplication();
}
Systrace.beginAsyncSection(
    TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
UiThreadUtil.runOnUiThread(
    new Runnable() {
      @Override
      public void run() {
        Systrace.endAsyncSection(
            TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
        reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE);
      }
    });
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}

其主要的作用就是通过 UIManager 去渲染控件,至此完整的启动流程已经结束。

总结:

ReactContext:ReactContext继承于ContextWrapper,是ReactNative应用的上下文,通过getContext()去获得,通过它可以访问ReactNative核心类的实现。

ReactInstanceManager:ReactInstanceManager是ReactNative应用总的管理类,创建ReactContext、CatalystInstance等类,解析ReactPackage生成映射表,并且配合ReactRootView管理View的创建与生命周期等功能。

CatalystInstance:CatalystInstance是ReactNative应用Java层、C++层、JS层通信总管理类,总管Java层、JS层核心Module映射表与回调,三端通信的入口与桥梁。

JavaScriptModule:JavaScriptModule是JS Module,负责JS到Java的映射调用格式声明,由CatalystInstance统一管理。

NativeModule:NativeModule是Java Module,负责Java到Js的映射调用格式声明,由CatalystInstance统一管理。

JavascriptModuleRegistry:JavascriptModuleRegistry是JS Module映射表,NativeModuleRegistry是Java Module映射表。

UIManager:主要处理UI的渲染,JS层通过C++层把创建View的请求发送给Java层的UIManagerModule。UIManagerModule通过UIImplentation对操作请求进行包装。
包装后的操作请求被发送到View处理队列UIViewOperationQueue队列中等待处理。实际处理View时,根据class name查询对应的ViewNManager,然后调用原生View的方法对View进行相应的操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容