【原始碼解析】React Native元件渲染

AndroidHint發表於2019-09-07

一、引言

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 void attachToReactInstanceManager() {
  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(new Runnable() {
    @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方法。

void runApplication() {
    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語句中,得到如下的呼叫棧。

【原始碼解析】React Native元件渲染

從上面的呼叫棧可以看出,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的渲染過程還是比較複雜的,本文也只是分析了其渲染的一個整體流程,有些具體的細節並沒有深究下去,有興趣的同學可以深入的分析一下。

相關文章