IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> React Native 源码分析(三)——Native View创建流程 -> 正文阅读

[移动开发]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()
-> 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都会被包裹在<View>中 ,<View>对应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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-18 17:46:02  更:2022-05-18 17:46:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:22:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码