【源码解析】React Native组件渲染

一、引言

React Native,简称RN,是FaceBook推出的一款跨平台的代码框架,其主要是为了解决Android和iOS两端代码逻辑不统一的痛点,可实现一套代码逻辑,运行于Android和iOS两端的功能。

由于其底层原理还是依赖于Android和iOS两个原生系统,只是这部分复杂逻辑是FaceBook在React Native框架中帮我们做了而已,使得开发者只需要关注框架提供给应用层的API就可以进行移动开发。

本文从源码的角度分析React Native的渲染过程,深扒一下React Native是怎么一步一步的创建原生View的,让我们开始吧~

二、Android端源码分析 I

在Android端集成过RN的小伙伴应该都知道,其最终的形态就是一个ReactRootView,一般都是使用关键字new出来的。 如果我们将该View集成到Activity,那么该Activity就是一个RN形态的Activity;如果我们将该View集成到Fragment,那么该Fragment就是一个RN形态的Fragment

首先我们看一下ReactRootView的类型。

public class ReactRootView extends SizeMonitoringFrameLayout implements RootView, MeasureSpecProvider {
}

public class SizeMonitoringFrameLayout extends FrameLayout {
}

通过源码可以知道ReactRootView间接继承了FrameLayout,也就是说它是一个容器。所以RN的渲染过程就是将JS中的Component映射到View,并将View添加到ReactRootView的过程。

目前,我们只是拿到了一个空的容器(ReactRootView),接下来还需要将View添加到该容器中。那么RN是如何将JS中的Component映射到View的呢?

我们注意到了ReactRootView有一个方法叫做startReactApplication

public void startReactApplication(
    ReactInstanceManager reactInstanceManager,
    String moduleName,
    @Nullable Bundle initialProperties) {
  try {
    mReactInstanceManager = reactInstanceManager;
    mJSModuleName = moduleName;
    mAppProperties = initialProperties;if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
      mReactInstanceManager.createReactContextInBackground();
    }
    attachToReactInstanceManager();
  } finally {
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }
}

该方法接收三个形式参数:

  • reactInstanceManager:ReactInstanceManager类型,CatalystInstance对象管理器
  • moduleName:String类型,模块名,和JS中通过AppRegistry.registerComponent注册的模块名一一对应。
  • initialProperties:Bundle类型,初始化参数,该参数会传递到JS的Component中,JS端通过构造函数props可以进行获取。

该方法的逻辑比较简单,就是将三个形式参数存储起来后,然后调用了attachToReactInstanceManager方法。

private voidattachToReactInstanceManager() {
  try {
    ...省略
    mIsAttachedToInstance = true;
    Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
    getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
  } finally {
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }
}

判断mReactInstanceManager对象不为空(该对象在startReactApplication方法中已赋值),则调用mReactInstanceManager对象的attachRootView方法。

public void attachRootView(ReactRootView rootView) {
  mAttachedRootViews.add(rootView);

  // Reset view content as it's going to be populated by the application content from JS.
  rootView.removeAllViews();
  rootView.setId(View.NO_ID);

  // If react context is being created in the background, JS application will be started
  // automatically when creation completes, as root view is part of the attached root view list.
  ReactContext currentContext = getCurrentReactContext();
  if (mCreateReactContextThread == null && currentContext != null) {
    attachRootViewToInstance(rootView, currentContext.getCatalystInstance());
  }
}

attachRootView方法清空了ReactRootView中的所有子View,并设置其Id为View.NO_ID,然后调用了attachRootViewToInstance方法。

private void attachRootViewToInstance(
    final ReactRootView rootView,
    CatalystInstance catalystInstance) {
  UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());
  final int rootTag = uiManagerModule.addRootView(rootView);
  rootView.setRootViewTag(rootTag);
  rootView.runApplication();
  UiThreadUtil.runOnUiThread(newRunnable() {
    @Override
    public void run() {
      Systrace.endAsyncSection(
        TRACE_TAG_REACT_JAVA_BRIDGE,
        "pre_rootView.onAttachedToReactInstance",
        rootTag);
      rootView.onAttachedToReactInstance();
    }
  });
}

attachRootViewToInstance方法中给每一个ReactRootView返回了一个整型的rootTag,该rootTag是唯一的。当该ReactRootView被destroy后,也是通过该rootTag找到相应的ReactRootView。最后执行了ReactRootView的runApplication方法。

voidrunApplication() {
    try {
      ...省略
      CatalystInstance catalystInstance = reactContext.getCatalystInstance();

      WritableNativeMap appParams = new WritableNativeMap();
      appParams.putDouble("rootTag", getRootViewTag()); //ReactRootView唯一的rootTag
      @Nullable Bundle appProperties = getAppProperties(); //需要传递给JS Component的属性
      if (appProperties != null) {
        appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
      }
      if (getUIManagerType() == FABRIC) {
        appParams.putBoolean("fabric", true);
      }
      mShouldLogContentAppeared = true;

      String jsAppModuleName = getJSModuleName(); //JS Component的模块名
      //通过动态代理形式执行AppRegistry.runApplication方法,最终映射到RN中的AppRegistry.js的同名方法
      catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
}

runApplication方法构造了一个WritableNativeMap对象(类似于HashMap),并将rootTag、初始化参数写入对象中。 通过catalystInstance.getJSModule(Class clazz)方法可以获取到JS中的同名类对象(底层实现是JNI),因此我们可以认为最后调用的是JS端AppRegistry的runApplication方法

三、JS端源码分析

从上面的Android端源码分析得知,最后调用了JS端的AppRegistry的runApplication方法

runApplication(appKey: string, appParameters: any): void {
  ...省略

  SceneTracker.setActiveScene({name: appKey});
  runnables[appKey].run(appParameters);
}

在runApplication方法中,其调用了一个runnables[appKey]的run方法。而runnables[appKey]返回一个Runnable对象,所以这里调用的是Runnable对象的run方法。

那么这个Runnable对象的run方法究竟执行了什么样的逻辑呢???在分析之前,我们先来看一下runnables[appKey]是怎么被赋值的

我们知道在JS端,需用通过AppRegistry的registerComponent方法注册模块名。

registerComponent(
  appKey: string,
  componentProvider: ComponentProvider,
  section?: boolean,
): string {
  runnables[appKey] = {
    componentProvider,
    run: appParameters =>
      renderApplication(
        componentProviderInstrumentationHook(componentProvider),
        appParameters.initialProps,
        appParameters.rootTag,
        wrapperComponentProvider && wrapperComponentProvider(appParameters),
      ),
  };if (section) {
    sections[appKey] = runnables[appKey];
  }
  return appKey;
}

在registerComponent方法中,直接给runnables[appKey]进行了赋值操作

所以,上面的runApplication方法中的runnables[appKey]方法就是在这里被赋值的!!!所以其中的run方法也是在这里被定义,我们单独拿出这方法块进行分析。

run: appParameters =>
renderApplication(
  componentProviderInstrumentationHook(componentProvider),
  appParameters.initialProps,
  appParameters.rootTag,
  wrapperComponentProvider && wrapperComponentProvider(appParameters),
),

run方法接收一个参数,叫做appParameters。由上面的分析可知,appParameters就是Android端传递进来的WritableNativeMap对象,其代表的是一些初始化参数。run方法中没有做过多的逻辑,而是直接调用了renderApplication.js中的renderApplication方法

function renderApplication<Props: Object>(
  RootComponent: React.ComponentType<Props>,
  initialProps: Props,
  rootTag: any,
  WrapperComponent?: ?React.ComponentType<*>,
) {
  invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);

  ReactNative.render(
    <AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
      <RootComponent {...initialProps} rootTag={rootTag} />
    </AppContainer>,
    rootTag,
  );
}

renderApplication方法没有过多复杂的逻辑,而是直接调用了ReactNative的render方法

也不知道是不是React版本太低还是什么原因,在源码中始终找不到ReactNative的render方法。

百度、Google都试过了就是找不到相关的文章,最后茅塞顿开地想到不是可以利用debug调试看调用栈吗???,最后随便找了个demo,将断点断在render的return语句中,得到如下的调用栈。

从上面的调用栈可以看出,renderApplication.js的renderApplication方法调用了ReactNativeRenderer-dev.js的render方法

render:function(element, containerTag, callback) {
  var root = roots.get(containerTag);

  if (!root) {
    // TODO (bvaughn): If we decide to keep the wrapper component,
    // We could create a wrapper for containerTag as well to reduce special casing.
    root = createContainer(containerTag, false, false);
    roots.set(containerTag, root);
  }
  updateContainer(element, root, null, callback);

  return getPublicRootInstance(root);
}

注意:开发环境下调用的是ReactNativeRenderer-dev.js的render方法,生产环境下调用的是ReactNativeRenderer-prod.js的render方法。由于两者的方法逻辑基本一致,故这里只讨论开发环境的render方法。

render方法中又调用了updateContainer方法,从调用栈来看,中间调用了一些ReactNativeRenderer-dev.js的其他方法,这里就不一一展开进行详细说明了。我们直接跳到performUnitOfWork这个方法

function performUnitOfWork(workInProgress) {
  ...省略

  var next = void 0;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }

    next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    next = beginWork(current?1, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }
  ...省略
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }
  ReactCurrentOwner$2.current = null;
  return next;
}

当next === null时,表示接下来并没有其他子View了,则执行completeUnitOfWork方法,而completeUnitOfWork方法中又执行了completeWork方法

function completeWork(current, workInProgress, renderExpirationTime) {
  var newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    ...省略
    case HostComponent: {
      popHostContext(workInProgress);
      var rootContainerInstance = getRootHostContainer();
      var type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        ...省略
      } else {

        var currentHostContext = getHostContext();
        var wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          if (
            prepareToHydrateHostInstance(
              workInProgress,
              rootContainerInstance,
              currentHostContext
            )
          ) {
            markUpdate(workInProgress);
          }
        } else {
          var instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress
          );

          appendAllChildren(instance, workInProgress, false, false);
          workInProgress.stateNode = instance;
        }

        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          markRef$1(workInProgress);
        }
      }
      break;
    }
    ...省略
  }
  return null;
}

completeWork方法中最重要的是执行了createInstance方法,因为该方法执行了创建View对象的逻辑

function createInstance(
  type,
  props,
  rootContainerInstance,
  hostContext,
  internalInstanceHandle
) {
  ...省略

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

  ...省略
  return component;
}

这里最重要的是调用了UIManager的createView方法。而这里的UIManager对应的是Android端的UIManagerModule,createView方法对应的是UIManagerModule中的createView方法

四、Android端源码分析 II

让我们继续回到Android端,找到UIManagerModule。

protected static final String NAME ="UIManager";

@Override
public String getName() {
    return NAME;
}

可以看到UIManagerModule的getName方法返回的是UIManager,这样验证了上面的JS端中的结论。我们继续看createView方法。

@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
	mUIImplementation.createView(tag, className, rootViewTag, props);
}

createView方法中直接调用了UIImplementation的createView方法

public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    ReactShadowNode cssNode = createShadowNode(className);
    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
    Assertions.assertNotNull(rootNode,"Root node with tag " + rootViewTag + " doesn't exist");
    cssNode.setReactTag(tag);
    cssNode.setViewClassName(className);
    cssNode.setRootTag(rootNode.getReactTag());
    cssNode.setThemedContext(rootNode.getThemedContext());

    mShadowNodeRegistry.addNode(cssNode);

    ReactStylesDiffMap styles = null;
    if (props != null) {
      styles = new ReactStylesDiffMap(props);
      cssNode.updateProperties(styles);
    }

    handleCreateView(cssNode, rootViewTag, styles);
}		

该方法中最后又调用了handleCreateView方法。

protected void handleCreateView(
  ReactShadowNode cssNode,
  int rootViewTag,
  @Nullable ReactStylesDiffMap styles) {if (!cssNode.isVirtual()) {
	  mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
	}
}	

在handleCreateView方法中,又调用了NativeViewHierarchyOptimizer的handleCrateView方法

public void handleCreateView(
      ReactShadowNode node,
      ThemedReactContext themedContext,
      @Nullable ReactStylesDiffMap initialProps) {if (!ENABLED) {
      int tag = node.getReactTag();
      mUIViewOperationQueue.enqueueCreateView(
          themedContext,
          tag,
          node.getViewClass(),
          initialProps);
      return;
    }

    boolean isLayoutOnly = node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME) &&
        isLayoutOnlyAndCollapsable(initialProps);
    node.setIsLayoutOnly(isLayoutOnly);

    if (!isLayoutOnly) {
      mUIViewOperationQueue.enqueueCreateView(
          themedContext,
          node.getReactTag(),
          node.getViewClass(),
          initialProps);
    }
}

而handleCreateView方法又去调用了UIViewOperationQueue的enqueueCreateView方法

public void enqueueCreateView(
      ThemedReactContext themedContext,
      int viewReactTag,
      String viewClassName,
      @Nullable ReactStylesDiffMap initialProps) {
    synchronized (mNonBatchedOperationsLock) {
      mNonBatchedOperations.addLast(
        new CreateViewOperation(
          themedContext,
          viewReactTag,
          viewClassName,
          initialProps));
    }
}

这里的mNonBatchedOperations是一个队列,所以这里的逻辑是new了一个CreateViewOperation对象,并加入到了mNonBatchedOperations这个队列中。而该队列最终会将其中的CreateViewOperation对象取出,并执行其中的execute方法。

public CreateViewOperation(
    ThemedReactContext themedContext,
    int tag,
    String className,
    @Nullable ReactStylesDiffMap initialProps) {
  super(tag);
  mThemedContext = themedContext;
  mClassName = className;
  mInitialProps = initialProps;
  Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW,"createView", mTag);
}

@Override
public void execute() {
  Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
  mNativeViewHierarchyManager.createView(
      mThemedContext,
      mTag,
      mClassName,
      mInitialProps);
}

这里的mClassName是从JS端传过来的,表示的是需要创建的View的名字,这是双端约定好的一个名字。例如,JS中的Text对应着Android端的TextView,它们双端约定的名字就是RCTText(这在ReactTextViewManager中可以查询到)。 在execute方法中,调用了NativeViewHierarchyManager的createView方法。

public synchronized void createView(
      ThemedReactContext themedContext,
      int tag,
      String className,
      @Nullable ReactStylesDiffMap initialProps) {
    try {
      ViewManager viewManager = mViewManagers.get(className);

      View view = viewManager.createView(themedContext, mJSResponderHandler);
      mTagsToViews.put(tag, view);
      mTagsToViewManagers.put(tag, viewManager);

      view.setId(tag);if (initialProps != null) {
        viewManager.updateProperties(view, initialProps);
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
    }
}

mViewManagers.get(className)返回一个ViewManager对象。上面提到,className是JS端和Android端双端约定的一个名字。拿上面的例子来说,**当JS中的标签为 时,className为RCTText,返回的是,ReactTextViewManager对象,其他组件以此类推。**然后执行了ViewManager对象的createView方法。

public final T createView(
      ThemedReactContext reactContext,
      JSResponderHandler jsResponderHandler) {
    T view = createViewInstance(reactContext);
    addEventEmitters(reactContext, view);if (view instanceof ReactInterceptingViewGroup) {
      ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
    }
    return view;
}

createView方法中通过执行createViewInstance方法返回一个View对象,而createViewInstance方法是一个抽象方法,其具体的实现在子类中。拿ReactTextViewManager举例的话,是这样的。

@Override
  public ReactTextView createViewInstance(ThemedReactContext context) {return new ReactTextView(context);
}

简单粗暴的返回一个ReactTextView,而ReactTextView就是一个TextView。我们看一下其继承关系。

public class ReactTextView extends TextView implements ReactCompoundView {
  ...
}

到这里,我们终于理清了React Native中的View标签是怎么映射到原生View的。既然View已经创建出来了,那么其更新过程基本上也是大同小异,本文就不再详述了。

五、总结

React Native在JS端的View标签本质上只是一个标记位,其最后还是需要通过和原生的交互,并将View标签转换成具体的View对象。其中View的渲染过程还是比较复杂的,本文也只是分析了其渲染的一个整体流程,有些具体的细节并没有深究下去,有兴趣的同学可以深入的分析一下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值