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

經(jīng)過了一番對(duì)ReactNative的學(xué)習(xí)之后,我們都知道只要敲入react-native run-android神奇的JS代碼就被編譯成Android可以理解的View了,不得不驚嘆ReactNative隱藏的神秘的力量,所以本文分析一下一個(gè)Hello,World的JS代碼是怎么變成Android控件的.

簡(jiǎn)單的看一下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作為這個(gè)Activity的contentView,換句話說整個(gè)RN其實(shí)就是一個(gè)ReactRootView而這個(gè)ReactRootView又是直接繼承了FrameLayout,所以那就好辦了原理不就等于是自定義View實(shí)現(xiàn)了。
但是?。。?!
不一樣的是只是重寫了onMeasure方法并沒有實(shí)現(xiàn)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是怎么實(shí)現(xiàn)的:

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

第一部分中createReactContextInBackground這個(gè)方法我們不看主要是去加載bundle文件并解析,更多的細(xì)節(jié)分析可以查看 React Native Android 源碼框架淺析(主流程及 Java 與 JS 雙邊通信)
我們主要看attachToReactInstanceManager這個(gè)方法,從字面的意思就是給ReactInstanceManager綁定ReactRootView,所以跟著代碼進(jìn)入:

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

經(jīng)過一番初始化以及清空ReactRootView,然后最后會(huì)執(zhí)行attachRootViewToInstance這個(gè)方法。不過這里的ReactContext還沒初始化不過通過上下文的源碼可以看到最后還是會(huì)走attachRootViewToInstance這個(gè)方法,所以不管我們就是分析它就對(duì)了。

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設(shè)置了進(jìn)去,再最后的runApplication中有這么一段代碼:

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

這段代碼就是通過catalystInstance去執(zhí)行JS中的:

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

到現(xiàn)在為止差不多我們的RN項(xiàng)目就啟動(dòng)起來了,但是每一個(gè)JS寫的控件是如何轉(zhuǎn)成Android識(shí)別的View呢?

View轉(zhuǎn)換原理

首先還是需要從JS中找入口就是說JS的view是怎么被創(chuàng)建?
React-Native 源碼分析二-JSX如何渲染成原生頁面(下)跟著這篇文章找到了相關(guān)的入口:
UIManager.createView UIManager.updateView UIManager.manageChildren
而UIManager對(duì)應(yīng)的Android源碼類是UIManagerModule.java,舉個(gè)例子看看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這個(gè)注解就對(duì)了,表明這個(gè)是個(gè)供JS調(diào)用的java方法。所以說我們的每一個(gè)JS代碼中寫的控件最終都是通過這個(gè)入口createView創(chuàng)建一個(gè)個(gè)Android能夠識(shí)別的View,具體的進(jìn)一步代碼實(shí)現(xiàn)如下:

  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這個(gè)方法最后是調(diào)用UIViewOperationQueue.enqueueCreateView

  public void enqueueCreateView(
      ThemedReactContext themedContext,
      int viewReactTag,
      String viewClassName,
      @Nullable ReactStylesDiffMap initialProps) {
    synchronized (mNonBatchedOperationsLock) {
      mNonBatchedOperations.addLast(
        new CreateViewOperation(
          themedContext,
          viewReactTag,
          viewClassName,
          initialProps));
    }
  }

最后是將一個(gè)個(gè)的CreateViewOperation對(duì)象加入到列表中去,所以這里總結(jié)一下流程:

RN-view-execute.png

現(xiàn)在一個(gè)個(gè)JS的View已經(jīng)加載到Android中了并被封裝成CreateViewOperation,從上圖我們知道需要執(zhí)行execute方法才能真正變成一個(gè)個(gè)Android中的View:

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

繼續(xù)進(jìn)入NativeViewHierarchyManager.createView方法看看具體的實(shí)現(xiàn)代碼:

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

代碼中直接調(diào)用ViewManager.createView就直接一個(gè)個(gè)View就出來了,那么我們舉個(gè)例子RCTText也就是JS端中的Text控件,由于每一個(gè)View都是對(duì)應(yīng)一個(gè)ViewManager所以Text這個(gè)控件對(duì)應(yīng)的就是ReactTextViewManager而ViewManager.createView最后調(diào)用的就是createViewInstance方法:

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

很好,一個(gè)Android端能夠識(shí)別的ReactTextView就出來了那么JS中的Text也就成功被初始化出來了,可是僅僅只是初始化而已并未被加入到ReactRootView中我們?nèi)匀徊荒芸匆姟?/p>

View顯示

我們已經(jīng)有一個(gè)個(gè)對(duì)象了,所以得找繪制入口了。很好的一點(diǎn)是我們站在前人的分析基礎(chǔ)上找到了當(dāng)JNI C++那一層完成JS代碼加載之后會(huì)回調(diào)到OnBatchCompleteListener.onBatchComplete

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

依然直接進(jìn)入到dispatchViewUpdates方法看看具體實(shí)現(xiàn)

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

這里看到一個(gè)方法updateViewHierarchy經(jīng)過一番源碼跟蹤最后進(jìn)入跟createView類似:

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

所以大膽的猜想U(xiǎn)pdateLayoutOperation其實(shí)對(duì)應(yīng)的操作ReactRootView中的onLayout,我們也在UIViewOperationQueue中看到一堆實(shí)現(xiàn)了UIOperation的類。

繼續(xù)進(jìn)入代碼我們就發(fā)現(xiàn)了UIOperation.execute執(zhí)行的地方了:

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

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

前面我們已經(jīng)看了CreateViewOperation,這里看下UpdateLayoutOpration的execute方法最后會(huì)進(jìn)入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相關(guān)的詞大概就差不多結(jié)束了,因?yàn)檫@些就是平時(shí)常見的View自定義流程相關(guān)的。

addView原理

我們都知道要往ViewGroup中添加View都是通過addView的方式,我們上面一些列操作并未發(fā)現(xiàn)這一個(gè)操作流程,原來Android中是使用yoga這個(gè)神奇的庫進(jìn)行操作。
yoga的github地址:https://github.com/facebook/yoga
由于是個(gè)c++的庫水平有限具體實(shí)現(xiàn)原理就略過了,我們大概看下例子:

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的源碼中提供了一個(gè)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);
  }

這樣子轉(zhuǎn)換了一下確實(shí)就清晰了, 這一步的操作太深了只能這么深入淺出了。

那么整體的源碼分析流程就這么結(jié)束了,路漫漫其修遠(yuǎn)兮 ..
!!== The end ==!!

參考文章:
https://blog.csdn.net/yanbober/article/details/53157456
http://lrd.ele.me/2016/09/12/React-Native-jsx-code-analyse1/
http://lrd.ele.me/2016/09/13/React-Native-jsx-code-analyse2/
https://zhuanlan.zhihu.com/p/32263682

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容