美文网首页Android开发经验谈
Rn与原生控件那些事

Rn与原生控件那些事

作者: 乐之飞于 | 来源:发表于2018-12-07 19:14 被阅读85次

    最近公司一直在搞关于Rn平台搭建相关的东西,以前自学都是断断续续的入坑,但好在基础都还在!

    从Android的角度开始分析一下反应native的基础组件如何加载,聊下它们与原生控件间的映射关系。(ps:希望能帮助一些朋友)

    安卓端源码浅析

    卖个关子,安卓老司机看页面的实现原理,怎么看的?RN在安卓端的加载开端也是如此!

    基于这个(https://github.com/facebook/react-native/releases/tag/v0.57.5),自己来一套(RN官方教程和RN源码一样,一日三变,习惯就好):

    public class DXYReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
        private ReactRootView mReactRootView;
        private ReactInstanceManager mReactInstanceManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mReactRootView = new ReactRootView(this);
            mReactInstanceManager = ReactInstanceManager.builder()
                    .setApplication(getApplication())
                    .setBundleAssetName("index.android.bundle")
                    .setJSMainModulePath("index")
                    .addPackage(new MainReactPackage())
                    .setUseDeveloperSupport(BuildConfig.DEBUG)
                    .setInitialLifecycleState(LifecycleState.RESUMED)
                    .build();
            mReactRootView.startReactApplication(mReactInstanceManager, “DXYReactNativeApp", null);
    
            setContentView(mReactRootView);
        }
    }
    
    

    从上面的代码中可以看出,承载RN页面显示的也是一个普通的Activity,但setContentView中传入的却的英文一个特定的ReactRootView,加载也就是说全部在这个ReactRootView中完成。ReactInstanceManager类似于一个代理,承接了IO,通信,布局及其他一些逻辑性操作,下文中还会提到。

    public class ReactRootView extends SizeMonitoringFrameLayout
        implements RootView, MeasureSpecProvider {
      ...
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // No-op since UIManagerModule handles actually laying out children.
      }
    }
    
    

    上面的代码省略了大部分与本文无关的代码,但也可以看出ReactRootView只不过是一个很普通的继承自SizeMonitoringFrameLayoutFrameLayout)的控件容器,而且它的onLayout方法是空的,从注释中可以子看出控件的布局在UIManagerModule中实现。

    public class UIManagerModule extends ReactContextBaseJavaModule
        implements OnBatchCompleteListener, LifecycleEventListener, UIManager {
      private final UIImplementation mUIImplementation;
      ...
      @ReactMethod(isBlockingSynchronousMethod = true)
      public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) {
        ...
        // 根据viewManagerName获取ViewManager的映射
        return computeConstantsForViewManager(viewManagerName);
      }
    
      @Override
      public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> int addRootView(
          final T rootView, WritableMap initialProps, @Nullable String initialUITemplate) {
        ...
        // 获取ReactRootView对象的引用,以便于再里面添加View
        mUIImplementation.registerRootView(rootView, tag, themedRootContext);
        ...
      }
      // 该注解的方法都是可以在js代码中调用的
      @ReactMethod
      public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
        if (DEBUG) {
          ...
        }
        // 实现的是reactRootView.addView()
        mUIImplementation.createView(tag, className, rootViewTag, props);
      }
      ...
    }
    
    

    同样,UIManagerModule里面也没有太多东西,它主要是用于暴露方法供JS调用的,实现具体的英文由UIImplementation来完成的被。@ReactMethod注解的方法都可以在JS代码中被调用到,包括:removeRootViewcreateViewmeasuremeasureLayoutmanageChildren等等,可见子控件的添加,测量,布局,删除等操作都是由JS调用UIManagerModule相应的方法后完成。

    public class UIImplementation {
      ...
      public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
        //构建ReactShadowNode
        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);
        ...
      }
      ...
    }
    
    

    就是以上createView的具体实现,它主要做的是构造了一个ReactShadowNode

    再看看createShadowNode

    protected ReactShadowNode createShadowNode(String className) {
      ViewManager viewManager = mViewManagers.get(className);
      return viewManager.createShadowNodeInstance(mReactContext);
    }
    
    

    的英文它通过classNameViewManager。问题来了,ViewManager是什么?看它的源码可知它是一个抽象类,从它的源码很难看出它是干什么用的,但一看继承自它的子类就豁然开朗了,它的子类包括ReactTextInputManagerReactTextViewManagerReactImageManagerSwipeRefreshLayoutManagerReactCheckBoxManagerReactProgressBarViewManagerReactScrollViewManager等等等。从类名上看,这不就是安卓的各种控件吗?查看源码后果然如此。

    ReactTextViewManager为例:

    public class ReactTextViewManager
        extends ReactTextAnchorViewManager<ReactTextView, ReactTextShadowNode> {
        ...
    }
    
    
    public class ReactTextView extends TextView implements ReactCompoundView {
      ...
    }
    
    

    就是它对TextView的封装。由此可见JS代码最终都映射到了原生的控件上。

    大家可以尝试下,一个很简单的RN页面(此处无图),只有一个Text和一个Image,通过AS上的布局检查员可以清晰地看到,最终显示的是封装过的TextViewImageView

    回到再@ReactMethod注解,在它JavaModuleWrapper中,通过再NativeModuleRegistry被放到了一个映射表里面:

    public class JavaModuleWrapper {
      ...
      private void findMethods() {
        ...
        for (Method targetMethod : targetMethods) {
          // 获取@ReactMethod注解
          ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class);
        ...
        }
      }
    }
    
    
    public class NativeModuleRegistry {
      /* package */ Collection<JavaModuleWrapper> getJavaModules(JSInstance jsInstance) {
        ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
        // 生成映射表
        for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
          if (!entry.getValue().isCxxModule()) {
            javaModules.add(new JavaModuleWrapper(jsInstance, entry.getValue()));
          }
        }
        return javaModules;
      }
    }
    
    
    
    public class CatalystInstanceImpl implements CatalystInstance {
      static {
        // jni
        ReactBridge.staticInit();
      }
    
      @Override
      public void extendNativeModules(NativeModuleRegistry modules) {
        mNativeModuleRegistry.registerModules(modules);
        Collection<JavaModuleWrapper> javaModules = modules.getJavaModules(this);
        Collection<ModuleHolder> cxxModules = modules.getCxxModules();
        // 将原生方法的映射表传给jsBridge
        jniExtendNativeModules(javaModules, cxxModules);
      }
    
      // C++的方法
      private native void jniExtendNativeModules(
        Collection<JavaModuleWrapper> javaModules,
        Collection<ModuleHolder> cxxModules);
      ...
    }
    
    

    最后定位到CatalystInstanceImpl,它内部初始化了ReactBridge(jsBridge),也就是说@ReactMethod注解的方法都放到了一个注册表A里面供jsBridge随时调用。

    CatalystInstanceImpl也是在ReactInstanceManager内部实例化的,兜兜转转又回到了开头的ReactInstanceManager,也就是说jsBridge映射到原生控件的逻辑都在它内部实现。

    小结

    安卓端的加载过程大致如下:

    1. jsBridge到映射UIManagerModule中有@ReactMethod的方法上;
    2. UIManagerModule针对中的控件操作由UIImplementation代理,完成控件的添加,测量,布局,删除等操作;
    3. 控件所有名单最终添加到ReactRootView中,最终由它完成总体的加载并显示。

    至此,安卓端相关的逻辑已经差不多了,接下来看看在JS端又是怎么映射的。

    JS端源码浅析

    先来一段上文中提到过的RN页面的代码:

    type Props = {};
    class App extends Component<Props> {
        render() {
            return (
                <View style={styles.container}>
                    <Image
                        style={styles.image}
                        source={require('./img.png')}>
                    </Image>
                    <Text style={styles.welcome}>Welcome to React Native!</Text>
                </View>
            );
        }
    }
    
    export default App;
    
    

    CSS代码目前不是分析重点,所以被我省略了,上面只有JS和,JSX,一种JS的语法糖,所有基础组件都会以JSX形式的置于Componentrender方法中。

    看看接下来Component的英文怎么实现的:

    const Component = class extends RealComponent {
        render() {
          const name = RealComponent.displayName || RealComponent.name;
          return React.createElement(
            name.replace(/^(RCT|RK)/,''),
            this.props,
            this.props.children,
          );
        }
      };
    
    

    最终JSX在会React.createElement方法中被翻译JS分类中翻译代码,有兴趣的童鞋可以查查框架,这里就不多展开了。

    现在回到例子代码中的基础组件,以Text为例,看看它的源码:

    ...
    const RCTVirtualText =
      UIManager.getViewManagerConfig('RCTVirtualText') == null
        ? RCTText
        : createReactNativeComponentClass('RCTVirtualText', () => ({
            validAttributes: {
              ...ReactNativeViewAttributes.UIView,
              isHighlighted: true,
              maxFontSizeMultiplier: true,
            },
            uiViewClassName: 'RCTVirtualText',
          }));
    
    const Text = (
      props: TextProps,
      forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>,
    ) => {
      return <TouchableText {...props} forwardedRef={forwardedRef} />;
    };
    const TextToExport = React.forwardRef(Text);
    TextToExport.displayName = 'Text';
    TextToExport.propTypes = DeprecatedTextPropTypes;
    module.exports = (TextToExport: Class<NativeComponent<TextProps>>);
    
    

    Text的源码不少,对于非专业前端,看起来比较吃力,但也有捷径,从对外暴露点开始找,就是也。从module.exports开始,到TextToExport,再到Text,再到RCTVirtualText,最后定位到了UIManager.getViewManagerConfig

    UIManager.getViewManagerConfig = function(viewManagerName: string) {
      if (
        viewManagerConfigs[viewManagerName] === undefined &&
        UIManager.getConstantsForViewManager
      ) {
        try {
          viewManagerConfigs[
            viewManagerName
          ] = UIManager.getConstantsForViewManager(viewManagerName);
        } catch (e) {
          viewManagerConfigs[viewManagerName] = null;
        }
      }
      ...
    };
    
    

    看到getConstantsForViewManager,是不是觉得很眼熟没错,它就是上一板块的Android源码中提到的?UIManagerModule中的方法,让我们再来回顾一下Java的源码:

      @ReactMethod(isBlockingSynchronousMethod = true)
      public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) {
        ...
        return computeConstantsForViewManager(viewManagerName);
      }
    
      private @Nullable WritableMap computeConstantsForViewManager(final String viewManagerName) {
        ViewManager targetView =
            viewManagerName != null ? mUIImplementation.resolveViewManager(viewManagerName) : null;
        if (targetView == null) {
          return null;
        }
    
        SystraceMessage.beginSection(
                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.getConstantsForViewManager")
            .arg("ViewManager", targetView.getName())
            .arg("Lazy", true)
            .flush();
        try {
          Map<String, Object> viewManagerConstants =
              UIManagerModuleConstantsHelper.createConstantsForViewManager(
                  targetView, null, null, null, mCustomDirectEvents);
          if (viewManagerConstants != null) {
            return Arguments.makeNativeMap(viewManagerConstants);
          }
          return null;
        } finally {
          SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE).flush();
        }
      }
    
    

    这个方法作用就是从缓存中读取ViewManager对象,装入WritableMap后回传给了JS,而WritableMap在JS中以对象的形式存在。

    再回到UIManager,它除了可以调用getConstantsForViewManager,个上提到板块的被@ReactMethod注解的方法诸如removeRootViewcreateViewmeasuremeasureLayout等等在JS中的映射都是由它来调用,也就是说JS原调用生控件的映射都由UIManager来完成。

    一眼再看UIManager的源码:

    const NativeModules = require('NativeModules');
    const {UIManager} = NativeModules;
    ...
    module.exports = UIManager;
    
    

    看来UIManager只不过的英文对NativeModules的二次封装。写过RN的童鞋对此肯定不陌生,写JS和原生通信的相关代码中肯定会用到NativeModules,它是JS和原生代码通信的桥梁。

    至于NativeModules:C ++的交互过程,这里就简单讲一下,NativeModules内部的有一个BatchedBridge(即MessageQueue)的对象:

    class MessageQueue {
      // js注册的回调,供原生代码调用
      _lazyCallableModules: {[key: string]: (void) => Object};
      // js调用原生代码请求的缓存列表
      _queue: [number[], number[], any[], number];
    
      // js调用原生方法的请求
      enqueueNativeCall(
        moduleID: number,
        methodID: number,
        params: any[],
        onFail: ?Function,
        onSucc: ?Function,
      ) {
        ...
        // 把请求打包成一个Message,放入缓存列表
        this._queue[MODULE_IDS].push(moduleID);
        this._queue[METHOD_IDS].push(methodID);
        this._queue[PARAMS].push(params);
        if (
          global.nativeFlushQueueImmediate &&
          (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
            this._inCall === 0)
        ) {
          var queue = this._queue;
          this._queue = [[], [], [], this._callID];
          this._lastFlush = now;
          // 如果是同步请求,则请求的message立即入列,否则等待flushedQueue()的执行
          // 这是一个C++的函数
          global.nativeFlushQueueImmediate(queue);
        }
      }
    
      // 将缓存的请求列表全部入列
      flushedQueue() {
        this.__guard(() => {
          this.__callImmediates();
        });
    
        const queue = this._queue;
        this._queue = [[], [], [], this._callID];
        return queue[0].length ? queue : null;
      }
    
      // 注册回调接口
      registerCallableModule(name: string, module: Object) {
        this._lazyCallableModules[name] = () => module;
      }
      ...
    }
    
    

    它内部保存了JS中对外暴露的方法和模块的映射表供jsBridge调用,如果需要调用原生代码中的方法,MessageQueue会将请求封装成一个消息放入一个请求队列,然后触发原生的方法。看着怎么这么像的Android中的处理程序机制?原因很简单,JS执行的线程是独立于原生代码所在的UI线程的,线程间通信最简单的还是类似处理器这样的方式。

    小结

    RN基础组件映射到原生在JS端的表现大致如下:

    1. JSX形式的RN基础组件首先会被翻译成JS代码;
    2. 组件会在JS中代码调用UIManager相应的方法;
    3. UIManager通过jsBridge到映射原生方法UIManagerModule中;

    C ++源码浅析

    安卓端和JS端都已经介绍完毕了,就像扁担两头的货物都准备完毕了,就差根扁担了,jsBridge就是这根扁担。

    来看先与一下CatalystInstanceImpl.java对应的CatalystInstanceImpl.cpp

    void CatalystInstanceImpl::registerNatives() {
      registerHybrid({
         // jniExtendNativeModules就是CatalystInstanceImpl.java中那个传入原生方法映射表的native方法
         // 它被指向了extendNativeModules方法
         makeNativeMethod("jniExtendNativeModules", CatalystInstanceImpl::extendNativeModules),
         ...
       });
    
       JNativeRunnable::registerNatives();
    }
    
    void CatalystInstanceImpl::extendNativeModules(
        jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
        jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
          // 注册映射表
          moduleRegistry_->registerModules(buildNativeModuleList(
            std::weak_ptr<Instance>(instance_),
            javaModules,
            cxxModules,
            moduleMessageQueue_));
     }
    
    

    可见CatalystInstanceImpl的这部分代码就是用来注册原生方法的映射表的。

    再来看看JS中调用C ++的方法nativeFlushQueueImmediate,代码以下位于JSIExecutor.cpp中:

    runtime_->global().setProperty(
          *runtime_,
          "nativeFlushQueueImmediate",
          Function::createFromHostFunction(
              *runtime_,
              PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
              1,
              [this](
                  jsi::Runtime&,
                  const jsi::Value&,
                  const jsi::Value* args,
                  size_t count) {
                if (count != 1) {
                  throw std::invalid_argument(
                      "nativeFlushQueueImmediate arg count must be 1");
                }
                // 调用已注册的原生模块
                callNativeModules(args[0], false);
                return Value::undefined();
              }));
    
    

    代码以下位于JsToNativeBridge.cpp中,它以委托的形式存在,执行上述代码中的callNativeModules

    void callNativeModules(
          JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
        ...
        for (auto& call : parseMethodCalls(std::move(calls))) {
          // 执行已注册的原生模块中的方法
          m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
        }
        ...
      }
    
    

    最后殊途同归都到了ModuleRegistry.cpp

    // 注册原生模块
    void ModuleRegistry::registerModules(std::vector<std::unique_ptr<NativeModule>> modules) {
      ...
    }
    
    // 执行原生模块的方法
    void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
      ...
      modules_[moduleId]->invoke(methodId, std::move(params), callId);
    }
    
    

    总算一条完整的映射链over。

    总结

    解读了的Android端,JS端和C ++的源码,分析了RN基础组件是如何一步步地映射成为原生控件的整个过程,展示了一条完整地映射。

    最后整理一下整个映射的:


    不喜勿喷.png
    • 文字 - > TextView
    • 图像 - > ImageView
    • TextInput - > EditText
    • CheckBox - > AppCompatCheckBox
    • RefreshControl - > SwipeRefreshLayout
    • ScrollView - > ScrollView
    • 滑块 - > SeekBar
    • Switch - > SwitchCompat

    相关文章

      网友评论

        本文标题:Rn与原生控件那些事

        本文链接:https://www.haomeiwen.com/subject/pyjxhqtx.html