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

React Native 源码分析(三)——Native View创建流程,第1张

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的表示,它存在的主要目的是 :

为了View的measure、layout。ReactNative的中原生View的measure、layout 是由Yoga这个库来实现的,而不是使用原生的measure、layout 。可以自定义属性,在React node中使用,例如 对应的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()
-> ReactShadowNodeImpl#.dispatchUpdates() //把YogaNode arr 数组中的布局结果,赋给ReactShadowNode对应的值
-> UIViewOperationQueue#.enqueueUpdateLayout()  //在hasNewLayout() 检查mHasNewLayout ,如果通过 则nativeViewHierarchyOptimizer.handleUpdateLayout,在队列中添加一条更新布局的任务UpdateLayoutOperation
-> ReactShadowNodeImpl#.markUpdateSeen() //设置标志位mHasNewLayout = false

上面代码enqueueUpdateLayout,添加任务UpdateLayoutOperation 到 UIViewOperationQueue 的 mOperations中

代码7.10
    public UpdateLayoutOperation(int parentTag, int tag, int x, int y, int width, int height) {
      super(tag);
      mParentTag = parentTag;
      mX = x;
      mY = y;
      mWidth = width;
      mHeight = height;
      Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
    }

    @Override
    public void execute() {
      Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
      //这个任务的执行,在运行任务时,再进行分析
      mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);
    }

UpdateLayoutOperation任务的执行,在运行任务时,再进行分析。下面继续代码6分析 mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);

8、UIViewOperationQueue # dispatchViewUpdates
 public void dispatchViewUpdates(
      final int batchId, final long commitStartTime, final long layoutTime) {
   ...省略部分代码... 
    try {
      final long dispatchViewUpdatesTime = SystemClock.uptimeMillis();
      final long nativeModulesThreadCpuTime = SystemClock.currentThreadTimeMillis();
      
      // Store the current operation queues to dispatch and create new empty ones to continue
      // receiving new operations
      final ArrayList<DispatchCommandViewOperation> viewCommandOperations;
      if (!mViewCommandOperations.isEmpty()) {
        viewCommandOperations = mViewCommandOperations;
        mViewCommandOperations = new ArrayList<>();
      } else {
        viewCommandOperations = null;
      }

      final ArrayList<UIOperation> batchedOperations;
      if (!mOperations.isEmpty()) {
        batchedOperations = mOperations;
        mOperations = new ArrayList<>();
      } else {
        batchedOperations = null;
      }

      final ArrayDeque<UIOperation> nonBatchedOperations;
      synchronized (mNonBatchedOperationsLock) {
        if (!mNonBatchedOperations.isEmpty()) {
          nonBatchedOperations = mNonBatchedOperations;
          mNonBatchedOperations = new ArrayDeque<>();
        } else {
          nonBatchedOperations = null;
        }
      }

      if (mViewHierarchyUpdateDebugListener != null) {
        mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued();
      }

      //创建runOperations,执行viewCommandOperations、nonBatchedOperations和batchedOperations 中的任务
      Runnable runOperations =
          new Runnable() {
            @Override
            public void run() {
             ...省略部分代码... 
              try {
                long runStartTime = SystemClock.uptimeMillis();

                // All ViewCommands should be executed first as a perf optimization.
                // This entire block is only executed if there's at least one ViewCommand queued.
                if (viewCommandOperations != null) {
                  for (DispatchCommandViewOperation op : viewCommandOperations) {
                    try {
                      op.executeWithExceptions();
                    } catch (RetryableMountingLayerException e) {
                      ...省略部分代码... 
                    }
                  }
                }

                // All nonBatchedOperations should be executed before regular operations as
                // regular operations may depend on them
                if (nonBatchedOperations != null) {
                  for (UIOperation op : nonBatchedOperations) {
                    op.execute();
                  }
                }

                if (batchedOperations != null) {
                  for (UIOperation op : batchedOperations) {
                    op.execute();
                  }
                }

                ...省略部分代码... 
               
                // Clear layout animation, as animation only apply to current UI operations batch.
                mNativeViewHierarchyManager.clearLayoutAnimation();

                if (mViewHierarchyUpdateDebugListener != null) {
                  mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished();
                }
              } catch (Exception e) {
                mIsInIllegalUIState = true;
                throw e;
              } finally {
                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
              }
            }
          };

      ...省略部分代码... 
      
      //把上面的runable加入到mDispatchUIRunnables中,mDispatchUIRunnables 会在 doFrameGuarded中flushPendingBatches 执行,代码9介绍
      synchronized (mDispatchRunnablesLock) {
        
        mDispatchUIRunnables.add(runOperations);
      }

      // In the case where the frame callback isn't enqueued, the UI isn't being displayed or is
      // being
      // destroyed. In this case it's no longer important to align to frames, but it is important to
      // make
      // sure any late-arriving UI commands are executed.
      if (!mIsDispatchUIFrameCallbackEnqueued) {
        UiThreadUtil.runOnUiThread(
            new GuardedRunnable(mReactApplicationContext) {
              @Override
              public void runGuarded() {
                flushPendingBatches();
              }
            });
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }
  }

viewCommandOperations 执行 JS层View 调用的函数(命令),最终会调用到Java层的ViewManager中receiveCommand函数中nonBatchedOperations 只有创建View 的任务CreateViewOperation,会被添加到这个队列中batchedOperations 除了创建View的任务,例如 更新布局UpdateLayoutOperation 等等。

这里之所以要把创建View,和其他View *** 作的任务 分开,是因为对View *** 作的任务,需要先创建出View后,才能进行。所以nonBatchedOperations必须要先于batchedOperations执行

接下来,我们来看加入的这些任务,是在什么时候被调用的。这部分代码比较重要,源码中自带的UI JS 帧率监测,也是在这个时机

9、DispatchUIFrameCallback

DispatchUIFrameCallback继承了Choreographer,看到后者大家应该清楚了,是在每一帧的回调执行任务,没错。回调注册和删除是在Activity的resume和pause时

  /* package */ void resumeFrameCallback() {
    mIsDispatchUIFrameCallbackEnqueued = true;
    ReactChoreographer.getInstance()
        .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
  }

  /* package */ void pauseFrameCallback() {
    mIsDispatchUIFrameCallbackEnqueued = false;
    ReactChoreographer.getInstance()
        .removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
    flushPendingBatches();
  }

Choreographer的重要回调doFrame,最后会调用DispatchUIFrameCallback 的doFrameGuarded

 @Override
    public void doFrameGuarded(long frameTimeNanos) {
      ...
      try {
      //先执行nonBatchedOperations中的任务,都是创建View的
        dispatchPendingNonBatchedOperations(frameTimeNanos);
      } ...
      //执行代码8,加入的Runable。执行viewCommandOperations、nonBatchedOperations、batchedOperations
      flushPendingBatches();

      ReactChoreographer.getInstance()
          .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this);
    }

下面接代码5.2,来看一下任务CreateViewOperation的执行mNativeViewHierarchyManager.createView(mThemedContext, mTag, mClassName, mInitialProps)

10、NativeViewHierarchyManager # CreateViewOperation

根据ViewManager,来创建View。一般我们自定义View给JS层使用,是需要自定义ViewManager

  public synchronized void createView(
      ThemedReactContext themedContext,
      int tag,
      String className,
      @Nullable ReactStylesDiffMap initialProps) {

    UiThreadUtil.assertOnUiThread();
    try {
      //获取到对应的ViewManager
      ViewManager viewManager = mViewManagers.get(className);
      //如果是自定义View,需要实现ViewManager中的createView方法
      View view =
          viewManager.createView(tag, themedContext, initialProps, null, mJSResponderHandler);
      mTagsToViews.put(tag, view);
      mTagsToViewManagers.put(tag, viewManager);
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
    }
  }

mViewManagers 中的数据,是在React Native初始化的时候,进行收集的(可参考文章React Native 源码分析(一)—— 启动流程),通过UIManagerModule来设置到NativeViewHierarchyManager的ViewManagerRegistry类型的mViewManagers变量中

大部分自定义ViewManager都是继承SimpleViewManager、ViewGroupManager

介绍一些自定义ViewManager的一些有用方法,最基本的就不介绍了:

onDropViewInstance 删除view时,会触发该方法,可以进行一些清理工作onAfterUpdateTransaction 在属性都初始化完成之后,会调用该方法receiveCommand 可以在js层通过View直接调用该命令,而不用 通过ReactContextBaseJavaModule

到此,从React到Android, View的创建过程就分析完了。通过上面分析知道,View的measure和layout,是通过Yoga进行的,那么layout的结果,该如何使用呢?下面接代码代码7.10,来看一下任务UpdateLayoutOperation的执行mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);

11、NativeViewHierarchyManager # UpdateLayoutOperation
  public synchronized void updateLayout(
      int parentTag, int tag, int x, int y, int width, int height) {
     ...省略部分代码... 
    try {
      View viewToUpdate = resolveView(tag);

      ...省略部分代码... 

      // Check if the parent of the view has to layout the view, or the child has to lay itself out.
      //判断是否是root view
      if (!mRootTags.get(parentTag)) {
        //获取一下,即将layout view 的父view 对应的ViewManager,目的就是为了判断needsCustomLayoutForChildren()的返回值
        ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
        IViewManagerWithChildren parentViewManagerWithChildren;
        if (parentViewManager instanceof IViewManagerWithChildren) {
          parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager;
        } else {
          throw new IllegalViewOperationException(
              "Trying to use view with tag "
                  + parentTag
                  + " as a parent, but its Manager doesn't implement IViewManagerWithChildren");
        }
        //needsCustomLayoutForChildren  返回 true 表示子view的layout由android原生的来调用,false 表示由RN 来设置layout的参数
        if (parentViewManagerWithChildren != null
            && !parentViewManagerWithChildren.needsCustomLayoutForChildren()) {
          //该函数里面 调用view 的layout,把 Yoga的测量结果,告知到Android 体系中
          updateLayout(viewToUpdate, x, y, width, height);
        }
      } else {
        updateLayout(viewToUpdate, x, y, width, height);
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
    }
  }

最后看一下updateLayout

代码11.2 
  private void updateLayout(View viewToUpdate, int x, int y, int width, int height) {
     //
    if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) {
      //如果允许动画的话,在这里处理
      mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height);
    } else {
      //一般到这里,layout就是android的三大绘制函数之一
      viewToUpdate.layout(x, y, x + width, y + height);
    }
  }

主要的流程就全部分析完了,那看到这里,我们来思考个问题:

React 中的View,它们的UI style 使用的是css那一套。那么ReactNative 使用Yoga在YGNodelayoutImpl 实现对UI 、布局的解析,可以得到每个View的宽高和位置,也就是代替了在android中的measure、layout功能 ,最终的结果也是需要告知到Android体系中,也就是代码11.2 调用了layout。 正常情况下在android中的ViewGroup 的 layout 会调用所有嵌套View的layout,那岂不是和ReactNative 使用Yoga 的出现矛盾了?(不用担心View的layout,因为代码11.2 调用 View的layout,只是设置自身的位置,不会调用到其他View的layout)

在React中所有的node都会被包裹在中 ,对应Android的类是ReactViewGroup,来看下它的部分代码:

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // No-op since UIManagerModule handles actually laying out children.
  }


  public void requestLayout() {
    // No-op, terminate `requestLayout` here, UIManagerModule handles laying out children and
    // `layout` is called on all RN-managed views by `NativeViewHierarchyManager`
  }

可是看到,重写了onLayout和requestLayout,但都是空实现。onLayout 没有必要 也不能实现,因为子View的位置都已经在Yoga 中确定了。下面的子View调用requestLayout,最终也会在这里被打断,因为measure、layout都在Yoga中计算了,requestLayout 也就形同虚设了。

到此Android层面的UI流程就大概分析完了,虽然不是逐行代码分析,但比网上大大部分,要详细很多。欢迎小伙伴们 点赞、评论 支持一下,U·ェ·U

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1297901.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存