React-Native series Android-Principles of communication between Native and Javascript (III)
The previous two blogs have analyzed in detailNativeAndJavascriptThe communication process can meet the needs of most scenarios.NativeAndJavascriptBut there is still a sound situation.
For exampleJavascriptReal-time retrieval of LayerNativeYou needNativePassive OrientationJavascriptLayer communication. This process is different from communication in Article 1NativeProactiveJavascriptLayer-7 communication, this blog will study such a passive callback process!
First, analyze from a common scenario.
Assume that the front-end developerJavascriptTo obtainAPPSuchAPPWhether it is in the foreground (Active), Or back-end (Background). There are two possible implementation methods:
1,NativeInAPPCallCallFunctionSend the latest statusJavascriptLayerJavascriptCache, so that developers can directly use the cached value to obtain the status.
2. Front-end developersJavascriptTo obtain the statusNativeThe client initiates a communication request to obtain the status.NativeThis status is returned as a communication responseJavascriptLayer.
Both solutions have their own application scenarios.React-NativeAre implemented accordingly. The first implementation is relatively simple for developers. It is a completely synchronous process to directly retrieve the cache value. Second Implementation directionNativeInitiate a Communication Request and waitNativeIs an asynchronous process.
The principle of the first solution is as follows:React-Native series Android-communication between Native and Javascript (I)This article focuses on the Implementation Principles of the second solution.
1. JavaScriptRequest
NativeAndJavaScriptThe communication is composedNativeAnd thenJavaScriptResponse,JavaScriptCannot forwardNativeActively initiate communication. So,JavaScriptHow to forwardNativeInitiate a communication request?
As mentioned in the previous blog,JavaScriptResponseNativeBy packaging the response dataJSONFormat, and thenFlushedQueue ()ReturnBridgeReturnNative. IfJavaScriptAdd the ID of the Communication Request to the response information.NativeWhen parsing the response information, it was found thatJavaScriptAnd thenNativeTo respond to this request.JavaScriptRequestNative?
Step 1: Add a JavaScript Communication Request to the response message returned to Native
SimilarlyJavascript.APPThe sample code is as follows:
var NativeModules = require('NativeModules');var RCTAppState = NativeModules.AppState;var logError = require('logError');RCTAppState.getCurrentAppState( (appStateData) => { console.log('dev', 'current state: ' + appStateData.app_state); }, logError);
Analyzed in the previous blogNativeModulesIn the past and present, it is a class that is dynamically initialized (For details, refer to the previous article about the communication principle between Native and Javascript (II), which is skipped here ),RCTAppState. getCurrentAppStateActually, the call isMessageQueue. jsThe following code:
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);};
ParametersArgsThere are two concrete expressions. One isLambdaExpression callback function. One isLogError, AllFunctionType. ResolutionLastArgVariable refersLogError,SecondLastArgA variable is a callback function.
Therefore_ NativeCallTwo parameters passed during the FunctionOnFailAndOnSuccThe callback function andLogError. Here is obviouslyReact-NativeNameBugI almost thought it was the reverse resolution of the two variables, but it didn't affect the entire process (the reason isNativeWhen parsing parameters in the Code, the default value isOnSuccIn front, it is reversed and will be analyzed later ).
Next let's take a look_ 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. _ queueIs used to save the response.NativeHere we mainly look at the dataIfThe judgment logic.
This. _ callbackIDYesThis. _ callbacksThe index of the set to identify the callback function, and the index will be placed inParamsPassedNativeTerminal,NativeThe index is returned when the client responds.JavascriptEnd, this wayJavascriptYou can find the data stored inThis. _ callbacksThe callback function in the set. So,This. _ callbackIDYesJavascriptRequestNative.
Step 2: How does Native respond to Javascript
Another step in the middleFlushedQueue ()DirectionBridgeFor the transfer process of the layer, refer to the previous article. Skip this section.
Analyzed in the previous blogNativeProcess fromJavascriptThe response information isModuleID + methodIDMap to specificNativeModuleComponent method, parse the parameters, and finally passInvokeReflection Method to complete the call.
In the example, obtainAPPThe component in the current status isNativeCorrespondingNativeModuleClass isAppStateModule. The method mapped to isGetCurrentAppStateIt has twoCallbackType parameter.
Let's see.NativeModuleAnalysisCallbackType parameter code, which is located in its parent classCom. facebook. react. bridge. BaseJavaModule. javaMedium
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; }
ForCallbackType parameter. The extracted parameter isARGUMENT_EXTRACTOR_CALLBACK, In itsExtractArgumentMethodJavascriptFrom the terminalCallbackID, ConstructorCallbackImplObject. And this constructedCallbackImplObject isInvokeReflectionGetCurrentAppStateParameters in the method.
Next let's take a look at the reflectedGetCurrentAppStateMethod, located inCom. 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; } ...}
WhenActivityWhen the lifecycle changes, the status will be updatedMAppState,CreateAppStateEventMap ()SetMAppStateEncapsulated inNative-BridgeInter-passWritableMapObject. ThenSuccess. invoke (), And thisCallbackTypeSuccessThe preceding ParameterARGUMENT_EXTRACTOR_CALLBACKConstructedCallbackImplObject. The memory stores the identifier used for callback.CallbackID.
So, let's seeCallbackImplOfInvokeMethod, the code inCom. 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)); }}
ItsInvokeThe method is called again.CatalystInstance. invokeCallbackWe know from the previous two blog postsCatalystInstanceYesNativeDirectionJavascriptCommunication entry, which is obvious hereCatalystInstance. invokeCallbackYesNativePairJavascript. It contains the identifierCallbackIDAnd content dataMAppState.
InCatalystInstanceImplementation classCatalystInstanceImplInternal and passReactBridgeCallJNIThis is the same
React-Native series Android-communication between Native and Javascript (I)InCallFunctionThe principle is the same.
public class CatalystInstanceImpl implements CatalystInstance { ... public void invokeCallback(final int callbackID, final NativeArray arguments) { ... Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments); ... } ...}
Step 3: Bridge transit
Passed in the previous stepJNICalledInvokeCallbackMethod, which has two parameters:CallbackIDAndArguments.CallbackIDYes fromJavascriptCommunication callback ID of the client,ArgumentsYesNativeResponseJavascriptRequest content.BridgeIs to transfer the two parametersJavascript.
BridgeThe call entry for the layer isReact \ jni \ OnLoad. cppLet's take a look.InvokeCallbackMethod
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(); }}
The call isCountableBridgeThat isBridgeObjectInvokeCallbackMethod, the code inReact \ Bridge. cppMedium
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); });}
InExecutorMessageQueueThreadIn the queue thread, the execution isJSExecutorOfInvokeCallbackMethod.
ContinueReact \ 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();}
This code andCallFunctionVery similar,ExecuteJSCallWithJSCThe second parameter is replacedInvokeCallbackAndReturnFlushedQueue.
This section generatesJavascriptThe execution statement is
__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);
JsonArgsContainsCallbackIDAndArguments,WebkitExecute this sectionJavascriptThe statement reaches the connectionJavascriptEnd.
Of courseJavascriptThere is alsoResultReturn, used to callCallNativeModulesAs a subsequent communication request, the process is exactly the same as that in the previous article!
Step 4: Javascript receives Native responses
ReferenceReact-Native series Android-communication between Native and Javascript (I), In the previous stepBridgeCreatedJavascriptExecution statement
__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);
Actually equivalent
MessageQueue.invokeCallbackAndReturnFlushedQueue.apply(null, callbackID, args);
So the execution isMessageQueue. jsOfInvokeCallbackAndReturnFlushedQueueMethod.
invokeCallbackAndReturnFlushedQueue(cbID, args) { guard(() => { this.__invokeCallback(cbID, args); this.__callImmediates(); }); return this.flushedQueue(); }
HereCbIDActuallyCallbackIDThat is, in step 1This. _ callbackID. This value is composedJavascriptPassNativeFrom now onNativeBack to Zhao!
Which of the following isThis. _ invokeCallback
__invokeCallback(cbID, args) { ... let callback = this._callbacks[cbID]; ... this._callbacks[cbID & ~1] = null; this._callbacks[cbID | 1] = null; callback.apply(null, args); ... }
This. _ callbacksSetCallbackIDStores the callback function for the index.Callback, You can useCbIDThis index isKeyExtracted. RunCallback. apply (null, args)The callback function is executed.
Also, clearThis. _ callbacksThe callback function saved in the set. Because_ NativeCallWhen the callback function is encapsulated, two callback functions are saved successively.OnFail(The index is an even number) andOnSucc(The index is odd, more than the former), and the obtainedCallbackAre you sure you wantOnFailOrOnSucc. So,CbID &~ 1Lowest position0,CbID | 1Lowest position1, RegardlessCbIDID isOnFailOrOnSuccBoth of them can be completely cleared.
ObtainAPPThe callback function in the status example is
function(appStateData){ console.log('dev', 'current state: ' + appStateData.app_state);}
App_stateThe variable value is the currentAPPAndAppStateModuleThe encapsulation of values in exactly echo
private WritableMap createAppStateEventMap() { WritableMap appState = Arguments.createMap(); appState.putString("app_state", mAppState); return appState;}
In this way, the entire communication process is almost complete here.
Summary
JavascriptRequestNativeReset againJavascriptThe process is as follows:
TotalJavascript> Bridge> Native> Bridge> JavascriptFive steps,CallbackIDIs the key point of the entire process.
JavascriptRequestNative, Mr ChengCallbackID, AndCallbackIDStores the callback function for the unique key.CallbackIDUploaded as the response content of the previous communication requestNativeTerminal,NativeReceived through reflectionNativeModuleAnd thenCallbackIDAnd return the processing resultJavascriptTerminal,JavascriptUseCallbackIDObtain the stored callback method and execute it.
The flowchart is as follows: