内容简介:开篇之前日常安利上一篇提到了入口类ReactActivity和他的代理实现类ReactActivityDelegate,这一次继续我们的分析之路
转载请注明出处:王亟亟的大牛之路
开篇之前日常安利
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 {...}
FrameLayout getWidthMeasureSpec() getHeightMeasureSpec()
既然是一个为了计算尺寸而自定义的的Layout那么一定会有 onMeasure(),onLayout(),
等方法
首先获取了Mode类型,判断如果是 MeasureSpec.AT_MOST
或者 MeasureSpec.UNSPECIFIED
就对子控件进行循环计算复制给 width
变量,如果不是的话直接调用 MeasureSpec.getSize()
方法进行赋值。
MeasureSpec有三种模式:
UNSPECIFIED: 父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
EXACTLY: 父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
AT_MOST: 子元素至多达到指定大小的值。
MeasureSpec.getSize(measureSpec): 根据提供的测量值提取大小值(尺寸级别的数值变化)
高度同上,我们就得到了 2个具体的宽高值。然后调用 setMeasuredDimension(width, height);
,设置当前View的大小。
计算完把ReactRootView的类变量mWasMeasured设置为true,表示控件已经计算过了!
经过判断决定是刷新位置信息还是构建ReactInstanceManager实例
在mReactInstanceManager为null时,enableLayoutCalculation()方法直接返回,否则会对当前mReactInstanceManager对象的ReactContext进行一轮设置。
那么mReactInstanceManager又是在哪初始化的呢?
这个就是我们在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(); }
getWindowVisibleDisplayFrame()是View类下的一个方法,从方法的名字就可以看出,它是用来获取当前窗口可视区域大小的。
各种噼里啪啦的计算后把结果用 sendEvent(String eventName, @Nullable WritableMap params)
方法进行传递
sendEvent方法会最终会调用 mReactInstanceManager
的 emit(String eventName, @Nullable Object data);
方法把结果传给JS部分,返回键啥的也是走emit方法
旋转方法checkForDeviceOrientationChanges()最终会传递一个key为namedOrientationDidChange的事件
检测屏幕尺寸的方法 checkForDeviceDimensionsChanges()
最终会传递一个key为didUpdateDimensions的事件
虽然计算场景有所差异 但是最终都是调用emit
在绘制的时候调用过 updateRootLayoutSpecs()
也就是当内容发现变化的时候由他来实现真实当更新操作
首先拿到上下文对象 ReactContext,因为它是一个 volatile
的变量所以是时不时会刷新一下值,但是不会为空
然后就是handler的UI操作了
调用的是 com.facebook.react.uimanager
下面的UIManagerModule updateRootLayoutSpecs(int rootViewTag, int widthMeasureSpec, int heightMeasureSpec)
方法
传入一个Tag和我们计算的结果进行UI操作(这部分怎么实现的之后再找时间分析)
那么看下这个Tag ,找了一圈是UIManagerModule addRootView( 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()
方法把子控件的事件用 UIManagerModule
的 mEventDispatcher
属性调用JS事件分发类 JSTouchDispatcher
的 onChildStartedNativeGesture(MotionEvent androidEvent, EventDispatcher eventDispatcher)
方法把事件传递给JS逻辑处理
在好几个容器控件都有用到,该实现
主要流程的方法都介绍完整了,这一篇还是比较细的,当然还有几个自定义入口的方法没介绍,但是并不影响你对 ReactRootView
的理解
ReactRootView主要的功能是提供强大的控件能力和事件传递
startReactApplication
方法调用后绑定上 OnGlobalLayoutListener
监听
然后对屏幕,页面旋转,键盘相关进行了着重计算处理。
onMeasure()
方法计算完结果通过 UIManagerModule
对UI进行渲染么不是本身自身实现绘制操作。
unmountReactApplication()
方法可以卸载不用的视图对象,以防内存泄漏
onChildStartedNativeGesture(MotionEvent androidEvent)
方法把事件传递给RTC控件处理业务逻辑
如果有不对的欢迎留言纠正!
插一段广告
蔚来汽车
上海 安亭/徐家汇/漕河泾 (安亭有班车)
收Android/iOS/.Net/Java/Vue/RN开发
标准五险一金(不避税)
不强制加班,弹性工作
有意向的可以加我微信,必须注明来意
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【Node】详解模块的实现过程
- Java核心库实现AOP过程
- 实现SQL分页的存储过程代码
- Loading动效实现背后的思考过程
- 首页查询功能的一次实现过程
- 使用MyBatis轻松实现递归查询与存储过程调用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SHA 加密
SHA 加密工具
HEX CMYK 转换工具
HEX CMYK 互转工具