React Native 代码阅读(九):APP 启动时的 Bridge 通信记录

2019-03-08

前言

在前面的文章中,我们知道 Native 侧与 JavaScript 是通过 Bridge 进行通信的。那么,在 JS APP 启动时到底进行了哪些通信呢?我把它记录在了本文中。

如何监听 Bridge 通信呢?

其实很简单,React Native 预留了一个方法,通过 JavaScript 侧 MessageQueue 的 spy 方法,会自动把通信记录打印在 Console 中。

具体可以参考我之前的两篇翻译文章:

两行代码就能搞定,在 index.js 中添加:

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'
MessageQueue.spy(true)

我们的 React Native APP 启动后的界面如下:

MaxieeRNLab 是我目前正在开发的一个开源项目。

未来我会将学到的 React Native 开发经验都沉淀在这个项目当中,并打造良好的学习体验。欢迎 Star 支持!

AppRegistry.runApplication

下面我们来逐行地分析启动记录。

N->JS : AppRegistry.runApplication(["MaxieeRNLab",{"rootTag":1}])

这是第一条记录:

  • N->JS 表示它是从 Native 侧调用 JavaScript 侧的

还记得我们在开发 React Native 应用时,首先都要调用:

AppRegistry.registerComponent("MaxieeRNLab", () => App);

之前写这句话的时候,还不知道哪里会调用,现在终于建立起联系了!

之后 Console 会再打一条:

Running application "MaxieeRNLab" with appParams: {"rootTag":1}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF

说明我们的程序启动了。

我们在深入一步问一个问题,Native 这一侧是哪里调用呢?

我们按照调用栈顺序,梳理出如下调用:

  • com.facebook.react.ReactRootView#runApplication
    • com.facebook.react.ReactInstanceManager#attachRootViewToInstance
      • com.facebook.react.ReactInstanceManager#attachRootView
        • com.facebook.react.ReactRootView#attachToReactInstanceManager
          • com.facebook.react.ReactRootView#onMeasure
          • com.facebook.react.ReactRootView#startReactApplication
            • com.facebook.react.ReactActivityDelegate#loadApp
              • com.facebook.react.ReactActivity#loadApp
              • com.facebook.react.ReactActivityDelegate#onCreate
      • com.facebook.react.ReactInstanceManager#setupReactContext
        • com.facebook.react.ReactInstanceManager#runCreateReactContextOnNewThread
          • ……

在之前的文章中,我们分析过 React Native 框架的启动过程,结合上面的调用栈,两个是一正一反的关系,能够帮助我们形成更深的理解。

Running application "MaxieeRNLab"…… 这个是在哪里执行的呢?

是在 Libraries/ReactNative/AppRegistry.js 的 runApplication 方法中进行的。这里我们不展开,放到后面地文章中再分析。

创建视图

接下来我们接着看日志,再下面一条日志就开始创建布局了:

S->N : UIManager.createView([3,"RCTRawText",1,{"text":"React Native UI"}])
JS->N : UIManager.createView([5,"RCTText",1,{"fontFamily":"sans-serif","fontSize":28,"fontWeight":"bold","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([5,[3]])
JS->N : UIManager.createView([7,"RCTRawText",1,{"text":"React Native Elements Demo"}])
JS->N : UIManager.createView([9,"RCTText",1,{"color":-14644772,"fontSize":16,"textAlign":"center","paddingTop":2,"paddingBottom":1,"fontFamily":"sans-serif-medium","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([9,[7]])
……

对照文章开头的 UI 界面来看:

  • 我们发现 <Text/> 组件实际上创建了两个 View:RCTRawText 和 RCTText
  • 之后通过 setChildren 对两者建立嵌套关系

跳过几行重复的,我们来到下面记录:

JS->N : UIManager.createView([37,"RCTView",1,{"flex":1,"margin":8}])
JS->N : UIManager.setChildren([37,[5,17,23,35]])

之前创建的编号为 5 的 RCTText(React Native UI),被放到了编号为 37 的 RCTView 当中。

再跳过一些重复记录,我们来到:

JS->N : UIManager.createView([77,"RCTView",1,{"flex":1,"pointerEvents":"box-none"}])
JS->N : UIManager.setChildren([77,[75]])
JS->N : UIManager.setChildren([1,[77]])

其中:

  • 编号为 1 View 是应用的根视图,我们把编号为 77 的 View 添加到了根视图当中

上面只是对视图的添加,由于界面中有两个按钮,因此接下来需要对按钮进行特别操作:

JS->N : NativeAnimatedModule.createAnimatedNode([1,{"type":"value","value":0,"offset":0}])
JS->N : NativeAnimatedModule.createAnimatedNode([2,{"type":"value","value":0,"offset":0}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationX"],"animatedValueTag":1}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationY"],"animatedValueTag":2}])
JS->N : RNGestureHandlerModule.createGestureHandler(["PanGestureHandler",1,{"enabled":false,"hitSlop":{"right":50},"activeOffsetXEnd":5,"failOffsetYStart":-20,"failOffsetYEnd":20}])
JS->N : RNGestureHandlerModule.attachGestureHandler([1,69])
J

APP 状态就绪

接下来是:

N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}])
N->JS : <callback for AppState.getCurrentAppState>([{"app_state":"active"}])

这是从 Native 调用到 JavaScript,用于通知 APP 状态就绪。

我们先来看 Native 侧的调用位置:

  • com.facebook.react.modules.appstate.AppStateModule#sendAppStateChangeEvent
    • com.facebook.react.modules.appstate.AppStateModule#onHostResume
    • com.facebook.react.modules.appstate.AppStateModule#onHostPause

可以看出,这是跟 Activity 生命周期走的。

也就是当 Activity 显示出来的时候,发出了 state active 消息。

其中,RCTDeviceEventEmitter 是 Native 向 JavaScript 发送消息的发送器。

建立 WebSocket 链接

由于我们运行的是 DEV 模式,因此会建立 WebSocket 链接:

JS->N : WebSocketModule.connect(["ws://localhost:8097",null,{"headers":{}},1])

如果我们进行浏览器调试,就会走这条 WebSocket。

它定义了一个定时器,如果没有连接上,会反复 connect。

小结

至此我们就完成了启动流程的 Bridge 记录分析。

附:Bridge 完整记录

最后,我给出启动时的完整记录:

N->JS : AppRegistry.runApplication(["MaxieeRNLab",{"rootTag":1}])
Running application "MaxieeRNLab" with appParams: {"rootTag":1}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
JS->N : UIManager.createView([3,"RCTRawText",1,{"text":"React Native UI"}])
JS->N : UIManager.createView([5,"RCTText",1,{"fontFamily":"sans-serif","fontSize":28,"fontWeight":"bold","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([5,[3]])
JS->N : UIManager.createView([7,"RCTRawText",1,{"text":"React Native Elements Demo"}])
JS->N : UIManager.createView([9,"RCTText",1,{"color":-14644772,"fontSize":16,"textAlign":"center","paddingTop":2,"paddingBottom":1,"fontFamily":"sans-serif-medium","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([9,[7]])
JS->N : UIManager.createView([13,"RCTView",1,{"flexDirection":"row","justifyContent":"center","alignItems":"center","borderRadius":3,"backgroundColor":0,"padding":8,"borderWidth":0.36363636363636365,"borderColor":-14644772,"nativeBackgroundAndroid":{"type":"RippleAndroid","borderless":false},"accessible":true}])
JS->N : UIManager.setChildren([13,[9]])
JS->N : UIManager.createView([15,"RCTView",1,{"borderRadius":3}])
JS->N : UIManager.setChildren([15,[13]])
JS->N : UIManager.createView([17,"RCTView",1,{"margin":10,"flexDirection":"column","alignItems":"center"}])
JS->N : UIManager.setChildren([17,[15]])
JS->N : UIManager.createView([19,"RCTRawText",1,{"text":"React Native Architecture"}])
JS->N : UIManager.createView([23,"RCTText",1,{"fontFamily":"sans-serif","fontSize":28,"fontWeight":"bold","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([23,[19]])
JS->N : UIManager.createView([25,"RCTRawText",1,{"text":"React Navigation Demo"}])
JS->N : UIManager.createView([27,"RCTText",1,{"color":-14644772,"fontSize":16,"textAlign":"center","paddingTop":2,"paddingBottom":1,"fontFamily":"sans-serif-medium","accessible":true,"allowFontScaling":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([27,[25]])
JS->N : UIManager.createView([29,"RCTView",1,{"flexDirection":"row","justifyContent":"center","alignItems":"center","borderRadius":3,"backgroundColor":0,"padding":8,"borderWidth":0.36363636363636365,"borderColor":-14644772,"nativeBackgroundAndroid":{"type":"RippleAndroid","borderless":false},"accessible":true}])
JS->N : UIManager.setChildren([29,[27]])
JS->N : UIManager.createView([33,"RCTView",1,{"borderRadius":3}])
JS->N : UIManager.setChildren([33,[29]])
JS->N : UIManager.createView([35,"RCTView",1,{"margin":10,"flexDirection":"column","alignItems":"center"}])
JS->N : UIManager.setChildren([35,[33]])
JS->N : UIManager.createView([37,"RCTView",1,{"flex":1,"margin":8}])
JS->N : UIManager.setChildren([37,[5,17,23,35]])
JS->N : UIManager.createView([39,"RCTView",1,{"flex":1}])
JS->N : UIManager.setChildren([39,[37]])
JS->N : UIManager.createView([43,"RCTRawText",1,{"text":"MaxieeRNLab"}])
JS->N : UIManager.createView([45,"RCTText",1,{"numberOfLines":1,"allowFontScaling":true,"accessibilityTraits":"header","fontSize":20,"fontWeight":"500","color":-436207616,"marginHorizontal":16,"textAlign":"left","accessible":true,"ellipsizeMode":"tail"}])
JS->N : UIManager.setChildren([45,[43]])
JS->N : UIManager.createView([47,"RCTView",1,{"pointerEvents":"box-none","backgroundColor":0,"bottom":0,"top":0,"position":"absolute","alignItems":"center","flexDirection":"row","justifyContent":"flex-start","left":0,"right":0,"opacity":1}])
JS->N : UIManager.setChildren([47,[45]])
JS->N : UIManager.createView([49,"RCTView",1,{"position":"absolute","left":0,"right":0,"top":0,"bottom":0,"flexDirection":"row"}])
JS->N : UIManager.setChildren([49,[47]])
JS->N : UIManager.createView([53,"RCTView",1,{"flex":1}])
JS->N : UIManager.setChildren([53,[49]])
JS->N : UIManager.createView([55,"RCTView",1,{"pointerEvents":"box-none","onLayout":true,"backgroundColor":-1,"elevation":0,"height":56,"shadowOpacity":0,"borderBottomColor":-1118482,"borderBottomWidth":1,"paddingTop":0,"paddingBottom":0,"paddingLeft":0,"paddingRight":0}])
JS->N : UIManager.setChildren([55,[53]])
JS->N : UIManager.createView([57,"RCTView",1,null])
JS->N : UIManager.setChildren([57,[55]])
JS->N : UIManager.createView([59,"RCTView",1,{"flex":1,"flexDirection":"column-reverse","overflow":"hidden"}])
JS->N : UIManager.setChildren([59,[39,57]])
JS->N : UIManager.createView([63,"RCTView",1,{"importantForAccessibility":"yes","flex":1,"backgroundColor":-1}])
JS->N : UIManager.setChildren([63,[59]])
JS->N : UIManager.createView([65,"RCTView",1,{"pointerEvents":"auto","position":"absolute","left":0,"right":0,"top":0,"bottom":0,"opacity":1,"transform":[{"translateX":0},{"translateY":0}]}])
JS->N : UIManager.setChildren([65,[63]])
JS->N : UIManager.createView([67,"RCTView",1,{"flex":1}])
JS->N : UIManager.setChildren([67,[65]])
JS->N : UIManager.createView([69,"RCTView",1,{"collapsable":false,"flex":1,"flexDirection":"column-reverse","overflow":"hidden"}])
JS->N : UIManager.setChildren([69,[67]])
JS->N : UIManager.createView([73,"RCTView",1,{"onLayout":true,"flex":1}])
JS->N : UIManager.setChildren([73,[69]])
JS->N : UIManager.createView([75,"RCTView",1,{"collapsable":true,"pointerEvents":"box-none","flex":1}])
JS->N : UIManager.setChildren([75,[73]])
JS->N : UIManager.createView([77,"RCTView",1,{"flex":1,"pointerEvents":"box-none"}])
JS->N : UIManager.setChildren([77,[75]])
JS->N : UIManager.setChildren([1,[77]])
JS->N : NativeAnimatedModule.createAnimatedNode([1,{"type":"value","value":0,"offset":0}])
JS->N : NativeAnimatedModule.createAnimatedNode([2,{"type":"value","value":0,"offset":0}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationX"],"animatedValueTag":1}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationY"],"animatedValueTag":2}])
JS->N : RNGestureHandlerModule.createGestureHandler(["PanGestureHandler",1,{"enabled":false,"hitSlop":{"right":50},"activeOffsetXEnd":5,"failOffsetYStart":-20,"failOffsetYEnd":20}])
JS->N : RNGestureHandlerModule.attachGestureHandler([1,69])
JS->N : IntentAndroid.getInitialURL([134,135])
JS->N : UIManager.measureInWindow([55,137])
N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}])
N->JS : <callback for AppState.getCurrentAppState>([{"app_state":"active"}])
N->JS : RCTDeviceEventEmitter.emit(["websocketFailed",{"message":"Failed to connect to localhost/127.0.0.1:8097","id":0}])
JS->N : Timing.createTimer([2,2000,1551945257451,false])
N->JS : <callback for IntentAndroid.getInitialURL>([null])
N->JS : <callback for UIManager.measureInWindow>([0,0,0,0])
N->JS : RCTDeviceEventEmitter.emit(["didUpdateDimensions",{"screenPhysicalPixels":{"densityDpi":440,"fontScale":1,"scale":2.75,"height":1920,"width":1080},"windowPhysicalPixels":{"densityDpi":440,"fontScale":1,"scale":2.75,"height":1812,"width":1080}}])
JS->N : NativeAnimatedModule.removeAnimatedEventFromView([69,"onGestureHandlerEvent",1])
JS->N : NativeAnimatedModule.removeAnimatedEventFromView([69,"onGestureHandlerEvent",2])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationX"],"animatedValueTag":1}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationY"],"animatedValueTag":2}])
N->JS : RCTEventEmitter.receiveEvent([55,"topLayout",{"target":55,"layout":{"height":56,"width":392.7272644042969,"y":0,"x":0}}])
N->JS : RCTEventEmitter.receiveEvent([73,"topLayout",{"target":73,"layout":{"height":634.9091186523438,"width":392.7272644042969,"y":0,"x":0}}])
JS->N : UIManager.updateView([65,"RCTView",{"transform":[{"translateY":0}]}])
JS->N : NativeAnimatedModule.removeAnimatedEventFromView([69,"onGestureHandlerEvent",1])
JS->N : NativeAnimatedModule.removeAnimatedEventFromView([69,"onGestureHandlerEvent",2])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationX"],"animatedValueTag":1}])
JS->N : NativeAnimatedModule.addAnimatedEventToView([69,"onGestureHandlerEvent",{"nativeEventPath":["translationY"],"animatedValueTag":2}])
JS->N : RNGestureHandlerModule.updateGestureHandler([1,{"enabled":false,"hitSlop":{"right":-342.7272644042969},"activeOffsetXEnd":5,"failOffsetYStart":-20,"failOffsetYEnd":20}])
N->JS : JSTimers.callTimers([[2]])
JS->N : WebSocketModule.connect(["ws://localhost:8097",null,{"headers":{}},1])
N->JS : RCTDeviceEventEmitter.emit(["websocketFailed",{"message":"Failed to connect to localhost/127.0.0.1:8097","id":1}])
JS->N : Timing.createTimer([6,2000,1551945259545,false])
N->JS : JSTimers.callTimers([[6]])
JS->N : WebSocketModule.connect(["ws://localhost:8097",null,{"headers":{}},2])
N->JS : RCTDeviceEventEmitter.emit(["websocketFailed",{"message":"Failed to connect to localhost/127.0.0.1:8097","id":2}])
JS->N : Timing.createTimer([7,2000,1551945261627,false])
N->JS : JSTimers.callTimers([[7]])
JS->N : WebSocketModule.connect(["ws://localhost:8097",null,{"headers":{}},3])
N->JS : RCTDeviceEventEmitter.emit(["websocketFailed",{"message":"Failed to connect to localhost/127.0.0.1:8097","id":3}])
JS->N : Timing.createTimer([8,2000,1551945263663,false])