React Native Android 通信原理

作者:Longv2go
地址:http://t.cn/RGxdO76

React Native (Android)内置了一个用于解析JavaScript(以下简称JS)脚本的框架,方便把Java类暴漏给JS调用,具体的使用方法参见,这篇文章就用来研究一下Java和JS的通信原理,JS是如何调用Java的。

总体结构

当初始化阶段,Java端会把所有要暴漏的Java类的信息封装成Config传给JS,然后根据Config生成对应Java类的Javascript镜像对象,以及要暴漏的方法,在JS中调用这个镜像对象的方法就会被转发到对应的Java对象上,如下所示

JS的代码总要被解析执行,那么React是在哪里执行JS的呢?React并没有通过webview去执行JS代码,它是通过Jni调用c++代码通过Javascriptcore来执行JS的,首先来看看生成so依赖的的文件,代码在react-native/ReactAndroid/src/main/jni目录下。(用NDK编译在Android上运行的c/c++代码,关于NDK请自行google)

reactnativejni
reactnativejni
其中OnLoad.cpp很关键,里面通过Jni映射了本地的方法到Java中,是Java和C++之间的桥梁。在Java中主要通过ReactBridge.java来调用C++,NativeModulesReactCallback类是C++调用Java的桥梁。

例如以下代码,截取自OnLoad.cpp的JNI_OnLoad方法(这个方法会在Java载入so文件的时候由Jni首先调用)

registerNatives("com/facebook/react/bridge/JSCJavaScriptExecutor", {
    makeNativeMethod("initialize", executors::createJSCExecutor),
});

意思是把Java中的JSCJavaScriptExecutor类的initialize方法映射为executors::createJSCExecutor的C++方法,这样当在Java中调用initialize就会在C++中执行executors::createJSCExecutor。

Java端初始化

在第一个Activity创建的时候开始进行整个Brdige的Java端的初始化,流程图如下

初始化主要做几件事情

  1. 创建JSCJavaScriptExecutor,这个是个C++包装类,会调用到C++的executors::createJSCExecutor()
  2. 创建NativeModuleRegistry管理所有的要暴漏给JS的Java类,暴漏给JS的java类的搜集是通过ReactActivity中的getPackages实现的,详看上图
  3. 创建ReactBridge对象,这个对象也是个C++桥梁对象,用来调用C++代码,4. 创建过程会调用到bridge::create()方法
  4. 创建config(包含了要暴漏的所有java类的信息,json格式),并通过bridge设置到JS环境中的__fbBatchedBridgeConfig变量,这样在JS端就可以通过这个变量来获取所有的Java类信息了,然后根据config生产对应的镜像对象。

config格式如下:

{
    "remoteModuleConfig": {
        "MyToastAndroid": {
            "moduleID": 14,
            "methods": {
                "show": {
                    "methodID": 0,
                    "type": "remote"
                }
            },
            "constants": {
                "LONG": 1,
                "SHORT": 0
            }
        },...
    }
}

Java端还会创建一个CatalystInstanceImpl对象,这个对象用来管理所有的NativeModules以及与C++通信的桥梁ReactBrdige,类图结构如下:
React 类图
React 类图

几个重要的类

  1. NativeModuleRegistry, 维护一个mModuleInstances数组,这个数组的顺序很重要,因为这和在JS端维护的镜像对象的数组是一致的当JS调用Java的时候实际上传递的正是在这个数组中的索引
  2. NativeModuleReactCallBack, C++回调Java的对象,这个对象会在创建ReactBridge的时候传递给C++,当JS调用Java的方法的时候会调用这个类的方法
  3. ReactBridge,调用C++的桥梁

最后catalystInstance.runJSBundle()开启JS端的初始化流程。

JS端的初始化

和React Native iOS的JS初始化是一样的,因为Android和iOS的react是同享一份JS代码的,在react命令生成的react native工程的node_modules目录下面存放着所有JS的模块。在编译的时候会把所有的JS模块合并成一个大的JS文件。初始化就是在JS环境中执行这个文件。其中MessageQueue.js, BatchedBridge.js和NativeModules.js三个文件是关于JS bridge的。初始化流程如下图
react-native 通信原理
react-native 通信原理

在遍历RemoteModules的时候需要为每一个映射对象生成Java暴漏的方法,因为JS是不支持消息转发,如果调用了没有实现的方法,那么就直接生成一个错误,所以要知道每一个暴漏的Module要暴漏的方法,在JS端预先生成对应的实现。在Java端初始化的时候已经在JS中注入了config信息,包括了要暴漏的类和方法名,足已生成镜像对象了。MessageQueue.js中的_genMethod方法中为每一个映射对象生成相应的方法实现。最后生成方法如下:

> NativeModules.ExportModule.hello
< function () {
          for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
            args[_key2] = arguments[_key2];
          }

          var lastArg = args.length > 0 ? args[args.length - 1] : null;
          var secondLastArg = args.length > 1 ? args[args.length - 2] : null;
          var hasSuccCB = typeof lastArg === 'function';
          var hasErrorCB = typeof secondLastArg === 'function';
          hasErrorCB && invariant(hasSuccCB, 'Cannot have a non-function arg after a function arg.');
          var numCBs = hasSuccCB + hasErrorCB;
          var onSucc = hasSuccCB ? lastArg : null;
          var onFail = hasErrorCB ? secondLastArg : null;
          args = args.slice(0, args.length - numCBs);
          return self.__nativeCall(module, method, args, onFail, onSucc);
        }

当调用一个镜像对象的方法,就会调用到_nativeCall方法,而参数就是闭包生成的时候捕获的module和method等, 在Java端和JS端会保存一份关于暴漏的Java类对象信息的数组,这俩分数组的顺序是相同的,而 _nativeCall中的参数就是要调用的Java类在数组中的索引,这样在Java端就可以通过索引找到要调用的Java类了。在JS端这个数组是MessageQueue的modulesConfig,Java端是NativeModuleRegistry的mModuleInstances。

JS调用Java流程

JS会在调用native方法的时候调用_nativeCall
然后调用global.nativeFlushQueueImmediate(this._queue);
,其中nativeFlushQueueImmediate方法会调用到C++中,是JS调用C++的桥梁
nativeFlushQueueImmediate方法是在C++中的JSCExecutor.cpp中注册的,我们先来看看JSCExecutor的创建过程,如下图

在JSCExecutor的构造方法中调用了installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
,这样就在JS环境中注册了nativeFlushQueueImmediate方法,当在JS中调用了nativeFlushQueueImmediate就会执行JSCExecutor的nativeFlushQueueImmediate C++方法,然后调用executor->flushQueueImmediate(resStr);,如上图所示,会回调到 OnLoad.cpp中的dispatchCallbacksToJava()方法,上图中红框中是采用了C++的闭包写法,参考

dispatchCallbacksToJava ---> makeJavaCall() ---> env->CallVoidMethod()

最后调用到CallVoidMethod的jni方法,这样就从C++调用到了Java代码了,传入的CallVoidMethod的callback参数就是在创建ReactBrdige的时候传入的NativeModuleReactCallback的java对象对应的jni对象,而gCallbackMethod就是call方法,这样就调用到了Java类NativeModuleReactCallback的call方法。哇哦终于回到java了,Java在通过反射最后调用实际的java方法。

总结

本文只是列出了整个Bridge比较难于理解的部分以及流程,想要详细了解具体原理还需要自己看代码,如果遇到代码中不明白的地方可以参考本文。关于React Native iOS的Objective-C和JS的通信原理请参考

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

推荐阅读更多精彩内容