轉載請註明出處:http://blog.csdn.net/u013531824/article/details/54020287,謝謝。
ReactNative作爲使用React開發Native應用的新框架,隨着時間的增加,無論是社區還是個人對她的興趣與日遞增。此文目的是希望和大家一起欣賞一下ReactNative的部分源碼。閱讀源碼好處多多,讓攻城獅更溜的開發ReactNative應用的同時,也能梳理RN項目的設計思路,增加自己的內功修爲,^_^。
好的,就讓我們輕鬆的開始吧。此篇是以Android
平臺源碼分析爲主,分享Native UI的封裝和管理,重點涉及react-native源碼中com.facebook.react.uimanager
包中的相關類。
通過下圖對剖析的源碼部分有個整體的概念,這是從下向上的調用關係。
因爲上層是向我們直接暴露的類,所以我們採用從上向下的分析過程,以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 ,他的定義如下,使用過Facebook
的Fresco
圖片開源項目的開發者應該會很熟悉GenericDraweeView
類,繼承她實現自己的圖片展示邏輯。
public class ReactImageView extends GenericDraweeView {}
通過ReactImageManager
對本地ReactImageView
進行管理。
知識點一:封裝React可以使用的Native UI View,需要創建一個ViewManager進行管理。
可以說這是標準ViewManager
的官方推薦的寫法,繼承SimpleViewManager
重寫getName
和createViewInstance
方法,但是此處我們不禁會問–爲什麼?爲什麼要重寫這兩個方法,在源碼中是什麼用的調用關係,導致了這種結果。
下面看一張ViewManager的繼承關係圖:
上圖可以清晰反饋ReactImageManager
的繼承關係,最終定位到ViewManager
類,同時SimpleViewManager
負責對View
的管理,而對ViewGroup
的封裝需要繼承ViewGroupManager
實現。也許上面問題的答案我們可以在他的超父類ViewManager
中找到答案。
看一下ViewManager的類圖可以給我們什麼信息:
OK~,ViewManager
中定義我們關心的getName
和createViewInstance
抽象方法。而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;
}
此方法完成兩件事:
- 創建本地View對象,通過抽象方法
createViewInstance(reactContext)
完成,所以子類必須實現這個方法,否則View
對象爲空。 - 通過抽象方法
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);
}
}
此方法完成以下幾個工作:
- 做線程判斷,此方法必須在UI線程中調用。
- 通過
ClassName
獲取到對應的ViewManager
; - 創建View實例,對應到我們剖析的主角就是
ReactImageView
,使用的方法就是上文提到的ViewManager
的createView
方法; - 分別存儲
View
和ViewManger
到mTagsToViews
和mTagsToViewManagers
中; - 設置新創建的
View
的Id
,爲什麼要這麼做?
是爲了重用,減少開銷,由於不是通過XML
的形式創建,所以View
並沒有對應的ID
,需要手動去設置,這裏設置的ID
值爲傳遞過來的參數Tag
- 如果所有屬性都初始化(
@ReactPro
註解的方法)完成,做一次回調,通知ViewManager
去做屬性全部初始化成功之後的操作。
最終會調用ViewManager
的updateProperties
函數,目的是更新屬性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
通過兩個主要類控制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
那麼我的問題又來了,誰調用的NativeViewHierarchyManager
的createView
方法吶?傳遞的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
方法,也就是調用NativeViewHierarchyManager
的createView
方法創建新的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
的隊列中添加元素,這裏引入了兩個關鍵類ReactShadowNode
和ViewProps
,先來說一下ViewProps
的Java
類,其定義了很多屬性名的常量。
//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
創建名稱爲cssNode
的ReactShadowNode
對象,上文使用的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
。
總結
最後,我們對上文的整個剖析,做個總結,通過下圖的梳理,希望能對我們理解這部分源碼思路有所幫助(此圖良心出品^_^)。
好的,但是問題又來了,爲什麼UIManagerModule方法能夠被JavaScript調用,答案是方法被@ReactMethod
註解 ,但是爲什麼?沒事就問句問什麼^_^,我們下篇文章再詳解分析。
謝謝閱讀,希望能對您理解ReactNative有幫助~~~