React-Native系列Android——Native与Javascript通信原理(二)

本文详细分析了React-Native中Javascript如何响应Native端的通信,包括Javascript如何构造应答内容,通过Bridge层转发到Java层,最终在Java层执行NativeModule的方法。文中以ToastAndroid为例,解释了Javascript的应答内容如何被放入this._queue数组,以及Bridge层和Java层如何处理这些应答,最后展示了整个通信流程的总结和流程图。
摘要由CSDN通过智能技术生成

前一篇博客分析了Native端向Javascript端通信的全流程,这次来研究下Javascript端向Native端通信的全流程,与前篇恰好构成一个基本完整的通信机制。

本篇博客内容与前篇联系较大,有些分析过的东西这次就直接拿来用了,不再赘述,所以希望阅读这篇文章之前先熟悉下前篇:
React-Native系列Android——Native与Javascript通信原理(一)
http://blog.csdn.net/MegatronKings/article/details/51114278

引用下前篇中的通信模型:

NativeJavascript之间的双向通信其实是你来我往的一个循环过程,就好像是两个人在对话,你一句我一句然后你再一句我再一句。那么总有一个会话的发起者吧?当然是Native了,因为所有的行为都是从Native端发起的,用户操作直接面向的也是Native。所以这个通信模型又可以看成是Native发起会话,然后Javascript进行应答。

所以,今天的博文重点就是分析Javascript是如何应答Native,同时Native又是如何处理来自Javascript的应答的。


1、Javascript的应答

还记得前篇Bridge层章节中JSCExecutor::callFunction最后有一个callNativeModules的调用吗?忘记了不要紧,再来回顾下jni/react/JSCExecutor.cpp中的这段代码吧!

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  // TODO:  Make this a first class function instead of evaling. #9317773
  std::vector<folly::dynamic> call{
    moduleId,
    methodId,
    std::move(arguments),
  };
  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {

  ...

  // Evaluate script with JSC
  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
  auto js = folly::to<folly::fbstring>(
      "__fbBatchedBridge.", methodName, ".apply(null, ",
      folly::toJson(jsonArgs), ")");
  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
  return Value(ctx, result).toJSONString();
}

executeJSCallWithJSC方法执行Javascript最终脚本后返回了一个名为callsJSON串,这个字符串又被塞进了Bridge.cppcallNativeModules方法,这个方法大家都能根据字面意思猜测到是调用Native端组件的,那么这个JSON串内容就是来自Javascript的应答内容了。

callNativeModules方法及里面的细节我们暂时放一放,先来看看这个Javascript的应答内容里面到底是些神马东西!

前篇里面,被WebKit库执行的Javascript语句,大家应该还记得吧,最终如下:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

再次看一下MessageQueue.jscallFunctionReturnFlushedQueue方法内容:

callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }
  flushedQueue() {
    this.__callImmediates();

    let queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

返回的是flushedQueue()方法,而flushedQueue()返回的是this._queue数组或者null

这里有个小细节,flushedQueue()并不是直接返回this._queue的,而是新定义了一个局部变量queue,先将this._queue的值赋给queue用于返回,然后又清空数组内容。这种处理方式,说明了this._queue数组是专门存放应答Native端内容的,每次应答之后都会置空然后等待下一次的会话到来。

那么,问题来了!应答Native端的内容是如何被放进this._queue数组里面的呢?

有点抽象,也有点玄乎了,我们不妨结合一下场景分析:假设用户点击了文本,然后手机弹出一个Toast,这个Toast其实就是来自Javascript的应答,如果用React-Native代码写出来应该是这样:

var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);

当然,弹这个Toast效果是需要Native端来做了,但是‘Awesome, Clicking!’文案和SHORT,两个参数是来自Javascript端的,Javascript端会告诉Native端弹一个内容‘Awesome, Clicking!’时长SHORT的Toast,其实就是对用户点击这个会话的应答了。

那我么就以ToastAndroid为例,来分析下Javascript的应答,也就是如何把Awesome, Clicking!’和SHORT两个参数塞进this._queue数组的。

先来研究一下ToastAndroid的代码,位于\node_modules\react-native\Libraries\Components\ToastAndroid\ToastAndroid.android.js

'use strict';

var RCTToastAndroid = require('NativeModules').ToastAndroid;

var ToastAndroid = {

  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,

  show: function (
    message: string,
    duration: number
  ): void {
   
    RCTToastAndroid.show(message, duration);
  },

};

module.exports = ToastAndroid;

里面使用的是RCTToastAndroid,而RCTToastAndroid又是NativeModules里的一个属性。所以,这一段Toast的调用代码等价于:

NativeModules.RCTToastAndroid.show(message, duration);

下面来看看NativeModules吧,代码位于\node_modules\react-native\Libraries\BatchedBridge\BatchedBridgedModules\NativeModules.js

const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
...
const NativeModules = {};
...
module.exports = NativeModules;

乍一看,NativeModules对象里面是空的,并没有所谓的RCTToastAndroid属性,但是不要忘了,Javascript是可以通过Object.defineProperty方式定义对象属性的,也算是其独门绝技了,仔细阅读一下NativeModules的代码,果然找到了一些蛛丝马迹,我们来看看:

const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
        const json = global.nativeRequireModuleConfig(moduleName);
        const config = json && JSON.parse(json);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      return module;
    },
  });
});

这段代码的意

RNPolymerPo 是一个基于 React Native 的生活类聚合实战项目,目前由于没有 MAC 设备,所以没有适配 iOS,感兴趣的可以自行适配 app 目录下相关 JS 代码即可。 获取代码与编译调试打包 如下所有步骤及说明均为 React Native Android 的 DIY,涉及命令均为 Ubuntu 环境,Windows 类推即可。 1. 获取代码及模块安装和签名配置 执行如下命令进行代码下载及模块安装: $ git clone https://github.com/yanbober/RNPolymerPo.git $ cd RNPolymerPo $ npm install //如果觉得慢可以先切换到国内 npm 镜像源再执行此命令 配置 Gradle 个人签名路径及属性: //1. 把你个人的签名 my-release-key.keystore 文件(不知道如何生成请自行搜索)放到 RNPolymerPo 工程的 android/app 文件夹下。 //2. 编辑工程的 gradle.properties 文件,添加如下的代码(注意把其中的****替换为你自己相应密码)。 MYAPP_RELEASE_STORE_FILE=my-release-key.keystore MYAPP_RELEASE_KEY_ALIAS=my-key-alias MYAPP_RELEASE_STORE_PASSWORD=***** MYAPP_RELEASE_KEY_PASSWORD=***** 2. 编译打包 APK 文件 编译生成在线快速调试 Debug 开发包,执行如下命令: $ adb reverse tcp:8081 tcp:8081 $ react-native start //开启本地 JS 服务 $ react-native run-android //新终端的 RNPolymerPo 目录下执行 编译生成 release 包,执行如下命令: $ cd android && ./gradlew assembleRelease 拓展规划 下一个版本准备做的事情: 兼容性处理; 夜间模式; 热修复及 PHP 服务端编写; 多语言切换等问题评估;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值