【React Native】源碼分析之Native UI的封裝和管理

轉載請註明出處:http://blog.csdn.net/u013531824/article/details/54020287,謝謝。

  ReactNative作爲使用React開發Native應用的新框架,隨着時間的增加,無論是社區還是個人對她的興趣與日遞增。此文目的是希望和大家一起欣賞一下ReactNative的部分源碼。閱讀源碼好處多多,讓攻城獅更溜的開發ReactNative應用的同時,也能梳理RN項目的設計思路,增加自己的內功修爲,^_^。
  好的,就讓我們輕鬆的開始吧。此篇是以Android平臺源碼分析爲主,分享Native UI的封裝和管理,重點涉及react-native源碼中com.facebook.react.uimanager包中的相關類。
  通過下圖對剖析的源碼部分有個整體的概念,這是從下向上的調用關係。
UIManagerModule調用關係

  因爲上層是向我們直接暴露的類,所以我們採用從上向下的分析過程,以ReactImageManager作爲切入點進行分析。兩個原因

  • 圖片是任何應用都必不可少的元素
  • ReactImageView封裝Facebook的Fresco圖片框架,在剖析的過程中可同時梳理RN封裝第三方框架的過程。

首先看一下ReactImageManager的代碼實現:

@ReactModule(name = ReactImageManager.REACT_CLASS)
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
  protected static final String REACT_CLASS = "RCTImageView";

  @Override
  public String getName() {
    return REACT_CLASS;
  }

  @Override
  public ReactImageView createViewInstance(ThemedReactContext context) {
    return new ReactImageView(
        context,
        getDraweeControllerBuilder(),
        getCallerContext());
  }
 }

  此處的ReactImageView就是ReactNative封裝的圖像處理相關的Native UI ,他的定義如下,使用過FacebookFresco圖片開源項目的開發者應該會很熟悉GenericDraweeView類,繼承她實現自己的圖片展示邏輯。

public class ReactImageView extends GenericDraweeView {}

  通過ReactImageManager對本地ReactImageView進行管理。

知識點一:封裝React可以使用的Native UI View,需要創建一個ViewManager進行管理。

  可以說這是標準ViewManager的官方推薦的寫法,繼承SimpleViewManager重寫getNamecreateViewInstance方法,但是此處我們不禁會問–爲什麼?爲什麼要重寫這兩個方法,在源碼中是什麼用的調用關係,導致了這種結果。

下面看一張ViewManager的繼承關係圖:
ViewManager的繼承關係
  上圖可以清晰反饋ReactImageManager的繼承關係,最終定位到ViewManager類,同時SimpleViewManager負責對View的管理,而對ViewGroup的封裝需要繼承ViewGroupManager實現。也許上面問題的答案我們可以在他的超父類ViewManager中找到答案。

看一下ViewManager的類圖可以給我們什麼信息:
ViewManager類圖

OK~,ViewManager中定義我們關心的getNamecreateViewInstance抽象方法。而createViewInstance的使用是在createView方法中,看源碼:

/**
 *  ViewManager類源碼
 *  Creates a view and installs event emitters on it.
 */
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;
}

此方法完成兩件事:

  1. 創建本地View對象,通過抽象方法createViewInstance(reactContext)完成,所以子類必須實現這個方法,否則View對象爲空。
  2. 通過抽象方法addEventEmitters()註冊事件的類型。(比如我們自定義的監聽事件,需要子類在此方法中註冊)

OK ~ , 以ViewManager的createView()爲切入口,看一下整個創建可以被React使用的Native UI的調用過程。


NativeViewHierarchyManager

  查看createView()的調用,引出一個新的類,名字叫NativeViewHierarchyManager,同樣位於com.facebook.react.uimanager包中。在她的實現中,有這麼一段代碼,

public void createView(
    ThemedReactContext themedContext,
    int tag,
    String className,
    @Nullable ReactStylesDiffMap initialProps) {
  UiThreadUtil.assertOnUiThread();
  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);
  }
}

此方法完成以下幾個工作:

  1. 做線程判斷,此方法必須在UI線程中調用。
  2. 通過ClassName獲取到對應的ViewManager
  3. 創建View實例,對應到我們剖析的主角就是ReactImageView,使用的方法就是上文提到的ViewManagercreateView方法;
  4. 分別存儲ViewViewMangermTagsToViewsmTagsToViewManagers中;
  5. 設置新創建的ViewId,爲什麼要這麼做?
    是爲了重用,減少開銷,由於不是通過XML的形式創建,所以View並沒有對應的ID,需要手動去設置,這裏設置的ID值爲傳遞過來的參數Tag
  6. 如果所有屬性都初始化(@ReactPro註解的方法)完成,做一次回調,通知ViewManager去做屬性全部初始化成功之後的操作。

最終會調用ViewManagerupdateProperties函數,目的是更新屬性Props和給子類刷新的機會。

public final void updateProperties(T viewToUpdate, ReactStylesDiffMap props) {
  ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props);
  onAfterUpdateTransaction(viewToUpdate);
}
  • 更新屬性。
  • 更新之後要做的事情交給子類去實現。

例如我們的主角ReactImageManager要做的事情就是:

//ReactImageManager源碼
@Override
protected void onAfterUpdateTransaction(ReactImageView view) {
  super.onAfterUpdateTransaction(view);
  view.maybeUpdateView();
}

判斷是否需要更新ImageView試圖,如果需要馬上更新。

知識點二:如果你的需求中要求在屬性都初始化完成之後,要做一些處理,請重寫onAfterUpdateTransaction方法。

OK~,NativeViewHierarchyManager類設計用途,除了觸發ViewManager創建Native UI的衍生對象對外,還有哪些?請看類圖:

NativeViewHierarchyManager類圖

  NativeViewHierarchyManager通過兩個主要類控制Native UI View的創建、更新、佈局修改、屬性變化等。其中一個是上文提到的ViewManager類,另外一個是ViewManagerRegister類。後者存放一個ViewManager的映射關係,通過getName的返回值作爲key值,而getName的返回值,也是在JavaScript中定義Module時使用名字,如此當JavaScript調用React組件時,通過名稱可以找到對應的ViewManager,通過ViewManager可以找到對應的Native UI View,從而可以使用JavaScript構建原生應用效果。帖一下下ViewManagerRegister的代碼,方便理解viewManager.getName()方法的使用。

 //ViewManagerRegistry源碼
 public ViewManagerRegistry(List<ViewManager> viewManagerList) {
    for (ViewManager viewManager : viewManagerList) {
      mViewManagers.put(viewManager.getName(), viewManager);
    }
  }

知識點三: 自定義ViewManager爲什麼要重寫getName方法?其一爲JavaScript使用封裝後的ReactView時,能對應到原生自定義的ViewManager,從而操作View;其二JavaScript當創建組件類時會使用這個名字。

知識點四:自定義ViewManager重寫createViewInstance的目的是創建Native UI View的對象,並且添加到本地視圖層級結構中。


UIViewOperationQueue

  那麼我的問題又來了,誰調用的NativeViewHierarchyManagercreateView方法吶?傳遞的Tag又是如何定義的?OK,我們在源碼中找到UIViewOperationQueue這個Java類,好樣的,根據名字感覺她是UIView的執行隊列。具體是不是吶,那我們來看下代碼:

//UIViewOperationQueue源碼
private final NativeViewHierarchyManager mNativeViewHierarchyManager;
private final class CreateViewOperation extends ViewOperation {

  private final ThemedReactContext mThemedContext;
  private final String mClassName;
  private final @Nullable ReactStylesDiffMap mInitialProps;
  ...
  @Override
  public void execute() {
    mNativeViewHierarchyManager.createView(
        mThemedContext,
        mTag,
        mClassName,
        mInitialProps);
  }
}

  代碼寫的清晰明瞭,當有UI操作(動畫、View的層次結構發生變化的時候),就會執行execute方法,也就是調用NativeViewHierarchyManagercreateView方法創建新的View對象。來個庖丁解牛CreateViewOperation在哪裏被調用?

// UIViewOperationQueue源碼
@GuardedBy("mNonBatchedOperationsLock")
private ArrayDeque<UIOperation> mNonBatchedOperations = new ArrayDeque<>();

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

  創建一個數組隊列,隊列的名字爲mNonBatchedOperations,每次調用enqueueCreateView方法,向數組隊列中添加一個創建View的操作。
那麼除了創建本地視圖,她還定義了那些操作吶:

  • ViewOperation:根據Tag,指定原生View去操作;
  • RemoveRootViewOperation:刪除TootView的操作;
  • UpdatePropertiesOperation:更新屬性操作;
  • UpdateLayoutOperation:更新Native View的位置和大小的操作;
  • ManageChildrenOperation:管理子視圖操作;
  • RegisterAnimationOperation:註冊動畫的操作;
  • AddAnimationOperation : 增加動畫的操作;
  • SetLayoutAnimationEnabledOperation:設置佈局動畫是否可用的操作
  • MeasureOperation:測量操作

  可以把UIViewOperationQueue看成一個緩衝帶,他不去完成實質性的操作,真正的實現都在NativeViewHierarchyManager中完成,他將JavaScript要對Native View做的所有操作都放在對應隊列中,緩存起來批量處理。根據上面的代碼,創建Native View衍生對象的操作,已經放到了隊列中,那麼是誰操作的隊列去添加操作(Operation)吶?

come on 搞起~


NativeViewHierarchyOptimizer

  不難跟到NativeViewHierarchyOptimizer類,看名字像是NativeViewHierarchy的優化程序,看代碼後,你還別說還真是做優化本地UI視圖層級結構的工作的,看看此類的官方介紹:

負責優化本地視圖層次結構,同時仍然遵循JS指定的最終UI樣式。 基本上,JS向我們發送了一個節點層次結構,雖然在JS中容易理解,但是直接轉換爲本地視圖效率很低。 這個類位於UIManagerModule(直接接收來自JS的視圖命令)和UIViewOperationQueue之間,它使本地視圖層次上的實際操作入隊。它能夠從UIManagerModule獲取指令,並將輸出指令傳遞到本地視圖層次結構,使用較少的視圖,實現相同的效果。

對於NativeViewHierarchyOptimizer的優化過程,咱們看一下他的實現思路,代碼如下:

private static final boolean ENABLED = true;
/**
 * Handles a createView call. May or may not actually create a native view.
 */
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);
  }
}

  這裏的ENABLED非常有意思,默認值是true,是私有的常量,不可重新賦值,那就逗了,所有if(!ENABLED)裏面的代碼永遠不會執行,這是我的理解,如果你有其他的理解,歡迎交流。

  通過isLayoutOnly來判斷是否向創建View的隊列中添加元素,這裏引入了兩個關鍵類ReactShadowNodeViewProps,先來說一下ViewPropsJava類,其定義了很多屬性名的常量。

  //ViewProps源碼
  public static final String ALIGN_ITEMS = "alignItems";
  public static final String ALIGN_SELF = "alignSelf";
  public static final String OVERFLOW = "overflow";
  public static final String BOTTOM = "bottom";
  ...

  另外將只導致佈局變化(Layout Change),不引起重繪(no Drawing)的常量放在HashSet中,起名爲LAYOU_ONLY_PROPS,在NativeViewHierarchyOptimizer類中運用,起到優化本地試圖層級的效果。
  代碼中的判斷條件爲節點爲View類型並且僅改變佈局屬性的話,就不需要重新創建本地View的實例,否則創建,通過這種邏輯來優化本地View的實例創建,從而節省內存開支。
  當然NativeViewHierarchyOptimizer還做了其他命令的優化工作,將優化後需要Native View執行的操作,存儲到上文中的UIViewOperationQueue中,等待JavaScript批處理執行。
  OK~,那麼JavaScript命令又是通過什麼傳遞到NativeViewHierarchyOptimizer中的吶?ReactShadowNode類又是如何傳遞過來的,JavaScript和Native通信的過程中扮演什麼樣的角色???

我們離真相越來越近了,Come On~~


UIImplementation

答案是通過UIImplementation類,看一小部分源碼實現:

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

調用的方法很熟悉,上文剛介紹完,繼續跟

/**
 * UIImplementation 源碼
 * 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) {
  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);
}

  在createView函數中,最後調用了handleCreateView,另外讓人興奮的是,找到了ReactShadowNode的源頭,在這裏根據className創建名稱爲cssNodeReactShadowNode對象,上文使用的node.getReactTag()獲取tag的方法,根源就在此處。在函數的註解中介紹到React通過給定的tag、類名、屬性調用這個函數去創建一個新的節點。

注: 在Android中,佈局的每個元素我們稱之爲View。在React中,因爲採用Web的思想,佈局中的元素被稱之爲節點(node)。

  所以分析到這裏,不用看ReactShadowNode的源碼實現,我們也能猜測到他的用途,他代表了React佈局中的一個元素,對應Native佈局層級中的一個View。他的屬性包括節點Tag(ReactTag)、節點類名(ViewClassName)、根節點信息(rootNode)、位置、自身大小等等信息,可以理解爲React虛擬數上的一個最基礎的節點。擁有這些信息,就可獲取到當前節點的位置進行佈局。

再進一步,創建ReactShadowNode方法:

//UIImplementation源碼
protected ReactShadowNode createShadowNode(String className) {
  ViewManager viewManager = mViewManagers.get(className);
  return viewManager.createShadowNodeInstance();
}

奧,好熟悉竟然是ViewManager,我們就是從這個類作爲入口進行分析的啊,OK~,看createShadowNodeInstance()方法,

//ViewManager源碼
/**
 * This method should return a subclass of {@link ReactShadowNode} which will be then used for
 * measuring position and size of the view. In mose of the cases this should just return an
 * instance of {@link ReactShadowNode}
 */
public abstract C createShadowNodeInstance();

原來是一個抽象方法,那我們的主角ReactImageManager需要去實現這個方法,找一下發現在SimpleViewManager裏進行了實現,

//SimpleViewManager源碼
@Override
public LayoutShadowNode createShadowNodeInstance() {
  return new LayoutShadowNode();
}

LayoutShadowNode提供了基本的佈局屬性,如寬高、flex等等,這裏也使用到了ViewProps定義的一些屬性常量。

public class LayoutShadowNode extends ReactShadowNode {

  @ReactProp(name = ViewProps.WIDTH, defaultFloat = CSSConstants.UNDEFINED)
  public void setWidth(float width) {
    setStyleWidth(CSSConstants.isUndefined(width) ? width : PixelUtil.toPixelFromDIP(width));
  }

  這裏就給了我們想象的空間,除去這些基本的佈局屬性,如果我們想自定義View,就可以繼承LayoutShadowNode,添加自定義的佈局屬性,在createShadowNodeInstance()中進行初始化,同樣可以被React承認。具體可以參考ReactTextInlineImageShadowNode類的實現,添加ImageSpan的過程。

知識點五:通過繼承LayoutShadowNode,添加自定義的佈局屬性。就想我們在Android中自定義View添加新屬性,需要在XML中註冊相同。

  另外根據源碼可以瞭解到,UIImplement還做了一件大事,首先他先創建了ReactShadowNode,我們稱之爲影子節點,然後通過UIImplemention創建由指定影子節點的相關屬性創建的Native View。如此一個Native View對應一個ReactShadowNode,JavaScript可以控制影子節點屬性,從而改變Native View的佈局和形態。

注:ReactShadowNode,網上稱之爲影子節點,感覺還挺好聽的,JS可以直接控制他的屬性,從而對Native View進行佈局(位置、大小、內容等)。

OK~ ,誰可以控制UIImplementation的調用?


UIManagerModule

  答案是com.facebook.react.uimanager包中的關鍵類,名字叫UIManagerModule。通俗一點說,此類的方法可以被JavaScript調用,也就是可以接收JavaScript的命令。然後再調用UIImplementation去執行具體的操作。
  這是在JavaScript線程(非UI線程)對View進行佈局和測量的一個關鍵類,是JS控制Native View的入口。然後根據我們上面的一起的一步一步的分析,最終實現控制Native View的效果。

看下面一小部分源碼:

//UIManagerModule源碼
@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
  mUIImplementation.createView(tag, className, rootViewTag, props);
}

調用UIImplementation創建Native View


總結

  最後,我們對上文的整個剖析,做個總結,通過下圖的梳理,希望能對我們理解這部分源碼思路有所幫助(此圖良心出品^_^)。

Native UI 源碼梳理

  好的,但是問題又來了,爲什麼UIManagerModule方法能夠被JavaScript調用,答案是方法被@ReactMethod註解 ,但是爲什麼?沒事就問句問什麼^_^,我們下篇文章再詳解分析。

謝謝閱讀,希望能對您理解ReactNative有幫助~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章