前面两篇博客,详细分析了Native与Javascript通信的过程,可以满足绝大部分场景下Native和Javascript的相互调用,但是仍然有不健全的情况。
比如Javascript层要实时获取Native的一些状态,就需要Native被动地向Javascript层通信了。这个过程区别于通信第一篇中Native主动向Javascript层通信,本篇博客就来研究下这样一个被动回调的过程!
在阅读本篇博客前,希望能回顾下前两篇。
React-Native系列Android——Native与Javascript通信原理(一)
http://www.jb51.cc/article/p-ahzjgpxg-up.html
React-Native系列Android——Native与Javascript通信原理(二)
http://www.jb51.cc/article/p-wdwkkrny-up.html
首先,从一个常用的场景开始分析。
假设前端开发者在Javascript的代码中想要获取APP的状态,比如APP是否是处于前台(active),还是后台(background)。大概有两种实现方式:
1、Native 在APP每次状态切换的时候,调用callFunction将最新的状态传给Javascript层,然后由Javascript缓存起来,这样开发者想要获取状态可以能直接使用这个缓存的值。
2、前端开发者在Javascript中想要获取状态时,先向Native端发起通信请求,表示想获取状态,然后由Native端把这个状态作为通信应答返给Javascript层。
这两种方案都有各自的使用性场景,并且在React-Native都有相应实现。第一种实现对开发者来说相对简单,直接取缓存值,是一个完全同步的过程。第二种实现向Native发起通信请求,需要等待Native的应答,是一个异步的过程。
第一种方案实现原理在React-Native系列Android——Native与Javascript通信原理(一)中已经详细分析过了,不再赘述,本篇博文重点来分析下第二种方案的实现原理。
1、JavaScript的请求
Native与JavaScript的通信,都是由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变量指logError,secondLastArg变量指回调函数。
所以调用__nativeCall函数时候传递的两个参数onFail和onSucc,就分别指回调函数和logError。这里明显是React-Native的命名bug了,差点以为是两个变量解析颠倒了,不过不影响整个流程(原因是Native代码中解析参数时默认是onSucc在前面,又颠倒回来了,后面会分析到)。
接下来看__nativeCall
__nativeCall(module,params,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<Callback> ARGUMENT_EXTRACTOR_CALLBACK =
new ArgumentExtractor<Callback>() {
@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生命周期变化的时候,会更新状态到mAppState,createAppStateEventMap()将mAppState封装在用于Native-Bridge间传递的WritableMap对象中。然后调用了success.invoke(),而这个Callback类型的 success参数就是前面ARGUMENT_EXTRACTOR_CALLBACK构造出来的CallbackImpl对象了,它内存保存着用于回调的标识callbackID。
所以,来看CallbackImpl的invoke方法,代码在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,通过前面两篇博文我们知道CatalystInstance是Native向Javascript通信的入口,那么这里很明显其CatalystInstance.invokeCallback就是Native对Javascript的应答了。里面包含了标识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方法,里面有两个参数:callbackID和arguments。callbackID是来自Javascript端的通信回调标识,arguments是Native应答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<CountableBridge>(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();
}
}
调用的又是CountableBridge即Bridge对象的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<bool> isDestroyed = m_destroyed;
executorMessageQueueThread->runOnQueue([=] () {
...
JSExecutor *executor = getExecutor(executorToken);
if (executor == nullptr) {
...
return;
}
...
executor->invokeCallback(callbackId,arguments);
});
}
在executorMessageQueueThread队列线程里面,执行的是JSExecutor的invokeCallback方法。
继续来看react\JSCExecutor.cpp
void JSCExecutor::invokeCallback(const double callbackId,const folly::dynamic& arguments) {
std::vector<folly::dynamic> 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<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();
}
这段代码和callFunction非常相似,只不过executeJSCallWithJSC里面第二个参数换成了invokeCallbackAndReturnFlushedQueue。
这一段生成的Javascript执行语句是
__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null,jsonArgs);
jsonArgs中包含callbackID和arguments,Webkit执行这段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.js的invokeCallbackAndReturnFlushedQueue方法。
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最低位置0,cbID | 1最低位置1,这样无论cbID标识是onFail还是onSucc的索引,都能保证两者完全清除。
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获取到存储的回调方法,然后执行。
流程图表示如下:
本博客不定期持续更新,欢迎关注和交流: