欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

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

来源: 开发者 投稿于  被查看 19944 次 评论:208

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


前面两篇博客,详细分析了NativeJavascript通信的过程,可以满足绝大部分场景下NativeJavascript的相互调用,但是仍然有不健全的情况。

比如Javascript层要实时获取Native的一些状态,就需要Native被动地向Javascript层通信了。这个过程区别于通信第一篇中Native主动向Javascript层通信,本篇博客就来研究下这样一个被动回调的过程!

首先,从一个常用的场景开始分析。

假设前端开发者在Javascript的代码中想要获取APP的状态,比如APP是否是处于前台(active),还是后台(background)。大概有两种实现方式:
1、NativeAPP每次状态切换的时候,调用callFunction将最新的状态传给Javascript层,然后由Javascript缓存起来,这样开发者想要获取状态可以能直接使用这个缓存的值。
2、前端开发者在Javascript中想要获取状态时,先向Native端发起通信请求,表示想获取状态,然后由Native端把这个状态作为通信应答返给Javascript层。

这两种方案都有各自的使用性场景,并且在React-Native都有相应实现。第一种实现对开发者来说相对简单,直接取缓存值,是一个完全同步的过程。第二种实现向Native发起通信请求,需要等待Native的应答,是一个异步的过程。

第一种方案实现原理在React-Native系列Android——Native与Javascript通信原理(一)中已经详细分析过了,不再赘述,本篇博文重点来分析下第二种方案的实现原理。


1、JavaScript的请求

NativeJavaScript的通信,都是由Native主动发起,然后由JavaScript应答,但是JavaScript是无法向Native主动发起通信的。那么,JavaScript如何才能向Native发起通信请求呢?

上一篇博文中讲过,JavaScript应答Native是通过将应答数据包装成JSON格式,然后在flushedQueue() 返给Bridge再返给Native的。如果JavaScript在这个应答信息加入通信请求的标识,那么Native在解析应答信息时发现了其中包含JavaScript的通信标识,然后Native来应答这个请求,这样不就完成了一次JavaScript请求Native的过程吗?

第一步:在返给Native的应答信息中加入JavaScript的通信请求

同样以在Javascript中获取APP当前状态为例,示范代码如下:

var NativeModules = require('NativeModules');

var RCTAppState = NativeModules.AppState;

var logError = require('logError');

RCTAppState.getCurrentAppState(
  (appStateData) => {
     console.log('dev', 'current state: ' + appStateData.app_state);
  },
  logError
);

前一篇博文中分析过NativeModules的前世今生,它是一个动态初始化的类(具体请看前篇Native与Javascript通信原理(二),这里略过),RCTAppState.getCurrentAppState实际上是调用的是MessageQueue.js的下面这段代码:

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

这里面参数args具体化有两个,一个是lambda表达式回调函数,一个是logError,都是function类型。解析的时候lastArg变量指logErrorsecondLastArg变量指回调函数。

所以调用__nativeCall函数时候传递的两个参数onFailonSucc,就分别指回调函数和logError。这里明显是React-Native的命名bug了,差点以为是两个变量解析颠倒了,不过不影响整个流程(原因是Native代码中解析参数时默认是onSucc在前面,又颠倒回来了,后面会分析到)。

接下来看__nativeCall

__nativeCall(module, method, params, onFail, onSucc) {

    if (onFail || onSucc) {
      ...
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }

    ...

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(params);

    ...

  }

this._queue的作用上篇分析过,是用来保存应答Native的数据的,这里主要来看if里面的判断逻辑。

this._callbackID是作为this._callbacks集合的索引来标识回调函数的,同时这个索引会放到params里面传递给Native端,Native端应答的时候会将这个索引传回到Javascript端,这样Javascript端就能通过索引找到事先存放在this._callbacks集合里的回调函数了。所以,this._callbackID就是Javascript请求Native的标识了。

第二步:Native如何应答Javascript端

中间还有一步flushedQueue()Bridge层的传递过程,参考前文即可,这里跳过。

前篇博文中分析过Native处理来自Javascript应答信息,都是通过moduleID+methodID映射到具体NativeModule组件的方法,然后解析参数,最后通过invoke反射方式完成调用的。

例子中,获取APP当前状态的组件在Native端对应的NativeModule类是AppStateModule。被映射到的方法是getCurrentAppState,它有两个Callback类型的参数。

来看看NativeModule解析Callback类型参数时的代码,位于其父类com.facebook.react.bridge.BaseJavaModule.java

  static final private ArgumentExtractor ARGUMENT_EXTRACTOR_CALLBACK =
      new ArgumentExtractor() {
        @Override
        public @Nullable Callback extractArgument(
            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
          if (jsArguments.isNull(atIndex)) {
            return null;
          } else {
            int id = (int) jsArguments.getDouble(atIndex);
            return new CallbackImpl(catalystInstance, id);
          }
        }
      };
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
      ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
      for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
        Class argumentClass = paramTypes[i];
        ...
        if (argumentClass == Callback.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
        }
        ...
      }
      return argumentExtractors;
    }

对于Callback类型参数,使用的参数提取器是ARGUMENT_EXTRACTOR_CALLBACK,在其extractArgument方法里面提取出由Javascript端传来的callbackID,构造进CallbackImpl对象里面。而这个构造出来的CallbackImpl对象,就是invoke反射getCurrentAppState方法里的参数了。

下面来看一下被反射的getCurrentAppState方法,位于com.facebook.react.modules.appstate.AppStateModule.java

public class AppStateModule extends ReactContextBaseJavaModule
        implements LifecycleEventListener {

  public static final String APP_STATE_ACTIVE = "active";
  public static final String APP_STATE_BACKGROUND = "background";

  private String mAppState = "uninitialized";

  public AppStateModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public String getName() {
    return "AppState";
  }

  @Override
  public void initialize() {
    getReactApplicationContext().addLifecycleEventListener(this);
  }

  @ReactMethod
  public void getCurrentAppState(Callback success, Callback error) {
    success.invoke(createAppStateEventMap());
  }

  @Override
  public void onHostResume() {
    mAppState = APP_STATE_ACTIVE;
    sendAppStateChangeEvent();
  }

  @Override
  public void onHostPause() {
    mAppState = APP_STATE_BACKGROUND;
    sendAppStateChangeEvent();
  }

  ...

  private WritableMap createAppStateEventMap() {
    WritableMap appState = Arguments.createMap();
    appState.putString("app_state", mAppState);
    return appState;
  }

  ...
}

Activity生命周期变化的时候,会更新状态到mAppStatecreateAppStateEventMap()mAppState封装在用于Native-Bridge间传递的WritableMap对象中。然后调用了success.invoke(),而这个Callback类型的 success参数就是前面ARGUMENT_EXTRACTOR_CALLBACK构造出来的CallbackImpl对象了,它内存保存着用于回调的标识callbackID

所以,来看CallbackImplinvoke方法,代码在com.facebook.react.bridge.CallbackImpl.java

public final class CallbackImpl implements Callback {

  private final CatalystInstance mCatalystInstance;
  private final int mCallbackId;

  public CallbackImpl(CatalystInstance bridge, int callbackId) {
    mCatalystInstance = bridge;
    mCallbackId = callbackId;
  }

  @Override
  public void invoke(Object... args) {
    mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
  }
}

invoke方法里面又调用了CatalystInstance.invokeCallback,通过前面两篇博文我们知道CatalystInstanceNativeJavascript通信的入口,那么这里很明显其CatalystInstance.invokeCallback就是NativeJavascript的应答了。里面包含了标识callbackID和内容数据mAppState

CatalystInstance的实现类CatalystInstanceImpl内部,又是通过ReactBridge调用JNI的,这一点同
React-Native系列Android——Native与Javascript通信原理(一)中的callFunction原理完全一样。

public class CatalystInstanceImpl implements CatalystInstance {
   ...

   public void invokeCallback(final int callbackID, final NativeArray arguments) {
       ...
       Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
       ...
   }

   ...
}

第三步:Bridge的中转

上一步中通过JNI调用了invokeCallback方法,里面有两个参数:callbackIDargumentscallbackID是来自Javascript端的通信回调标识,argumentsNative应答Javascript请求的内容。Bridge的作用就是将这两个参数中转到Javascript端。

Bridge层的调用入口是react\jni\OnLoad.cpp,先来瞧瞧invokeCallback方法

static void invokeCallback(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint callbackId,
                           NativeArray::jhybridobject args) {
  auto bridge = extractRefPtr(env, obj);
  auto arguments = cthis(wrap_alias(args));
  try {
    bridge->invokeCallback(
      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
      (double) callbackId,
      std::move(arguments->array)
    );
  } catch (...) {
    translatePendingCppExceptionToJavaException();
  }
}

调用的又是CountableBridgeBridge对象的invokeCallback方法,代码在react\Bridge.cpp

void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {
  ...

  auto executorMessageQueueThread = getMessageQueueThread(executorToken);
  if (executorMessageQueueThread == nullptr) {
    ...
    return;
  }

  std::shared_ptr isDestroyed = m_destroyed;
  executorMessageQueueThread->runOnQueue([=] () {
    ...

    JSExecutor *executor = getExecutor(executorToken);
    if (executor == nullptr) {
      ...
      return;
    }
    ...
    executor->invokeCallback(callbackId, arguments);
  });
}

executorMessageQueueThread队列线程里面,执行的是JSExecutorinvokeCallback方法。

继续来看react\JSCExecutor.cpp

void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
  std::vector call{
    (double) callbackId,
    std::move(arguments)
  };
  std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}

static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector& arguments) {
  ...

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

这段代码和callFunction非常相似,只不过executeJSCallWithJSC里面第二个参数换成了invokeCallbackAndReturnFlushedQueue

这一段生成的Javascript执行语句是

__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);

jsonArgs中包含callbackIDargumentsWebkit执行这段Javascript语句达到连接到Javascript端的目的。

当然,执行完Javascript语句后也有一个result返回,用来调用callNativeModules,作为后续的通信请求,流程和前篇完全一致!

第四步:Javascript接收Native的应答

参考React-Native系列Android——Native与Javascript通信原理(一),上一步中Bridge创建的Javascript执行语句

__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);

其实等同于

MessageQueue.invokeCallbackAndReturnFlushedQueue.apply(null, callbackID, args);

所以执行的是MessageQueue.jsinvokeCallbackAndReturnFlushedQueue方法。

  invokeCallbackAndReturnFlushedQueue(cbID, args) {
    guard(() => {
      this.__invokeCallback(cbID, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

这里的cbID其实就是callbackID了,也就是第一步里面的this._callbackID。这个值是由Javascript传给Native的,现在又从Native传回来了,完璧归赵啊!

下面调用的是this.__invokeCallback

  __invokeCallback(cbID, args) {
    ...
    let callback = this._callbacks[cbID];
    ...
    this._callbacks[cbID & ~1] = null;
    this._callbacks[cbID |  1] = null;
    callback.apply(null, args);
    ...
  }

this._callbacks集合里面以callbackID为索引保存着回调函数callback,这里就可以通过cbID这个索引为key取出来了。这样执行callback.apply(null, args)就等于执行回调函数了。

同时,还要清除this._callbacks集合里面保存的回调函数。由于__nativeCall中封装回调函数时,先后保存了两个回调函数onFail(索引为偶数)和onSucc(索引为奇数,比前者+1),而取出来的callback并不确定是onFail还是onSucc。所以,cbID & ~1最低位置0cbID | 1最低位置1,这样无论cbID标识是onFail还是onSucc的索引,都能保证两者完全清除。

获取APP状态例子中的回调函数是

function(appStateData){
   console.log('dev', 'current state: ' + appStateData.app_state);
}

app_state变量的值就是当前APP的状态了,与AppStateModule中的值的封装恰好呼应

private WritableMap createAppStateEventMap() {
    WritableMap appState = Arguments.createMap();
    appState.putString("app_state", mAppState);
    return appState;
}

这样,整个通信流程差不多就到此完整了。


总结

Javascript请求Native再回调到Javascript中,一共经历了如下流程:

一共Javascript->Bridge->Native->Bridge->Javascript五个步骤,callbackID是整个流程的关键点。

Javascript请求Native,需要先生成callbackID,并以callbackID为唯一键存储回调函数。callbackID作为上一次通信请求的应答内容传到Native端,Native接收到后通过反射NativeModule的处理方法,然后将callbackID及处理结果返给Javascript端,Javascript使用callbackID获取到存储的回调方法,然后执行。

流程图表示如下:
\


 

用户评论