React-Native系列Android——Native与Javascript通信原理(三)
来源:互联网 发布:炉石传说淘宝买卡包 编辑:程序博客网 时间:2024/04/30 12:08
前面两篇博客,详细分析了Native与Javascript通信的过程,可以满足绝大部分场景下Native和Javascript的相互调用,但是仍然有不健全的情况。
比如Javascript层要实时获取Native的一些状态,就需要Native被动地向Javascript层通信了。这个过程区别于通信第一篇中Native主动向Javascript层通信,本篇博客就来研究下这样一个被动回调的过程!
在阅读本篇博客前,希望能回顾下前两篇。
React-Native系列Android——Native与Javascript通信原理(一)
http://blog.csdn.net/megatronkings/article/details/51114278
React-Native系列Android——Native与Javascript通信原理(二)
http://blog.csdn.net/megatronkings/article/details/51138499
首先,从一个常用的场景开始分析。
假设前端开发者在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, 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<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的索引,都能保证两者完全清除。
获取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
- React-Native系列Android——Native与Javascript通信原理(三)
- React-Native系列Android——Native与Javascript通信原理(一)
- React-Native系列Android——Native与Javascript通信原理(一)
- React-Native系列Android——Native与Javascript通信原理(二)
- React-Native系列Android——通信数据模型分析
- React-Native 与 Android 集成 <三、原理与总结>
- 【React Native】组件的声明周期与通信(三)
- React Native与Android通信——Android calls JS(一)0.45
- React Native与Android通信交互
- React Native 实战系列三
- React Native Android入门实战及深入源码分析系列(2)——React Native源码编译
- React-Native系列Android——Touch事件原理及状态效果
- React-Native系列Android——Touch事件原理及状态效果
- 【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信
- React-Native系列Android——Javascript文件加载过程分析
- React Native 混合编程 之与原生平台通信原理
- React Native for android——React Native 介绍
- React-Native系列Android——自定义View组件开发
- [LeetCode] 343. Integer Break
- 记录开发中listview的动态显示
- 202. Happy Number
- httpclient 简单实例
- Android 多点touch触控事件传递
- React-Native系列Android——Native与Javascript通信原理(三)
- QMenu的个性化定制
- 诺亚方舟实验室李航:深度学习还局限在复杂的模式识别上
- 理解DrawerLayout抽屉
- java并发编程实践学习---java的类锁和对象锁
- web.xml 配置中classpath: 与classpath*:的区别
- 黑盒测试的几个实测试设计
- 自己写个AsyncTask
- 基于大数据与深度学习的自然语言对话