1、React Native 源码分析(一)—— 启动流程 2、React Native 源码分析(二)—— 通信机制 3、React Native 源码分析(三)—— Native View创建流程 4、React Native 源码分析(四)—— 任务调度 5、React Native 源码分析(五)—— 事件分发
前两篇分析了,React Native 的启动和 通信机制,这篇来分析一下,在收到React 的通信数据字符串后,是如何在原生创建对应的View,React标签设置的View的属性,如何被原生View解析,为什么在自定义View调用requestLayout没有作用呢?
熟悉React 源码的小伙伴们应该知道,React对Component 最终解析为字符串,来提供给ReactNative 使用。在React 中是调用 UIManagerModule中提供的@ReactMethod 一系列方法,来实现在Android创建View。下面先来看下ReactNative中UI相关的类图、各类的功能。然后我们从Android接收到这些字符串开始分析源码。
图一(来源)
图二(来源)
在开始分析源码前,先来看下,从React 传递过来的数据是什么样的,如下图,在桥通信中,是根据methodId找到对应的mMethod(这里是UIManager.createView),后续根据 [3,"RCTRawText",31,{"text":"Press onChange"}] 来创建View
下面我们就从上图断点堆栈中的UIManagerModule 开始分析
源码分析
1、UIManagerModule# createView
@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
mUIImplementation.createView(tag, className, rootViewTag, props);
}
UIManagerModule 中函数的具体实现都是交给UIImplementation处理的,下面来看看在UIImplementation中处理
2、UIImplementation# createView
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
if (!mViewOperationsEnabled) {
return;
}
synchronized (uiImplementationThreadLock) {
ReactShadowNode cssNode = createShadowNode(className);
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
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);
}
}
下面介绍一下 该段代码的重要点,:
ReactShadowNode
ReactShadowNode 代表一个React nodes,可以理解为Android层对React层view的表示,它存在的主要目的是 :
- 为了View的measure、layout。ReactNative的中原生View的measure、layout 是由Yoga这个库来实现的,而不是使用原生的measure、layout 。
- 可以自定义属性,在React node中使用,例如 <Text/> 对应的ShadowNode,@ReactProp 定义了text属性,如下:
public class ReactRawTextShadowNode extends ReactShadowNodeImpl {
@VisibleForTesting public static final String PROP_TEXT = "text";
private @Nullable String mText = null;
public ReactRawTextShadowNode() {}
@ReactProp(name = PROP_TEXT)
public void setText(@Nullable String text) {
mText = text;
markUpdated();
}
public @Nullable String getText() {
return mText;
}
@Override
public boolean isVirtual() {
return true;
}
@Override
public String toString() {
return getViewClass() + " [text: " + mText + "]";
}
}
ReactShadowNode、 YogaNode(java)、YogaNode(C++)是一一对应的,ReactNative 对View的测量、布局,是在Yoga框架中进行的,后面calculateRootLayout函数中会介绍
图片来自
ReactTag
ReactTag是在React中创建的,单数表示 默认UI框架 ,双数表示Fabric框架的node。两种ReactNative架构的对比,可以参考React Native 源码分析(一)—— 启动流程 在ReactFabric-dev.js 中的nextReactTag = 2
在点击事件中,也会用到ReactTag 用于区分新旧UI框架,如下图:
React Native 0.64.2 针对异常Unable to find JSIModule for class UIManager,怎么定位这个错误,顺着点击事件去找,在上面这个红框的位置断点,等待错误uiManagerType的出现,为什么会错误,是因为自定义view 中子view的tag,没有按照ReactTag的分配规则
cssNode.updateProperties(styles);
ReactShadowNodeImpl 中的 updateProperties ,会调用到ViewManagerPropertyUpdater.updateProps ,如下
public static <T extends ReactShadowNode> void updateProps(T node, ReactStylesDiffMap props) {
ShadowNodeSetter<T> setter = findNodeSetter(node.getClass());
Iterator<Map.Entry<String, Object>> iterator = props.mBackingMap.getEntryIterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
setter.setProperty(node, entry.getKey(), entry.getValue());
}
}
上面的实现比较抽象,具体ShadowNodeSetter的实现是什么,来看下面的代码
private static <T extends ReactShadowNode> ShadowNodeSetter<T> findNodeSetter(
Class<? extends ReactShadowNode> nodeClass) {
@SuppressWarnings("unchecked")
ShadowNodeSetter<T> setter = (ShadowNodeSetter<T>) SHADOW_NODE_SETTER_MAP.get(nodeClass);
if (setter == null) {
setter = findGeneratedSetter(nodeClass);
if (setter == null) {
setter = new FallbackShadowNodeSetter<>(nodeClass);
}
SHADOW_NODE_SETTER_MAP.put(nodeClass, setter);
}
return setter;
}
如果没有提供PropsSetter,那么都会使用默认的FallbackShadowNodeSetter,下面来看看
private FallbackShadowNodeSetter(Class<? extends ReactShadowNode> shadowNodeClass) {
mPropSetters =
ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(shadowNodeClass);
}
@Override
public void setProperty(ReactShadowNode node, String name, Object value) {
ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
if (setter != null) {
setter.updateShadowNodeProp(node, value);
}
}
到此ReactShadowNode 就已经创建、赋值完成了,继续往下分析handleCreateView
3、UIImplementation# handleCreateView
protected void handleCreateView(
ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) {
if (!cssNode.isVirtual()) {
mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
}
}
通过图一,可以清楚看到,UIImplementation中的函数,都交给了NativeViewHierarchyOptimizer,对View 的层级进行优化
4、NativeViewHierarchyOptimizer# handleCreateView
public void handleCreateView(
ReactShadowNode node,
ThemedReactContext themedContext,
@Nullable ReactStylesDiffMap initialProps) {
boolean isLayoutOnly =
node.getViewClass().equals(com.facebook.react.uimanager.ViewProps.VIEW_CLASS_NAME)
&& isLayoutOnlyAndCollapsable(initialProps);
node.setIsLayoutOnly(isLayoutOnly);
if (node.getNativeKind() != NativeKind.NONE) {
mUIViewOperationQueue.enqueueCreateView(
themedContext, node.getReactTag(), node.getViewClass(), initialProps);
}
}
5、UIViewOperationQueue# enqueueCreateView
public void enqueueCreateView(
ThemedReactContext themedContext,
int viewReactTag,
String viewClassName,
@Nullable ReactStylesDiffMap initialProps) {
synchronized (mNonBatchedOperationsLock) {
mCreateViewCount++;
mNonBatchedOperations.addLast(
new CreateViewOperation(themedContext, viewReactTag, viewClassName, initialProps));
}
}
代码5.2
private final class CreateViewOperation extends ViewOperation {
private final ThemedReactContext mThemedContext;
private final String mClassName;
private final @Nullable ReactStylesDiffMap mInitialProps;
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);
}
}
创建view的操作,被添加到mNonBatchedOperations后,在js->java 一批通信结束后,会调用这些操作,下面来看看
6、onBatchComplete
通过前两篇文章,我们知道,js->java 的回调是 CatalystInstanceImpl 中的静态内部类BridgeCallback,onBatchComplete调用过程如下
BridgeCallback#onBatchComplete -> NativeModuleRegistry#onBatchComplete -> UIManagerModule#onBatchComplete
UIManagerModule#onBatchComplete
为了省篇幅,就不贴代码了,执行 UIManagerModule中的onBatchComplete ,如果设置listener,就调用。最后调用到mUIImplementation.dispatchViewUpdates(batchId);
UIImplementation#onBatchComplete
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);
}
}
7、UIImplementation #updateViewHierarchy
protected void updateViewHierarchy() {
try {
for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
int tag = mShadowNodeRegistry.getRootTag(i);
ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
if (cssRoot.getWidthMeasureSpec() != null && cssRoot.getHeightMeasureSpec() != null) {
try {
notifyOnBeforeLayoutRecursive(cssRoot);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
calculateRootLayout(cssRoot);
try {
applyUpdatesRecursive(cssRoot, 0f, 0f);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
...
}
}
} ...
}
下面分析一下其中的 calculateRootLayout和applyUpdatesRecursive
calculateRootLayout
calculateRootLayout(cssRoot);的调用过程如下:
ReactShadowNode 和 YogaNode是一一对应的 ReactShadowNode的实现是ReactShadowNodeImpl,YogaNode的实现有YogaNodeJNIBase
代码7.2
calculateRootLayout(cssRoot);
-> ReactShadowNode# calculateLayout
-> YogaNodeJNIBase# calculateLayout(width, height);
->YogaNative.jni_YGNodeCalculateLayoutJNI(mNativePointer, width, height, nativePointers, nodes);
->jni_YGNodeCalculateLayoutJNI
->YGNodeCalculateLayoutWithContext
->YGLayoutNodeInternal
->YGNodelayoutImpl
可以通过ReactShadowNode#setMeasureFunction() 来向YogaNode设置一个测量函数,在Yoga框架中 对UI进行计算时,就可以调用到我们自定义的Measure函数
举个例子: ReactTextViewManager 中创建
public ReactTextShadowNode createShadowNodeInstance() {
return new ReactTextShadowNode();
}
ReactTextShadowNode 的是继承ReactBaseTextShadowNode 构造函数会调用initMeasureFunction->setMeasureFunction,后者的实现是在ReactBaseTextShadowNode
@Override
public void setMeasureFunction(YogaMeasureFunction measureFunction) {
mYogaNode.setMeasureFunction(measureFunction);
}
mYogaNode对应的类是 YogaNodeJNIBase
public void setMeasureFunction(YogaMeasureFunction measureFunction) {
mMeasureFunction = measureFunction;
YogaNative.jni_YGNodeSetHasMeasureFuncJNI(mNativePointer, measureFunction != null);
}
jni_YGNodeSetHasMeasureFuncJNI 调用到C++ src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp
static void jni_YGNodeSetHasMeasureFuncJNI(
JNIEnv* env,
jobject obj,
jlong nativePointer,
jboolean hasMeasureFunc) {
_jlong2YGNodeRef(nativePointer)
->setMeasureFunc(hasMeasureFunc ? YGJNIMeasureFunc : nullptr);
}
在YGNode.cpp 中,是把函数YGJNIMeasureFunc 设置到了measure_ 的YGMeasureFunc类型变量 noContext 中了,YGJNIMeasureFunc在下面会介绍到。
接着代码7.2 看看,在YGNodelayoutImpl 算法中,是如何调用到设置的measureFunction
->YGNodelayoutImpl
-> if (node->hasMeasureFunc()) {
YGNodeWithMeasureFuncSetMeasuredDimensions
}
-> const YGSize measuredSize = node->measure(
innerWidth,
widthMeasureMode,
innerHeight,
heightMeasureMode,
layoutContext);
-> YGSize YGNode::measure(
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode,
void* layoutContext) {
return facebook::yoga::detail::getBooleanData(flags, measureUsesContext_)
? measure_.withContext(
this, width, widthMode, height, heightMode, layoutContext)
: measure_.noContext(this, width, widthMode, height, heightMode);
}
-> static const jmethodID methodId = facebook::yoga::vanillajni::getMethodId(
env, objectClass.get(), "measure", "(FIFI)J");
下图7.1
下图7.2
@DoNotStrip
public final long measure(float width, int widthMode, float height, int heightMode) {
if (!isMeasureDefined()) {
throw new RuntimeException("Measure function isn't defined!");
}
return mMeasureFunction.measure(
this,
width,
YogaMeasureMode.fromInt(widthMode),
height,
YogaMeasureMode.fromInt(heightMode));
}
最终每个YogaNode 测量、布局完的结果,会保存到YogaNodeJNIBase的 arr 数组中
applyUpdatesRecursive(cssRoot, 0f, 0f);的调用过程如下:
UIImplementation#applyUpdatesRecursive()
-> ReactShadowNodeImpl#.dispatchUpdates()
-> UIViewOperationQueue#.enqueueUpdateLayout()
-> ReactShadowNodeImpl#.markUpdateSeen()
上面代码enqueueUpdateLayout,添加任务UpdateLayoutOperation 到 UIViewOperationQueue 的 mOperations中
代码7.10
public UpdateLayoutOperation(int parentTag, int tag, int x, int y, int width, int height) {
super(tag);
mParentTag = parentTag;
mX = x;
mY = y;
mWidth = width;
mHeight = height;
Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
}
@Override
public void execute() {
Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);
}
UpdateLayoutOperation任务的执行,在运行任务时,再进行分析。下面继续代码6分析 mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);
8、UIViewOperationQueue # dispatchViewUpdates
public void dispatchViewUpdates(
final int batchId, final long commitStartTime, final long layoutTime) {
...省略部分代码...
try {
final long dispatchViewUpdatesTime = SystemClock.uptimeMillis();
final long nativeModulesThreadCpuTime = SystemClock.currentThreadTimeMillis();
final ArrayList<DispatchCommandViewOperation> viewCommandOperations;
if (!mViewCommandOperations.isEmpty()) {
viewCommandOperations = mViewCommandOperations;
mViewCommandOperations = new ArrayList<>();
} else {
viewCommandOperations = null;
}
final ArrayList<UIOperation> batchedOperations;
if (!mOperations.isEmpty()) {
batchedOperations = mOperations;
mOperations = new ArrayList<>();
} else {
batchedOperations = null;
}
final ArrayDeque<UIOperation> nonBatchedOperations;
synchronized (mNonBatchedOperationsLock) {
if (!mNonBatchedOperations.isEmpty()) {
nonBatchedOperations = mNonBatchedOperations;
mNonBatchedOperations = new ArrayDeque<>();
} else {
nonBatchedOperations = null;
}
}
if (mViewHierarchyUpdateDebugListener != null) {
mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued();
}
Runnable runOperations =
new Runnable() {
@Override
public void run() {
...省略部分代码...
try {
long runStartTime = SystemClock.uptimeMillis();
if (viewCommandOperations != null) {
for (DispatchCommandViewOperation op : viewCommandOperations) {
try {
op.executeWithExceptions();
} catch (RetryableMountingLayerException e) {
...省略部分代码...
}
}
}
if (nonBatchedOperations != null) {
for (UIOperation op : nonBatchedOperations) {
op.execute();
}
}
if (batchedOperations != null) {
for (UIOperation op : batchedOperations) {
op.execute();
}
}
...省略部分代码...
mNativeViewHierarchyManager.clearLayoutAnimation();
if (mViewHierarchyUpdateDebugListener != null) {
mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished();
}
} catch (Exception e) {
mIsInIllegalUIState = true;
throw e;
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
};
...省略部分代码...
synchronized (mDispatchRunnablesLock) {
mDispatchUIRunnables.add(runOperations);
}
if (!mIsDispatchUIFrameCallbackEnqueued) {
UiThreadUtil.runOnUiThread(
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
flushPendingBatches();
}
});
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
- viewCommandOperations 执行 JS层View 调用的函数(命令),最终会调用到Java层的ViewManager中receiveCommand函数中
- nonBatchedOperations 只有创建View 的任务CreateViewOperation,会被添加到这个队列中
- batchedOperations 除了创建View的任务,例如 更新布局UpdateLayoutOperation 等等。
这里之所以要把创建View,和其他View操作的任务 分开,是因为对View操作的任务,需要先创建出View后,才能进行。所以nonBatchedOperations必须要先于batchedOperations执行
接下来,我们来看加入的这些任务,是在什么时候被调用的。这部分代码比较重要,源码中自带的UI JS 帧率监测,也是在这个时机
9、DispatchUIFrameCallback
DispatchUIFrameCallback继承了Choreographer,看到后者大家应该清楚了,是在每一帧的回调执行任务,没错。回调注册和删除是在Activity的resume和pause时
void resumeFrameCallback() {
mIsDispatchUIFrameCallbackEnqueued = true;
ReactChoreographer.getInstance()
.postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
}
void pauseFrameCallback() {
mIsDispatchUIFrameCallbackEnqueued = false;
ReactChoreographer.getInstance()
.removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
flushPendingBatches();
}
Choreographer的重要回调doFrame,最后会调用DispatchUIFrameCallback 的doFrameGuarded
@Override
public void doFrameGuarded(long frameTimeNanos) {
...
try {
dispatchPendingNonBatchedOperations(frameTimeNanos);
} ...
flushPendingBatches();
ReactChoreographer.getInstance()
.postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this);
}
下面接代码5.2,来看一下任务CreateViewOperation的执行mNativeViewHierarchyManager.createView(mThemedContext, mTag, mClassName, mInitialProps)
10、NativeViewHierarchyManager # CreateViewOperation
根据ViewManager,来创建View。一般我们自定义View给JS层使用,是需要自定义ViewManager
public synchronized void createView(
ThemedReactContext themedContext,
int tag,
String className,
@Nullable ReactStylesDiffMap initialProps) {
UiThreadUtil.assertOnUiThread();
try {
ViewManager viewManager = mViewManagers.get(className);
View view =
viewManager.createView(tag, themedContext, initialProps, null, mJSResponderHandler);
mTagsToViews.put(tag, view);
mTagsToViewManagers.put(tag, viewManager);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
mViewManagers 中的数据,是在React Native初始化的时候,进行收集的(可参考文章React Native 源码分析(一)—— 启动流程),通过UIManagerModule来设置到NativeViewHierarchyManager的ViewManagerRegistry类型的mViewManagers变量中
大部分自定义ViewManager都是继承SimpleViewManager、ViewGroupManager
介绍一些自定义ViewManager的一些有用方法,最基本的就不介绍了:
- onDropViewInstance 删除view时,会触发该方法,可以进行一些清理工作
- onAfterUpdateTransaction 在属性都初始化完成之后,会调用该方法
- receiveCommand 可以在js层通过View直接调用该命令,而不用 通过ReactContextBaseJavaModule
到此,从React到Android, View的创建过程就分析完了。通过上面分析知道,View的measure和layout,是通过Yoga进行的,那么layout的结果,该如何使用呢?下面接代码代码7.10,来看一下任务UpdateLayoutOperation的执行mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);
11、NativeViewHierarchyManager # UpdateLayoutOperation
public synchronized void updateLayout(
int parentTag, int tag, int x, int y, int width, int height) {
...省略部分代码...
try {
View viewToUpdate = resolveView(tag);
...省略部分代码...
if (!mRootTags.get(parentTag)) {
ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
IViewManagerWithChildren parentViewManagerWithChildren;
if (parentViewManager instanceof IViewManagerWithChildren) {
parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager;
} else {
throw new IllegalViewOperationException(
"Trying to use view with tag "
+ parentTag
+ " as a parent, but its Manager doesn't implement IViewManagerWithChildren");
}
if (parentViewManagerWithChildren != null
&& !parentViewManagerWithChildren.needsCustomLayoutForChildren()) {
updateLayout(viewToUpdate, x, y, width, height);
}
} else {
updateLayout(viewToUpdate, x, y, width, height);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
最后看一下updateLayout
代码11.2
private void updateLayout(View viewToUpdate, int x, int y, int width, int height) {
if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) {
mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height);
} else {
viewToUpdate.layout(x, y, x + width, y + height);
}
}
主要的流程就全部分析完了,那看到这里,我们来思考个问题:
React 中的View,它们的UI style 使用的是css那一套。那么ReactNative 使用Yoga在YGNodelayoutImpl 实现对UI 、布局的解析,可以得到每个View的宽高和位置,也就是代替了在android中的measure、layout功能 ,最终的结果也是需要告知到Android体系中,也就是代码11.2 调用了layout。 正常情况下在android中的ViewGroup 的 layout 会调用所有嵌套View的layout,那岂不是和ReactNative 使用Yoga 的出现矛盾了?(不用担心View的layout,因为代码11.2 调用 View的layout,只是设置自身的位置,不会调用到其他View的layout)
在React中所有的node都会被包裹在<View>中 ,<View>对应Android的类是ReactViewGroup,来看下它的部分代码:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
public void requestLayout() {
}
可是看到,重写了onLayout和requestLayout,但都是空实现。onLayout 没有必要 也不能实现,因为子View的位置都已经在Yoga 中确定了。下面的子View调用requestLayout,最终也会在这里被打断,因为measure、layout都在Yoga中计算了,requestLayout 也就形同虚设了。
到此Android层面的UI流程就大概分析完了,虽然不是逐行代码分析,但比网上大大部分,要详细很多。欢迎小伙伴们 点赞、评论 支持一下,U·ェ·U
|