ReactNative源码分析之JS渲染成Android控件

581 阅读6分钟
原文链接: blog.csdn.net

经过了一番对ReactNative的学习之后,我们都知道只要敲入react-native run-android神奇的JS代码就被编译成Android可以理解的View了,不得不惊叹ReactNative隐藏的神秘的力量,所以本文分析一下一个Hello,World的JS代码是怎么变成Android控件的.

简单的看一下JS代码:

render() {
    return (<View >
        <Text >Hello,World</Text>
    </View>);
}

开始分析

一开始要分析所以顺着Android的RN源码一步一步的追踪源码在ReactActivityDelegate.js中有这么一步入口源码:

protected void loadApp(String appKey) {
  mReactRootView = createRootView();
  mReactRootView.startReactApplication(
    getReactNativeHost().getReactInstanceManager(),
    appKey,
    getLaunchOptions());
  getPlainActivity().setContentView(mReactRootView);
}

代码中很明显直接将ReactRootView作为这个Activity的contentView,换句话说整个RN其实就是一个ReactRootView而这个ReactRootView又是直接继承了FrameLayout,所以那就好办了原理不就等于是自定义View实现了。
但是!!!!
不一样的是只是重写了onMeasure方法并没有实现onLayout方法:

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

所以我们还是得回头去看看ReactRootView中startReactApplication是怎么实现的:

public void startReactApplication(
    ReactInstanceManager reactInstanceManager,
    String moduleName,
    @Nullable Bundle initialProperties) {
    // 省略不要的相关代码
    if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
      mReactInstanceManager.createReactContextInBackground();
    }
    attachToReactInstanceManager();
}

第一部分中createReactContextInBackground这个方法我们不看主要是去加载bundle文件并解析,更多的细节分析可以查看 React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)
我们主要看attachToReactInstanceManager这个方法,从字面的意思就是给ReactInstanceManager绑定ReactRootView,所以跟着代码进入:

public void attachRootView(ReactRootView rootView) {
   UiThreadUtil.assertOnUiThread();
   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());
   }
 }

经过一番初始化以及清空ReactRootView,然后最后会执行attachRootViewToInstance这个方法。不过这里的ReactContext还没初始化不过通过上下文的源码可以看到最后还是会走attachRootViewToInstance这个方法,所以不管我们就是分析它就对了。

private void attachRootViewToInstance(
    final ReactRootView rootView,
    CatalystInstance catalystInstance) {
  UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
  final int rootTag = uiManagerModule.addRootView(rootView);
  rootView.setRootViewTag(rootTag);
  rootView.runApplication();
  // 省略了部分代码
}

得到了rootTag并给rootView设置了进去,再最后的runApplication中有这么一段代码:

catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);

这段代码就是通过catalystInstance去执行JS中的:

AppRegistry.registerComponent('RN_Demo', () => ProptypesComponent);

到现在为止差不多我们的RN项目就启动起来了,但是每一个JS写的控件是如何转成Android识别的View呢?

View转换原理

首先还是需要从JS中找入口就是说JS的view是怎么被创建?
React-Native 源码分析二-JSX如何渲染成原生页面(下)跟着这篇文章找到了相关的入口:
UIManager.createView
UIManager.updateView
UIManager.manageChildren

而UIManager对应的Android源码类是UIManagerModule.java,举个例子看看createView:

@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
  if (DEBUG) {
    String message =
        "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
    FLog.d(ReactConstants.TAG, message);
    PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
  }
  mUIImplementation.createView(tag, className, rootViewTag, props);
}

看到@ReactMethod这个注解就对了,表明这个是个供JS调用的java方法。所以说我们的每一个JS代码中写的控件最终都是通过这个入口createView创建一个个Android能够识别的View,具体的进一步代码实现如下:

  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    ReactShadowNode cssNode = createShadowNode(className);
    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
    cssNode.setReactTag(tag);
    cssNode.setViewClassName(className);
    cssNode.setRootNode(rootNode);
    cssNode.setThemedContext(rootNode.getThemedContext());

    mShadowNodeRegistry.addNode(cssNode);

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

    handleCreateView(cssNode, rootViewTag, styles);
  }

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

mNativeViewHierarchyOptimizer.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));
    }
  }

最后是将一个个的CreateViewOperation对象加入到列表中去,所以这里总结一下流程:
这里写图片描述

现在一个个JS的View已经加载到Android中了并被封装成CreateViewOperation,从上图我们知道需要执行execute方法才能真正变成一个个Android中的View:

public void execute() {
  mNativeViewHierarchyManager.createView(
      mThemedContext,
      mTag,
      mClassName,
      mInitialProps);
}

继续进入NativeViewHierarchyManager.createView方法看看具体的实现代码:

ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(themedContext, mJSResponderHandler);

代码中直接调用ViewManager.createView就直接一个个View就出来了,那么我们举个例子RCTText也就是JS端中的Text控件,由于每一个View都是对应一个ViewManager所以Text这个控件对应的就是ReactTextViewManager而ViewManager.createView最后调用的就是createViewInstance方法:

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

很好,一个Android端能够识别的ReactTextView就出来了那么JS中的Text也就成功被初始化出来了,可是仅仅只是初始化而已并未被加入到ReactRootView中我们仍然不能看见。

View显示

我们已经有一个个对象了,所以得找绘制入口了。很好的一点是我们站在前人的分析基础上找到了当JNI C++那一层完成JS代码加载之后会回调到OnBatchCompleteListener.onBatchComplete

  public void onBatchComplete() {
    int batchId = mBatchId;
    mBatchId++;
    // 省略代码
    mUIImplementation.dispatchViewUpdates(batchId);
  }

依然直接进入到dispatchViewUpdates方法看看具体实现

  public void dispatchViewUpdates(int batchId) {
    final long commitStartTime = SystemClock.uptimeMillis();
    try {
      updateViewHierarchy();
      mNativeViewHierarchyOptimizer.onBatchComplete();
      mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }
  }

这里看到一个方法updateViewHierarchy经过一番源码跟踪最后进入跟createView类似:

mOperations.add(
        new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height));

所以大胆的猜想UpdateLayoutOperation其实对应的操作ReactRootView中的onLayout,我们也在UIViewOperationQueue中看到一堆实现了UIOperation的类。

继续进入代码我们就发现了UIOperation.execute执行的地方了:

for (UIOperation op : nonBatchedOperations) {
  op.execute();
}

for (UIOperation op : batchedOperations) {
 op.execute();
}

前面我们已经看了CreateViewOperation,这里看下UpdateLayoutOpration的execute方法最后会进入NativeViewHierarchManager.updateLayout:

  public synchronized void updateLayout(
      int parentTag, int tag, int x, int y, int width, int height) {
      //省略注释
    try {
      View viewToUpdate = resolveView(tag);
      viewToUpdate.measure(
          View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
          View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
      ViewParent parent = viewToUpdate.getParent();
      if (parent instanceof RootView) {
        parent.requestLayout();
      }

      // Check if the parent of the view has to layout the view, or the child has to lay itself out.
      if (!mRootTags.get(parentTag)) {
        ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
        ViewGroupManager parentViewGroupManager;
        if (parentViewManager instanceof ViewGroupManager) {
          parentViewGroupManager = (ViewGroupManager) parentViewManager;
        } else {
          throw new IllegalViewOperationException(
              "Trying to use view with tag " + tag +
                  " as a parent, but its Manager doesn't extends ViewGroupManager");
        }
        if (parentViewGroupManager != null
            && !parentViewGroupManager.needsCustomLayoutForChildren()) {
          updateLayout(viewToUpdate, x, y, width, height);
        }
      } else {
        updateLayout(viewToUpdate, x, y, width, height);
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
    }
  }

这里看到MeasureSpec layout相关的词大概就差不多结束了,因为这些就是平时常见的View自定义流程相关的。

addView原理

我们都知道要往ViewGroup中添加View都是通过addView的方式,我们上面一些列操作并未发现这一个操作流程,原来Android中是使用yoga这个神奇的库进行操作。
yoga的github地址:github.com/facebook/yo…
由于是个c++的库水平有限具体实现原理就略过了,我们大概看下例子:

YogaNode root = new YogaNode();
root.setWidth(500);
root.setHeight(300);
root.setAlignItems(CENTER);
root.setJustifyContent(CENTER);
root.setPadding(ALL, 20);

YogaNode text = new YogaNode();
text.setWidth(200);
text.setHeight(25);

YogaNode image = new YogaNode();
image.setWidth(50);
image.setHeight(50);
image.setPositionType(ABSOLUTE);
image.setPosition(END, 20);
image.setPosition(TOP, 20);

root.addChildAt(text, 0);
root.addChildAt(image, 1);

而在RN的源码中提供了一个yoga的入口:

  public void addChildAt(YogaNode child, int i) {
    if (child.mParent != null) {
      throw new IllegalStateException("Child already has a parent, it must be removed first.");
    }

    if (mChildren == null) {
      mChildren = new ArrayList<>(4);
    }
    mChildren.add(i, child);
    child.mParent = this;
    jni_YGNodeInsertChild(mNativePointer, child.mNativePointer, i);
  }

这样子转换了一下确实就清晰了, 这一步的操作太深了只能这么深入浅出了。

那么整体的源码分析流程就这么结束了,路漫漫其修远兮 .. !!== The end ==!!

参考文章:
blog.csdn.net/yanbober/ar…
lrd.ele.me/2016/09/12/…
lrd.ele.me/2016/09/13/…
zhuanlan.zhihu.com/p/32263682