React-Native-源码分析二-JSX如何渲染成原生页面(上)

前端之家收集整理的这篇文章主要介绍了React-Native-源码分析二-JSX如何渲染成原生页面(上)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本文跳过了React-Native 的通讯过程,详细请参考大头鬼写的Java和JS的通讯原理,虽然0.33版本加入了懒加载,原来配置表生成的时机和方式发生了改变,但是原理还是没有改变:通过约定的JSON,解析出moduleName,function name,然后通过本地找到对应的模块中的方法,然后通过反射执行这些方法,实现调用

这篇将从Android原生反推JSX如何最终变化为原生控件的过程。

博主使用的环境是(版本很重要,RN发展飞快,不同的版本之间可能有差别)

“react”: “15.3.1”,

“react-native”: “^0.33.0”,

React-Native 源码分析一-如何启动JS页面的最后一步,我们看到XReactInstanceManagerImpl.java的attachMeasuredRootViewToInstance方法中有设置View的逻辑

  1. private void attachMeasuredRootViewToInstance(
  2. ...
  3. UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
  4. int rootTag = uiManagerModule.addMeasuredRootView(rootView);
  5. rootView.setRootViewTag(rootTag);
  6. ...
  7. }

可以看到uiManagerModule.addMeasuredRootView(rootView)这个方法好像很厉害的样子,进去看看

  1. public int addMeasuredRootView(final SizeMonitoringFrameLayout rootView) {
  2. //去掉了宽高赋值的代码
  3.  
  4. mUIImplementation.registerRootView(rootView,tag,width,height,themedRootContext);
  5. //忽略了setOnSizeChangedListener
  6.  
  7. return tag;
  8. }

去掉无关代码之后,可以看到 mUIImplementation.registerRootView(rootView,themedRootContext)方法传递了view和相关宽,高,theme信息进去,进去看代码发现利用这些数据构造了一个ReactShadowNode,然后add到了mOperationsQueue中,一看到Queue立马想到肯定有个UI相关的轮循在处理UI绘制事务。

  1. public void registerRootView(
  2. SizeMonitoringFrameLayout rootView,int tag,int width,int height,ThemedReactContext context) {
  3. final ReactShadowNode rootCSSNode = createRootShadowNode();
  4. rootCSSNode.setReactTag(tag);
  5. rootCSSNode.setThemedContext(context);
  6. rootCSSNode.setStyleWidth(width);
  7. rootCSSNode.setStyleHeight(height);
  8.  
  9. mShadowNodeRegistry.addRootNode(rootCSSNode);
  10.  
  11. // register it within NativeViewHierarchyManager
  12. mOperationsQueue.addRootView(tag,rootView,context);
  13. }

所以我们先放下这里,回到上一个方法addMeasuredRootView的注释

  1. /** * Registers a new root view. JS can use the returned tag with manageChildren to add/remove * children to this view. * * Note that this must be called after getWidth()/getHeight() actually return something. See * CatalystApplicationFragment as an example. * * TODO(6242243): Make addMeasuredRootView thread safe * NB: this method is horribly not-thread-safe. */

js能根据tag,使用manageChildren 来添加删除 rootview中的子view

那么可以猜想manageChildren 可能是js直接控制原生代码增删布局的入口,来看下

  1. @ReactMethod
  2. public void manageChildren(
  3. int viewTag,@Nullable ReadableArray moveFrom,@Nullable ReadableArray moveTo,@Nullable ReadableArray addChildTags,@Nullable ReadableArray addAtIndices,@Nullable ReadableArray removeFrom) {
  4.  
  5. mUIImplementation.manageChildren(
  6. viewTag,moveFrom,moveTo,addChildTags,addAtIndices,removeFrom);
  7. }

果然这是个用ReactMethod注解过的方法,代表这他要被JS直接调用,从注释:Interface for adding/removing/moving views within a parent view from JS也能知道js通过这个方法增删改view,同样有@ReactMethod注解的类还有:createView,removeRootView,updateView,setChildren,replaceExistingNonRootView,removeSubviewsFromContainerWithID,measure,measureInWindow。。。等等方法,随便找了一个方法看一下,比如createView

  1. @ReactMethod
  2. public void createView(int tag,String className,int rootViewTag,ReadableMap props) {
  3. if (DEBUG) {
  4. FLog.d(
  5. ReactConstants.TAG,"(UIManager.createView) tag: " + tag + ",class: " + className + ",props: " + props);
  6. }
  7. mUIImplementation.createView(tag,className,rootViewTag,props);
  8. }

继续进mUIImplementation.createView,

  1. public void createView(int tag,ReadableMap props) {
  2. ReactShadowNode cssNode = createShadowNode(className);
  3. ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
  4. cssNode.setReactTag(tag);
  5. cssNode.setViewClassName(className);
  6. cssNode.setRootNode(rootNode);
  7. cssNode.setThemedContext(rootNode.getThemedContext());
  8.  
  9. mShadowNodeRegistry.addNode(cssNode);
  10.  
  11. ReactStylesDiffMap styles = null;
  12. if (props != null) {
  13. styles = new ReactStylesDiffMap(props);
  14. cssNode.updateProperties(styles);
  15. }
  16.  
  17. handleCreateView(cssNode,styles);
  18. }

构造一个ReactShadowNode,其中createShadowNode 是通过className 找到之前注册的ViewManager比如ReactTextInputManager,再设置他的rootNode,最后handleCreateView

  1. protected void handleCreateView(
  2. ReactShadowNode cssNode,@Nullable ReactStylesDiffMap styles) {
  3. if (!cssNode.isVirtual()) {
  4. mNativeViewHierarchyOptimizer.handleCreateView(cssNode,cssNode.getThemedContext(),styles);
  5. }
  6. }
  7.  
  8. public void handleCreateView(
  9. ReactShadowNode node,ThemedReactContext themedContext,@Nullable ReactStylesDiffMap initialProps) {
  10. if (!ENABLED) {
  11. int tag = node.getReactTag();
  12. mUIViewOperationQueue.enqueueCreateView(
  13. themedContext,node.getViewClass(),initialProps);
  14. return;
  15. }
  16. }
  17.  
  18. public void enqueueCreateView(
  19. ThemedReactContext themedContext,int viewReactTag,String viewClassName,@Nullable ReactStylesDiffMap initialProps) {
  20. synchronized (mNonBatchedOperationsLock) {
  21. mNonBatchedOperations.addLast(
  22. new CreateViewOperation(
  23. themedContext,viewReactTag,viewClassName,initialProps));
  24. }
  25. }

这样一路跟下去,我们会发现,如果要createView的一个View,最后只是在ArrayDeque mNonBatchedOperations中add了一个CreateViewOperation(),很敏感的会发现UIOperation 是抽象的接口

  1. public interface UIOperation {
  2. void execute();
  3. }

果然只有一个接口execute,那自然的还有很多实现了UIOperation的类比如:RemoveRootViewOperation,ChangeJSResponderOperation,ShowPopupMenuOperation等等,
之前我们好像隐约的感觉到有个UI轮询在不停的执行这些UIOperation,也就是业务方只需要往池子里面添加就行,这样的队列在Android很多系统中都有遇到,比如Handle还有EventBus,有兴趣的读者可以看一下我之前的一个总结
这个类的名字com/facebook/react/uimanager/UIViewOperationQueue.java 所以大胆的在里面找轮训的代码,很快我们发现了dispatchViewUpdates方法

  1. void dispatchViewUpdates(final int batchId) {
  2. ...
  3. if (nonBatchedOperations != null) {
  4. for (UIOperation op : nonBatchedOperations) {
  5. op.execute();
  6. }
  7. }
  8.  
  9. ...
  10. });
  11. }

在一个线程数组中添加了一个线程,专门for循环调用各自的execute()方法,这里举个例子CreateViewOperation

  1. private final class CreateViewOperation extends ViewOperation {
  2.  
  3. private final ThemedReactContext mThemedContext;
  4. private final String mClassName;
  5. private final @Nullable ReactStylesDiffMap mInitialProps;
  6.  
  7. public CreateViewOperation(
  8. ThemedReactContext themedContext,@Nullable ReactStylesDiffMap initialProps) {
  9. super(tag);
  10. mThemedContext = themedContext;
  11. mClassName = className;
  12. mInitialProps = initialProps;
  13. Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW,"createView",mTag);
  14. }
  15.  
  16. @Override
  17. public void execute() {
  18. Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW,mTag);
  19. mNativeViewHierarchyManager.createView(
  20. mThemedContext,mTag,mClassName,mInitialProps);
  21. }
  22. }

执行execute方法也就是 执行mNativeViewHierarchyManager.createView

  1. public void createView(
  2. ThemedReactContext themedContext,int tag,@Nullable ReactStylesDiffMap initialProps) {
  3. ...
  4. try {
  5. ViewManager viewManager = mViewManagers.get(className);
  6.  
  7. View view = viewManager.createView(themedContext,mJSResponderHandler);
  8. mTagsToViews.put(tag,view);
  9. mTagsToViewManagers.put(tag,viewManager);
  10.  
  11. view.setId(tag);
  12. if (initialProps != null) {
  13. viewManager.updateProperties(view,initialProps);
  14. }
  15. } finally {
  16. Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
  17. }
  18. }

这里的mViewManagers.get(className) 是根据className找到之前MainReactPackage里面添加的各种ViewManagers,然后调用ViewManager的createView方法,因为ViewManager是父类,他的createView里调用抽象方法createViewInstance,看下面代码

  1. public final T createView(
  2. ThemedReactContext reactContext,JSResponderHandler jsResponderHandler) {
  3. T view = createViewInstance(reactContext);
  4. addEventEmitters(reactContext,view);
  5. if (view instanceof ReactInterceptingViewGroup) {
  6. ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
  7. }
  8. return view;
  9. }
  10.  
  11. protected abstract T createViewInstance(ThemedReactContext reactContext);

createViewInstance 抽象方法是每个子类必须要实现的方法,也是正在构造View的方法,还是举个例子:ReactTextInputManager

  1. public class ReactTextInputManager extends BaseViewManager<ReactEditText,LayoutShadowNode> {
  2.  
  3. /* package */ static final String REACT_CLASS = "AndroidTextInput";
  4.  
  5.  
  6. @Override
  7. public String getName() {
  8. return REACT_CLASS;
  9. }
  10.  
  11. @Override
  12. public ReactEditText createViewInstance(ThemedReactContext context) {
  13. ReactEditText editText = new ReactEditText(context);
  14. int inputType = editText.getInputType();
  15. editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
  16. editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
  17. editText.setTextSize(
  18. TypedValue.COMPLEX_UNIT_PX,(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)));
  19. return editText;
  20. }
  21. }

他的createViewInstance方法就是new ReactEditText(context),到这里一个View已经创建完成,那么他的属性在哪里设置?放心JS已经将生成一个View要的数据都带了回来,initialProps中就是jsx中的style,viewManager.updateProperties(view,initialProps);再下面就是解析,设置属性,然后在在rootView中测量大小,确定位置,原生的UI渲染就完成了,期间细节太繁琐,不容易都写出来,只是描述一个流程,如果真正了解绘制细节的,还有好几个重要的类需要慢慢解析,请需求的同学自行解读。

前文中这次会反推JSX如何最终变化为原生控件的过程,上面这部分算是原生的绘制已经结束,下面开始到JS代码中找,JSX布局如何传达到原生的。

猜你在找的React相关文章