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

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

开篇之前日常安利
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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343