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