React-Native系列Android——Native与Javascript通信原理(三)
前面两篇博客,详细分析了Native与Javascript通信的过程,可以满足绝大部分场景下Native和Javascript的相互调用,但是仍然有不健全的情况。 比如Javascript层要实时获取Native的一些状态,就需要Native被动地向Javascript层通信了。这个过程区别于通信第一篇中Native主动向Javascript层通信,本篇博客就来研究下这样一个被动回调的过程! 在阅读本篇博客前,希望能回顾下前两篇。 React-Native系列Android——Native与Javascript通信原理(一) React-Native系列Android——Native与Javascript通信原理(二) 首先,从一个常用的场景开始分析。 假设前端开发者在Javascript的代码中想要获取APP的状态,比如APP是否是处于前台(active),还是后台(background)。大概有两种实现方式: 这两种方案都有各自的使用性场景,并且在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的,这一点同 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层的调用入口是reactjniOnLoad.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方法,代码在reactBridge.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方法。 继续来看reactJSCExecutor.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的索引,都能保证两者完全清除。 获取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获取到存储的回调方法,然后执行。 流程图表示如下: 本博客不定期持续更新,欢迎关注和交流: http://blog.csdn.net/megatronkings (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |