React Native 源码分析——Native View创建流程

Posted 薛瑄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native 源码分析——Native View创建流程相关的知识,希望对你有一定的参考价值。

1、React Native 源码分析(一)—— 启动流程
2、React Native 源码分析(二)—— 通信机制
3、React Native 源码分析(三)—— Native View创建流程
4、React Native 源码分析(四)—— 任务调度
5、React Native 源码分析(五)—— 事件分发

前两篇分析了,React Native 的启动和 通信机制,这篇来分析一下,在收到React 的通信数据字符串后,是如何在原生创建对应的View,React标签设置的View的属性,如何被原生View解析,为什么在自定义View调用requestLayout没有作用呢?

熟悉React 源码的小伙伴们应该知道,React对Component 最终解析为字符串,来提供给ReactNative 使用。在React 中是调用 UIManagerModule中提供的@ReactMethod 一系列方法,来实现在android创建View。下面先来看下ReactNative中UI相关的类图、各类的功能。然后我们从Android接收到这些字符串开始分析源码。

图一(来源

图二(来源

在开始分析源码前,先来看下,从React 传递过来的数据是什么样的,如下图,在桥通信中,是根据methodId找到对应的mMethod(这里是UIManager.createView),后续根据 [3,"RCTRawText",31,"text":"Press onChange"] 来创建View

下面我们就从上图断点堆栈中的UIManagerModule 开始分析

源码分析

1、UIManagerModule# createView

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

UIManagerModule 中函数的具体实现都是交给UIImplementation处理的,下面来看看在UIImplementation中处理

2、UIImplementation# createView

  /** Invoked by React to create a new node with a given tag, class name and properties. */
  public void createView(int tag, String className, int rootViewTag, ReadableMap props) 
    if (!mViewOperationsEnabled) 
      return;
    

    synchronized (uiImplementationThreadLock) 
      //首先创建ShadowNode,它用来表示React中一个对应的node,之后根据这个ShadowNode去创建对应的View
      ReactShadowNode cssNode = createShadowNode(className);
      ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
      //设置node的tag ,它的值是在React中生成的,
      cssNode.setReactTag(tag); // Thread safety needed here
      cssNode.setViewClassName(className);
      cssNode.setRootTag(rootNode.getReactTag());
      cssNode.setThemedContext(rootNode.getThemedContext());
      mShadowNodeRegistry.addNode(cssNode);
      
      //根据React中该node设置的属性,来更新对应的ReactShadowNode
      ReactStylesDiffMap styles = null;
      if (props != null) 
        styles = new ReactStylesDiffMap(props);
        //这里最终是调用了ReactShadowNode,@ReactProp注解方法的函数,把styles设置到ReactShadowNode的成员变量中的
        cssNode.updateProperties(styles);
      

      handleCreateView(cssNode, rootViewTag, styles);
    
  

下面介绍一下 该段代码的重要点,:

ReactShadowNode

ReactShadowNode 代表一个React nodes,可以理解为Android层对React层view的表示,它存在的主要目的是 :

  1. 为了View的measure、layout。ReactNative的中原生View的measure、layout 是由Yoga这个库来实现的,而不是使用原生的measure、layout 。
  2. 可以自定义属性,在React node中使用,例如 <Text/> 对应的ShadowNode,@ReactProp 定义了text属性,如下:
public class ReactRawTextShadowNode extends ReactShadowNodeImpl 

  @VisibleForTesting public static final String PROP_TEXT = "text";

  private @Nullable String mText = null;

  public ReactRawTextShadowNode() 

  @ReactProp(name = PROP_TEXT)
  public void setText(@Nullable String text) 
    mText = text;
    markUpdated();
  

  public @Nullable String getText() 
    return mText;
  

  @Override
  public boolean isVirtual() 
    return true;
  

  @Override
  public String toString() 
    return getViewClass() + " [text: " + mText + "]";
  

ReactShadowNode、 YogaNode(java)、YogaNode(C++)是一一对应的,ReactNative 对View的测量、布局,是在Yoga框架中进行的,后面calculateRootLayout函数中会介绍

图片来自

ReactTag

ReactTag是在React中创建的,单数表示 默认UI框架 ,双数表示Fabric框架的node。两种ReactNative架构的对比,可以参考React Native 源码分析(一)—— 启动流程

在ReactFabric-dev.js 中的nextReactTag = 2

在点击事件中,也会用到ReactTag 用于区分新旧UI框架,如下图:

React Native 0.64.2 针对异常Unable to find JSIModule for class UIManager,怎么定位这个错误,顺着点击事件去找,在上面这个红框的位置断点,等待错误uiManagerType的出现,为什么会错误,是因为自定义view 中子view的tag,没有按照ReactTag的分配规则

cssNode.updateProperties(styles);

ReactShadowNodeImpl 中的 updateProperties,会调用到ViewManagerPropertyUpdater.updateProps,如下

  public static <T extends ReactShadowNode> void updateProps(T node, ReactStylesDiffMap props) 
    //查找ShadowNodeSetter,该接口有setProperty和getProperties 两个方法
    ShadowNodeSetter<T> setter = findNodeSetter(node.getClass());
    Iterator<Map.Entry<String, Object>> iterator = props.mBackingMap.getEntryIterator();
    while (iterator.hasNext()) 
      Map.Entry<String, Object> entry = iterator.next();
      //设置属性值到ReactShadowNode中
      setter.setProperty(node, entry.getKey(), entry.getValue());
    
  

上面的实现比较抽象,具体ShadowNodeSetter的实现是什么,来看下面的代码

  private static <T extends ReactShadowNode> ShadowNodeSetter<T> findNodeSetter(
      Class<? extends ReactShadowNode> nodeClass) 
    @SuppressWarnings("unchecked")
    //在缓存中查找
    ShadowNodeSetter<T> setter = (ShadowNodeSetter<T>) SHADOW_NODE_SETTER_MAP.get(nodeClass);
    if (setter == null) 
      //查找是否有 Class.forName(clsName + "$$PropsSetter"); 的类,如果没有提供就使用FallbackShadowNodeSetter
      //我们在自定义ReactShadowNode时,也可以写个内部类PropsSetter,来实现属性赋值的操作
      setter = findGeneratedSetter(nodeClass);
      if (setter == null) 
        setter = new FallbackShadowNodeSetter<>(nodeClass);
      
      SHADOW_NODE_SETTER_MAP.put(nodeClass, setter);
    

    return setter;
  

如果没有提供PropsSetter,那么都会使用默认的FallbackShadowNodeSetter,下面来看看

    private FallbackShadowNodeSetter(Class<? extends ReactShadowNode> shadowNodeClass) 
      //它会根据ReactShadowNode,把其中使用@ReactProp 注解的方法提取出来,用注解属性name的值 进行区分
      mPropSetters =
          ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(shadowNodeClass);
    

    @Override
    public void setProperty(ReactShadowNode node, String name, Object value) 
      //获取注解属性name的值 对应的函数,函数时以Method 的形式存储在类PropSetter中
      ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
      if (setter != null) 
        //通过反射,在对象node上调用函数,传值为value,这样就相当于是调用了ReactShadowNode中@ReactProp 注解的方法
        setter.updateShadowNodeProp(node, value);
      
    

到此ReactShadowNode 就已经创建、赋值完成了,继续往下分析handleCreateView

3、UIImplementation# handleCreateView

  protected void handleCreateView(
      ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) 
    //是否是一个虚拟的node,就是没有对应的native view。
    // 那么有个疑问,没有对应的native view,那它的内容如何显示出来呢?  
    if (!cssNode.isVirtual()) 
      mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
    
  

通过图一,可以清楚看到,UIImplementation中的函数,都交给了NativeViewHierarchyOptimizer,对View 的层级进行优化

4、NativeViewHierarchyOptimizer# handleCreateView

  /** Handles a createView call. May or may not actually create a native view. */
  public void handleCreateView(
      ReactShadowNode node,
      ThemedReactContext themedContext,
      @Nullable ReactStylesDiffMap initialProps) 

    //判断这个View 是否是一个纯容器view,它自身没有任何需要绘制的,只是为了包裹其他view
    boolean isLayoutOnly =
        node.getViewClass().equals(com.facebook.react.uimanager.ViewProps.VIEW_CLASS_NAME)
            && isLayoutOnlyAndCollapsable(initialProps);
    node.setIsLayoutOnly(isLayoutOnly);

    if (node.getNativeKind() != NativeKind.NONE) 
     //向队列中添加一个创建view的任务
      mUIViewOperationQueue.enqueueCreateView(
          themedContext, node.getReactTag(), node.getViewClass(), initialProps);
    
  

5、UIViewOperationQueue# enqueueCreateView

  public void enqueueCreateView(
      ThemedReactContext themedContext,
      int viewReactTag,
      String viewClassName,
      @Nullable ReactStylesDiffMap initialProps) 
    synchronized (mNonBatchedOperationsLock) 
      mCreateViewCount++;
      //添加到mNonBatchedOperations ArrayDeque
      mNonBatchedOperations.addLast(
          new CreateViewOperation(themedContext, viewReactTag, viewClassName, initialProps));
    
  
代码5.2
  private final class CreateViewOperation extends ViewOperation 

    private final ThemedReactContext mThemedContext;
    private final String mClassName;
    private final @Nullable ReactStylesDiffMap mInitialProps;

    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);
    
  

创建view的操作,被添加到mNonBatchedOperations后,在js->java 一批通信结束后,会调用这些操作,下面来看看

6、onBatchComplete

通过前两篇文章,我们知道,js->java 的回调是 CatalystInstanceImpl 中的静态内部类BridgeCallback,onBatchComplete调用过程如下

BridgeCallback#onBatchComplete -> NativeModuleRegistry#onBatchComplete -> UIManagerModule#onBatchComplete

UIManagerModule#onBatchComplete

为了省篇幅,就不贴代码了,执行 UIManagerModule中的onBatchComplete ,如果设置listener,就调用。最后调用到mUIImplementation.dispatchViewUpdates(batchId);

UIImplementation#onBatchComplete

  /** Invoked at the end of the transaction to commit any updates to the node hierarchy. */
  public void dispatchViewUpdates(int batchId) 
    ...
    final long commitStartTime = SystemClock.uptimeMillis();
    try 
      //更新Root及其子View 的大小、位置。子View的更新是添加到队列中的,代码7分析
      updateViewHierarchy();
      //在上个函数更新View的时候,会mTagsWithLayoutVisited.put(tag, true); 防止相同的更新被添加到队列。添加队列完成后,就清除mTagsWithLayoutVisited
      mNativeViewHierarchyOptimizer.onBatchComplete();
      //从队列中取出任务,添加到新线程中,等ReactChoreographer执行回调mDispatchUIFrameCallback时,会触发新线程中的任务,代码8分析
      mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);
     finally 
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    
  

7、UIImplementation #updateViewHierarchy

  protected void updateViewHierarchy() 

    try 
      for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) 
        int tag = mShadowNodeRegistry.getRootTag(i);
        ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
         //root view 的widthSpec、heightSpec 是 在ReactRootView中通过uiManager.updateRootLayoutSpecs设置的
        if (cssRoot.getWidthMeasureSpec() != null && cssRoot.getHeightMeasureSpec() != null) 
          try 
            //递归通知所有cssNode 即将开始calculateLayout
            notifyOnBeforeLayoutRecursive(cssRoot);
           finally 
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
          
          //计算root view 的位置,通过YogaNative 来计算的
          calculateRootLayout(cssRoot);
          try 
             //递归更新子View,其中如果hasNewLayout()为true,就把更新子View的任务加入到队列UIViewOperationQueue
             //然后设置标志位mHasNewLayout = false,后面在访问相同cssNode的 hasNewLayout()就是false
            applyUpdatesRecursive(cssRoot, 0f, 0f);
           finally 
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
          
          ...
        
      
     ...
  

下面分析一下其中的 calculateRootLayout和applyUpdatesRecursive

calculateRootLayout

calculateRootLayout(cssRoot);的调用过程如下:

ReactShadowNode 和 YogaNode是一一对应的
ReactShadowNode的实现是ReactShadowNodeImpl,YogaNode的实现有YogaNodeJNIBase

代码7.2
calculateRootLayout(cssRoot); //cssRoot 是根ReactShadowNode
-> ReactShadowNode# calculateLayout 
-> YogaNodeJNIBase# calculateLayout(width, height); //广度优先遍历YogaNode节点
->YogaNative.jni_YGNodeCalculateLayoutJNI(mNativePointer, width, height, nativePointers, nodes);
//下面调用到C++,源码路径ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp
->jni_YGNodeCalculateLayoutJNI
->YGNodeCalculateLayoutWithContext
->YGLayoutNodeInternal //这里会先从缓存中,取数据,如果没有就进行计算
->YGNodelayoutImpl // 这里就是RN 的 flexbox layout 算法的实现,

可以通过ReactShadowNode#setMeasureFunction() 来向YogaNode设置一个测量函数,在Yoga框架中 对UI进行计算时,就可以调用到我们自定义的Measure函数

举个例子:
ReactTextViewManager 中创建

  public ReactTextShadowNode createShadowNodeInstance() 
    return new ReactTextShadowNode();
  

ReactTextShadowNode 的是继承ReactBaseTextShadowNode
构造函数会调用initMeasureFunction->setMeasureFunction,后者的实现是在ReactBaseTextShadowNode

  @Override
  public void setMeasureFunction(YogaMeasureFunction measureFunction) 
    mYogaNode.setMeasureFunction(measureFunction);
  

mYogaNode对应的类是 YogaNodeJNIBase

  public void setMeasureFunction(YogaMeasureFunction measureFunction) 
    //最终还是在java中调用自定义的measure
    mMeasureFunction = measureFunction;
    //告诉YogaNative 有自定义的measure
    YogaNative.jni_YGNodeSetHasMeasureFuncJNI(mNativePointer, measureFunction != null);
  

jni_YGNodeSetHasMeasureFuncJNI 调用到C++ src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp

static void jni_YGNodeSetHasMeasureFuncJNI(
    JNIEnv* env,
    jobject obj,
    jlong nativePointer,
    jboolean hasMeasureFunc) 
    //调用到node_modules/react-native/ReactCommon/yoga/yoga/YGNode.cpp 的setMeasureFunc
  _jlong2YGNodeRef(nativePointer)
      ->setMeasureFunc(hasMeasureFunc ? YGJNIMeasureFunc : nullptr);

在YGNode.cpp 中,是把函数YGJNIMeasureFunc 设置到了measure_ 的YGMeasureFunc类型变量 noContext 中了,YGJNIMeasureFunc在下面会介绍到。

接着代码7.2 看看,在YGNodelayoutImpl 算法中,是如何调用到设置的measureFunction

->YGNodelayoutImpl
//hasMeasureFunc 就是判断measure_.noContext 是否为空
-> if (node->hasMeasureFunc()) 
    YGNodeWithMeasureFuncSetMeasuredDimensions
    
-> const YGSize measuredSize = node->measure(
        innerWidth,
        widthMeasureMode,
        innerHeight,
        heightMeasureMode,
        layoutContext);    
//进入类 node_modules/react-native/ReactCommon/yoga/yoga/YGNode.cpp,见下图7.1
-> YGSize YGNode::measure(
    float width,
    YGMeasureMode widthMode,
    float height,
    YGMeasureMode heightMode,
    void* layoutContext) 
  return facebook::yoga::detail::getBooleanData(flags, measureUsesContext_)
      ? measure_.withContext(
            this, width, widthMode, height, heightMode, layoutContext)
      : measure_.noContext(this, width, widthMode, height, heightMode);
    
//进入类 ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp 中的YGJNIMeasureFunc,
//是在jni_YGNodeSetHasMeasureFuncJNI中设置的
//调用到java YogaNodeJNIBase 的measure函数,因为objectClass是YogaNodeJNIBase的对象
//见下图7.2
-> static const jmethodID methodId = facebook::yoga::vanillajni::getMethodId(
        env, objectClass.get(), "measure", "(FIFI)J");

下图7.1

下图7.2

  // Implementation Note: Why this method needs to stay final
  //
  // We cache the jmethodid for this method in Yoga code. This means that even if a subclass
  // were to override measure, we'd still call this implementation from layout code since the
  // overriding method will have a different jmethodid. This is final to prevent that mistake.
  @DoNotStrip
  public final long measure(float width, int widthMode, float height, int heightMode) 
    if (!isMeasureDefined()) 
      throw new RuntimeException("Measure function isn't defined!");
    
    //这里就调用到了自定义的measure
    return mMeasureFunction.measure(
        this,
        width,
        YogaMeasureMode.fromInt(widthMode),
        height,
        YogaMeasureMode.fromInt(heightMode));
  

最终每个YogaNode 测量、布局完的结果,会保存到YogaNodeJNIBase的 arr 数组中

applyUpdatesRecursive(cssRoot, 0f, 0f);的调用过程如下:

UIImplementation#applyUpdatesRecursive()
-> 以上是关于React Native 源码分析——Native View创建流程的主要内容,如果未能解决你的问题,请参考以下文章

React Native 源码分析——Native View创建流程

React Native 源码分析——Native View创建流程

React Native 源码分析——Native View创建流程

React Native Android入门实战及深入源码分析系列——React Native源码编译

React Native源码分析之Native UI的封装和管理

React Native之底层源码分析篇