首先也感谢大家的支持,我的个人博客(www.lcode.org)终于达到100万PV了,在往后的日子里会更加努力,为大家分享更多的技术文章!
本文有showCar投稿,带领源码的角度来分析下React-Native底层的通信机制。
先大概讲一下React-Native的通信过程。RN主要的通信在于Java与js之间,平常我们写的jsx代码最终会调用到原生的View。上一篇博客我们也了解到了要新建一个原生模块需要在java层和js层分别写一个Module,那这彼此之间联系是如何实现的呢?
层次结构
RN总共分为三层,java层,C++层,js层。借用一幅图来看下:
①.Java层:java层就是app原生代码,它通过启动C++层的JavaScript解析器javascriptCore来执行js代码,从而构建原生UI等。java层依赖于众多优秀开源库,在图片处理使用的是Fresco,网络通信使用的是okhttp,Json解析工具用jackson,动画库用NineOldAndroids等,在java层原生的功能均封装为Module,如Toast和Log等。
②.C++层:c++层最主要是封装了JavaScriptCore,它是一个全新的支持ES6的webKit。Bridge连接了java与js之间的通信。解析js文件是通过JSCExectutor进行的。
③.JavaScript层:主要处理事件分发及UI Layout,平常开发最常用的。通用jsx来写业务代码,通过flexbox来实现布局。不依赖DOM。由于react有 DOM diff这个算法,所以它的效率非常高。
通信机制
在Java层与Js层的bridge分别存有相同一份模块配置表,Java与Js互相通信时,通过将里配置表中的moduleID,methodID转换成json数据的形式传递给到C++层,C++层传送到js层,通过js层的的模块配置表找到对应的方法进行执行,如果有callback,则回传给java层。这里只是大概介绍,后面会有详细讲解。
主要流程与主要类
先看下java层的流程图:
ReactInstanceManager:主要是用来创建及管理CatalyInstance的实例的上层接口,控制开发调试,生命周期与ReactRootView所在activity保持一致。
ReactRootView:为启动入口核心类,负责监听及分发事件并重新渲染元素,App启动后,其将作为App的root view。
CatalystInstance:提供Java与Js互通的环境,创建Java模块注册表及Javascript模块注册表,并遍历实例化模块,最后通过ReactBridge将Js Bundle传送到Js引擎。
JSBuilderLoader:缓存了JsBundle的信息,封装了上层加载JsBundle相关接口,CatalystInstance通过其间接调用ReactBridge去加载文件。
NativeModuleRegistry:Java层模块注册表,即暴露给Js的API集合。
JavascriptModuleRegistry:Js层模块注册表,负责将所有JavaScriptModule注册到CatalystInstance。
CoreModulePackage:CoreModulesPackage里面定义了RN框架核心的一些Java和JS的module,创建NativeModules&JsModules组件模块。
加载Module
首先看MainActivity的
AppReactPackage是我们自定义的一个ReactPackage,也就是说如果自己定义了新组件,要在这里添加。看下ReactActivity,看它的onCreate方法:
主要完成三个功能,通过createReactInstanceManager创建ReactInstanceManager,它主要是用来创建及管理CatalyInstance的实例的上层接口。第二步是通过createRootView来创建ReactRootView。最后调用ReactRootView的startReactApplication来启动应用,并把它当作rootview setContentView到界面上。重点看startReactApplication方法:
mJSModuleName是与前端约定好所要启动的JS Application Name。先看createReactContextInBackground方法,它位于ReactInstanceManager的实现类ReactInstanceManagerImpl中:
createReactContextInBackground最终调用到recreateReactContextInBackgroundInner:
接着调用recreateReactContextInBackgroundFromBundleFile:
经过一系列的周转,最后调用到了recreateReactContextInBackground:
上面代码通过ReactContextInitAsyncTask这个AsyncTask来初始化ReactCotext。
ReactContextInitAsyncTask为创建ReactContext的核心类,随后,调用createReactContext进一步创建ReactContext。在创建完React Context后会调用setUpReactContext,将ReactRootView做为Root View传递给UIManagerModule,调用AppRegistry的runApplication去启动Js Application等。看createReactContext的代码:
代码很长,我们来分段分析。
第一步
创建JavaScriptModulesConfig。
第二步
创建ReactApplicationContext上下文。
第三步
创建ReactPackage。ReactPackage主要通过createNativeModules、createJSModules和createViewManagers等API去创建本地模块,JS模块及视图组件等。ReactPackage分为framework的CoreModulesPackage和业务方可选的基础MainReactPackage,CoreModulesPackage封装了大部分通信,调试核心类,如UIManagerModule,这个负责控制Js层Dom到Native View的核心类;看下processPackage方法:很简单,拿到具体的native和JS的module把它们添加到对应的builder中。先是添加CoreModulesPackage中的module再添加我们自定义的module,先看CoreModulesPackage中的createNativeModules方法:
就是将UIManagerModule、AnimationsDebugModule等装到build中。
接着添加我们自定义的组件,以自定义Log为例,需要如下内容吗:
很简单,装到自定义的List中。
第四步
创建CatalystInstance。CatalystInstance并不直接面向开发者,开发者通ReactInstanceManger间接操作CatalystInstance。CatalystInstance持有对ReactBridge的引用,主要通过ReactBridge这个JNI类去实现Java层与Js层的通信,ReactBridge由CatalystInstance的Constructor创建。同时初始化的时候调用了ReactQueueConfigurationSpec.createDefault创建了ReactNative通信的两个线程 JsQueueThread&NativeModulesQueueThread;
在这里ReactBridge由CatalystInstance的Constructor创建。看下它的构造函数:
注意到这行代码:
这里通过jsModulesConfig(封装了module)创建了JSModuleRegistry。好了js注册表终于创建成功了。这里有两个问题,native注册表在哪创建呢,还有就是注册表什么时候传给js层呢。先留着这两个问题。
接下来看下initializeBridge方法:
ReactBridge将注册表信息存入与前端互通的全局变量 __fbBatchedBridgeConfig 中,使得Js层与Java层存在同样的模块注册表。bridge.setGlobalVariable是一个native函数。让我们猜一下下它的功能,就是用jsModulesConfig这个参数在js层中生成模块注册表,先看一下参数 buildModulesConfigJSONProperty的代码:
看到JsonWriter就知道是把NativeModuleRegistry 和JavaScriptModulesConfig 转换成Json字符串,其中remoteModuleConfig指NativeModuleRegistry 信息,localModulesConfig指JavaScriptModulesConfig 信息。看下JavaScriptModulesConfig 的writeModuleDescriptions方法:
看下appendJSModuleToJSONObject方法:
从上代码可知生成的json字符串包含moduleID和methodID信息。NativeModuleRegistry 也同理,我们大概看下它的代码:
接下来我们要找到setGlobalVariable的Native层代码,C++层代码我不太懂,这里参考了下别人的分析过程。大概过程是这样,首先入口是OnLoad.cpp。在其中找到如下代码:
都是塞进runOnExecutorQueue执行队列里面等待调用,最后回调到JSExecutor,而JSExecutor的实现类是JSCExecutor,最后来看看它的setGlobalVariable方法。
懂个大概吧,参数propName是从Java层传递过来的,相当于java代码中的__fbBatchedBridgeConfig和__RCTProfileIsProfiling。jsPropertyName方法就是buildModulesConfigJSONProperty封装好的对象。JSContextGetGlobalObject是WeiKit的方法,接下来会调用到js层的MessageQueue中:
生成两个映射表,从上面的代码我们己经分析过了,remoteModuleConfig是NativeModuleRegisty映射表内容。localModulesConfig则是JavaScriptModule内容。 到这里,js就生成了两张映射表了,这样java层和js层就都存在同样的映射表,相互通信就是通过它来实现。扯远了,回到createReactView。
第五步
调用catalystInstance.runJSBundle加载解析Jsbundle。
回到createReactView方法,看catalystInstance.runJSBundle:
调用catalystInstance.runJSBundle加载解析Jsbundle。假如在解析过程中出现Exception,统一交给NativeModuleCallExceptionHandler处理。
在创建完React Context后会执行ReactContextInitAsyncTask的onPostExecute。来看下onPostExecute的代码:
这里主要实现两个功能,第一,调用catalystInstance.initialize()来创建NativeModuleRegistry,好啦,回答了一个问题了哈。
第二,调用attachMeasuredRootView方法。将ReactRootView做为Root View传递给UIManagerModule,此后Js通过UIManager创建的View都会add到该View上。如下:
再来看下attachMeasuredRootViewToInstance这个方法:
在绑定完RootView后,通过CatalystInstance获取AppRegistry这个JSModule后,进一步调用runApplication启动Js Application。这个方法的最后用了我们的CatalystInstanceImpl的getJSModule方法,它会去调用JavaScriptModuleRegistry的getJSModule方法,获取对应的JavaScriptModule,也就是从注册表中获取对应的模块。这个地方很新颖,用的是用动态代理方式调用到 JavaScriptModule,具体看JavaScriptModuleInvocationHandler中的invoke方法。
这里获取了调用了方法的moduleId,methodId和参数args,然后调用了CatalystInstanceImpl的callFunction去执行。callFunction也是一个native方法。跟上面的setGlobalVariable流程是一样的,调用的是JSCExecutor的callFunction方法。
看下executeJSCallWithJSC方法:
如上:
用于生成js语名,evaluateScript用于执行js语句。methodName的值为callFunctionReturnFlushedQueue,所以它会调用到MessageQueue.callFunctionReturnFlushedQueue方法,这时就到了js层了:
看下__callFunction方法:
通过moduleID和methodID来查询两张映射Table了,获取到了具体的moduleName和methodName,接着肯定要做调用Javascript对应组件了。这样就完成了java层调用js层的module了。说了这么多看下流程图吧:
这里还有一个问题就是反过来的调用 。js调用java的Module。
RN的js调java的流程具体就是是将对应的的参数(moduleId和methodId)push到一个messageQueue中,然后等待java层的事件来驱动它,当java层的事件传递过来时,js层把messageQueue中数据一次性回调给了给java层,最后再通过注册表去调用相应Module的方法。
这里以Toast为例。我们在js层给java层回调参数时会这么写:
而RCTToastAndroid又是NativeModules里的一个属性,最终会调用MessageQueue.RemoteModules:
remoteModules就是上面分析过的,NativeModuleRegistry映射表。看下_genModules方法。
再看下_genModule方法:
主要调用_genMethod方法,它里面实现跳到了__nativeCall方法。所以,说了这么之所有的js最终都会调用到__nativeCall方法。
将ModuleID和MethodID和要传的参数push到_queue中。
当java事件驱动到来时,调用callFunctionReturnFlushedQueue方法:
返回_queue。如上面分析过的,事件驱动到来会执行JSCExecutor的callFunction。最终会执行:
m_callback真正的引用是PlatformBridgeCallback,直接看它的onCallNativeModules方法:
在回调队列线程中执行回调,被执行的回调方法里面对calls进行遍历,分别执行makeJavaCall并把多个执行结果放到一次回调给Native。
makeJavaCall将来自Javascript层的moduleId、methodId、args,被调用到Java层的ReactCallback的call方法里面。
java层中,JNI层调用的ReactCallback其实就是NativeModulesReactCallback对象,NativeModulesReactCallback是CatalystInstanceImpl的一个内部类,直接看它的call方法:
mJavaRegistry就是java层保存的NativeModuleRegistry映射表,这里就是通过Js传过来moduleId, methodId来匹配方法,看下它的call方法:
ModuleDefinition则是NativeModuleRegistry的一个内部类,mModuleTable是保持着NativeModule的映射表,通过get方法获得所有调用的Module,在这里就是ToastModule。看下ModuleDefinition的call方法:
ModuleDefinition是NativeModule内方法信息的封装类,代码也在NativeModuleRegistry中。
NativeModule.NativeMethod对象,真正的实现则是JavaMethod类,所以this.methods.get(methodId)).method.invoke最终是调用javaMethod的invoke方法。
上面代码中,从js层传过来的参数被封装到mArguments中,最后调用以下代码来完成 最终操作:
BaseJavaModule.this指代当前NativeModule对象的实例,如果是Toast组件的话就是ToastModule了,利用反射就找到了ToastModule模块。到此,js调用java流程就完成了。画了个流程图方便理解:
最后来对这篇文章做一个总结。
在程序启动的时候,首先会调用ReactActivity的onCreate函数中,我们会去创建一个ReactInstanceManagerImpl对象。通过ReactRootView的startReactApplication方法开启整个RN世界的大门。
在这个方法中,我们会通过一个AsyncTask去创建ReactContext
在创建ReactContext中,我们把我们自己注入和CoreModulesPackage通过processPackage方法将其中的各个modules注入到了对应的Registry中。最后通过CatalystInstanceImpl中的ReactBridge将NativeModule和JSModule注册表通过jni传输到了JS层。
java调用js时,会在ReactApplicationContext创建的时候存入注册表类JavaScriptModuleRegistry中,同时通过动态代理生成代理实例,并在代理拦截类JavaScriptModuleInvocationHandler中统一处理发向Javascript的所有通信请求。
JSCExecutor将所有来自Java层的通信请求封装成Javascript执行语句。
接着在js层中的MessageQueue里匹配ModuleId和MethodId。找到调用模块。
如果是js层调用java层,js最终都会调用__nativeCall方法,通过flushedQueue将this._queue返回给Bridger。
C++层调用PlatformBridgeCallback对象的onCallNativeModules方法,执行makeJavaCall方法,里面最终通过env->CallVoidMethod调用了Java层的方法。
调用Java层NativeModulesReactCallback的call方法,通过moduleID从保存在其内部的NativeModule映射表,匹配到需要被执行的NativeModule对象,再通过methodID匹配到所要调用的方法。通过invoke反射方式执行NativeModule的方法。
ReactNative的源码流程就分析完了,当然还有很多不懂的地方,以后学习中再争取弄懂。
各位童鞋,恭喜你居然把本文看完了,当然了作为回报,也弥补一下大家买了辣条和啤酒饮料,最后送上支付宝红包口令哈哈~74516771