美文网首页
RN安卓实现分析之ReactRootView的实现过程

RN安卓实现分析之ReactRootView的实现过程

作者: 王亟亟 | 来源:发表于2018-10-13 19:19 被阅读88次

    转载请注明出处:王亟亟的大牛之路

    开篇之前日常安利
    https://github.com/ddwhan0123/Useful-Open-Source-Android (各种库的收纳,长期维护)

    上一篇提到了入口类ReactActivity和他的代理实现类ReactActivityDelegate,这一次继续我们的分析之路

    写着一片之前,没有看过任何其他兄弟对相关内容的分析,不是觉得自己牛逼。
    是怕别人的思维影响到我的理解,如果讲得不对,欢迎指出!

    上一篇的传送门RN安卓实现分析之ReactActivity的前世今生


    ReactRootView

    这是一个被ReactActivity.setContentView(mReactRootView)的UI控件,我们先来看下他的实现

    public class ReactRootView extends SizeMonitoringFrameLayout
        implements RootView, MeasureSpecProvider {...}
    
    • SizeMonitoringFrameLayout 它是一个继承FrameLayout的一个ViewGroup,实现没什么复杂的,主要是可以监听尺寸的变化,由OnSizeChangedListener这个接口对外暴露内容。OnSizeChangedListener可以回传4个属性,分别是 新的宽高和旧的宽高。
    • RootView 它是一个接口,子控件手势回传时实现,通过onChildStartedNativeGesture方法传递一个MotionEvent对象
    • MeasureSpecProvider 它是一个接口,getWidthMeasureSpec() getHeightMeasureSpec()两个方法用来计算重新计算根视图的长和宽的值

    既然是一个为了计算尺寸而自定义的的Layout那么一定会有onMeasure(),onLayout(),等方法

    [图片上传失败...(image-3543be-1539429545128)]
    首先获取了Mode类型,判断如果是MeasureSpec.AT_MOST或者MeasureSpec.UNSPECIFIED就对子控件进行循环计算复制给width变量,如果不是的话直接调用MeasureSpec.getSize()方法进行赋值。

    MeasureSpec有三种模式:
    UNSPECIFIED: 父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
    EXACTLY: 父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
    AT_MOST: 子元素至多达到指定大小的值。
    MeasureSpec.getSize(measureSpec): 根据提供的测量值提取大小值(尺寸级别的数值变化)

    高度同上,我们就得到了 2个具体的宽高值。然后调用setMeasuredDimension(width, height);,设置当前View的大小。

    [图片上传失败...(image-9f6f59-1539429545128)]

    计算完把ReactRootView的类变量mWasMeasured设置为true,表示控件已经计算过了!

    经过判断决定是刷新位置信息还是构建ReactInstanceManager实例
    在mReactInstanceManager为null时,enableLayoutCalculation()方法直接返回,否则会对当前mReactInstanceManager对象的ReactContext进行一轮设置。

    那么mReactInstanceManager又是在哪初始化的呢?
    [图片上传失败...(image-1a33ab-1539429545128)]
    这个就是我们在ReactActivity调用的那个方法,传入的是

     private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this)
    

    所携带的mReactNativeHost里的mReactInstanceManager。这个对象一定不会为空因为,该对象为空的话他会创建个新的!

      /**
       * Get the current {@link ReactInstanceManager} instance, or create one.
       */
      public ReactInstanceManager getReactInstanceManager() {
        if (mReactInstanceManager == null) {
          mReactInstanceManager = createReactInstanceManager();
        }
        return mReactInstanceManager;
      }
    

    所以startReactApplication方法后执行的方法为其内部的attachToReactInstanceManager();


     private void attachToReactInstanceManager() {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachToReactInstanceManager");
        try {
          if (mIsAttachedToInstance) {
            return;
          }
    
          mIsAttachedToInstance = true;
          Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
          getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
      }
    

    该方法把mIsAttachedToInstance值改为了true,然后添加了一个自定义OnGlobalLayoutListener

    ViewTreeObserver是用来帮助我们监听某些View的某些变化的。
    ViewTreeObserver.OnGlobalLayoutListener当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类


    private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
        if (mCustomGlobalLayoutListener == null) {
          mCustomGlobalLayoutListener = new CustomGlobalLayoutListener();
        }
        return mCustomGlobalLayoutListener;
      }
    

    无论怎么走都会有一个私有类CustomGlobalLayoutListener的实例,它实现了ViewTreeObserver.OnGlobalLayoutListener接口

    CustomGlobalLayoutListener有点长,我们一步步看

        private final Rect mVisibleViewArea; //可视的一个方块区域
        private final int mMinKeyboardHeightDetected;//最小键盘高度 60
        private int mKeyboardHeight = 0;//键盘高度
        private int mDeviceRotation = 0;//旋转后会赋值
        //以下是屏幕属性的两个对象
        private DisplayMetrics mWindowMetrics = new DisplayMetrics();
        private DisplayMetrics mScreenMetrics = new DisplayMetrics();
    

    构造函数会给创建小方块以及给键盘最小高度赋值

    /* package */ CustomGlobalLayoutListener() {
          DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
          mVisibleViewArea = new Rect();
          mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
        }
    

    可视回调触发后,分别检验键盘,横竖屏和设备可用尺寸的变化

     @Override
        public void onGlobalLayout() {
          if (mReactInstanceManager == null || !mIsAttachedToInstance ||
            mReactInstanceManager.getCurrentReactContext() == null) {
            return;
          }
          checkForKeyboardEvents();
          checkForDeviceOrientationChanges();
          checkForDeviceDimensionsChanges();
        }
    

    [图片上传失败...(image-d27922-1539429545128)]

    getWindowVisibleDisplayFrame()是View类下的一个方法,从方法的名字就可以看出,它是用来获取当前窗口可视区域大小的。

    各种噼里啪啦的计算后把结果用sendEvent(String eventName, @Nullable WritableMap params)方法进行传递

    sendEvent方法会最终会调用mReactInstanceManageremit(String eventName, @Nullable Object data);方法把结果传给JS部分,返回键啥的也是走emit方法

    旋转方法checkForDeviceOrientationChanges()最终会传递一个key为namedOrientationDidChange的事件

    检测屏幕尺寸的方法checkForDeviceDimensionsChanges()最终会传递一个key为didUpdateDimensions的事件

    虽然计算场景有所差异 但是最终都是调用emit


    在绘制的时候调用过updateRootLayoutSpecs()也就是当内容发现变化的时候由他来实现真实当更新操作

    [图片上传失败...(image-629dfb-1539429545128)]

    首先拿到上下文对象 ReactContext,因为它是一个volatile 的变量所以是时不时会刷新一下值,但是不会为空

    然后就是handler的UI操作了
    调用的是com.facebook.react.uimanager下面的UIManagerModuleupdateRootLayoutSpecs(int rootViewTag, int widthMeasureSpec, int heightMeasureSpec)方法
    传入一个Tag和我们计算的结果进行UI操作(这部分怎么实现的之后再找时间分析)

    那么看下这个Tag ,找了一圈是UIManagerModuleaddRootView( final T rootView)方法的返回值,也就是拿这个ReactRootView类里的Tag变量和当前业务UIManagerModule类中rootView的Tag做了关联


    有启动就一定有销毁,不然强行等GC么?unmountReactApplication()

     public void unmountReactApplication() {
        if (mReactInstanceManager != null && mIsAttachedToInstance) {
          mReactInstanceManager.detachRootView(this);
          mIsAttachedToInstance = false;
        }
        mShouldLogContentAppeared = false;
      }
    

    官方建议在外部Activity或者容器Fragment的onDestroy()/onDestroyView()
    方法调用即可


    一开始有提到这个容器控件还传递子控件的手势,在onChildStartedNativeGesture()方法把子控件的事件用UIManagerModulemEventDispatcher属性调用JS事件分发类JSTouchDispatcheronChildStartedNativeGesture(MotionEvent androidEvent, EventDispatcher eventDispatcher)方法把事件传递给JS逻辑处理

    [图片上传失败...(image-a7d9ae-1539429545128)]

    在好几个容器控件都有用到,该实现
    [图片上传失败...(image-622f7f-1539429545128)]


    主要流程的方法都介绍完整了,这一篇还是比较细的,当然还有几个自定义入口的方法没介绍,但是并不影响你对ReactRootView的理解

    总结:

    ReactRootView主要的功能是提供强大的控件能力和事件传递

    startReactApplication方法调用后绑定上OnGlobalLayoutListener监听
    然后对屏幕,页面旋转,键盘相关进行了着重计算处理。
    onMeasure()方法计算完结果通过UIManagerModule对UI进行渲染么不是本身自身实现绘制操作。
    unmountReactApplication()方法可以卸载不用的视图对象,以防内存泄漏
    onChildStartedNativeGesture(MotionEvent androidEvent)方法把事件传递给RTC控件处理业务逻辑

    如果有不对的欢迎留言纠正!

    克拉拉

    插一段广告

    蔚来汽车
    上海 安亭/徐家汇/漕河泾 (安亭有班车)
    收Android/iOS/.Net/Java/Vue/RN开发
    标准五险一金(不避税)
    不强制加班,弹性工作

    有意向的可以加我微信,必须注明来意


    微信.jpeg

    相关文章

      网友评论

          本文标题:RN安卓实现分析之ReactRootView的实现过程

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