本篇文章已授權微信公眾號 JueCode 獨家釋出
小夥伴們都知道在Android開發中實現Java和JS的通訊可以通過WebView來實現,包括註冊JSBridge或者在介面中攔截都可以。然而React Native中並沒有用WebView控制元件的方式,而是基於WebKit核心的方式來實現Java與JS的通訊,也就是Java與JS之間的通訊都是通過中間層C++來實現的,今天我們來分析下Android中React Native怎麼實現Java和JS之間的通訊。
React Native中呼叫所有的行為都是從Native端發起的,使用者操作直接面向的也是Native。所以這個通訊模型又可以看成是Native發起會話,然後Javascript進行應答。
關於JavaScript呼叫Java,可以參考我的另外一篇部落格:React Native通訊原理原始碼分析二
Java要能呼叫到JS需要在Java層註冊JS模組,先來看下JS模組的註冊過程。
1.JavaScriptModule模組註冊(Java層)
系統註冊了一些常用的JS模組,在CoreModulesPackage的createJSModules中註冊了一些系統核心的JS模組,比如AppRegistry(RN元件註冊模組)/RCTEventEmitter(事件發射模組)等。
class CoreModulesPackage implements ReactPackage{
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
......
return Arrays.<NativeModule>asList(
new AnimationsDebugModule(
catalystApplicationContext,
mReactInstanceManager.getDevSupportManager().getDevSettings()),
new AndroidInfoModule(),
new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
new Timing(catalystApplicationContext),
new SourceCodeModule(
mReactInstanceManager.getSourceUrl(),
mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
uiManagerModule,
new DebugComponentOwnershipModule(catalystApplicationContext));
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
RCTNativeAppEventEmitter.class,
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
}
public interface ReactPackage {
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
List<NativeModule> createNativeModules(ReactApplicationContext reactContext);
/**
* @return list of JS modules to register with the newly created catalyst instance.
*
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
List<Class<? extends JavaScriptModule>> createJSModules();
/**
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
複製程式碼
以RCTEventEmitter這個JavaScriptModule模組為例來看下JS模組在Java層的實現。
所有JS層元件實現JavaScriptModule介面,比如RCTEventEmitter:
public interface RCTEventEmitter extends JavaScriptModule {
public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
public void receiveTouches(
String eventName,
WritableArray touches,
WritableArray changedIndices);
}
複製程式碼
可以看出來JS模組在Java層只是簡單的繼承JavaScriptModule,並沒有具體的實現,那麼問題就來了,光是一個介面是沒法呼叫的,好像到這裡就跟蹤不下去了?其實可以換種思路,JS模組肯定是要在Native中註冊的,我們先到ReactInstanceManagerImpl中的createReactContext方法中去看下怎麼註冊的。
所有的NativeModule和JSModule在ReactInstanceManagerImpl中註冊
方法的程式碼比較多,但是邏輯不復雜,關鍵的幾個步驟我都在程式碼中直接做了註釋,就不多說了,這裡主要分析JS模組的註冊,所以我們看到JavaScriptModulesConfig中。
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
FLog.i(ReactConstants.TAG, "Creating react context.");
//Native模組
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
//JS模組
JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
//React Context
ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
//處理CoreModules---包含Native模組和JS模組
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCoreModulesPackage");
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
// 處理使用者註冊的模組
for (ReactPackage reactPackage : mPackages) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
//buildNativeModuleRegistry
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
NativeModuleRegistry nativeModuleRegistry;
try {
nativeModuleRegistry = nativeRegistryBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
//buildJSModuleConfig
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig");
JavaScriptModulesConfig javaScriptModulesConfig;
try {
javaScriptModulesConfig = jsModulesBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
...
//createCatalystInstance---Java/JS/C++三方通訊總管
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModulesConfig(javaScriptModulesConfig)
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
reactContext.initializeWithInstance(catalystInstance);
//runJSBundle
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
try {
catalystInstance.runJSBundle();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
ReactMarker.logMarker("CREATE_REACT_CONTEXT_END");
return reactContext;
}
複製程式碼
看到JavaScriptModulesConfig,主要是對每個JavaScriptModule構造JavaScriptModuleRegistration,存放在mModules中
/**
* Class stores configuration of javascript modules that can be used across the bridge
*/
public class JavaScriptModulesConfig {
private final List<JavaScriptModuleRegistration> mModules;
private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
mModules = modules;
}
/*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
return mModules;
}
/*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
jg.writeStartObject();
for (JavaScriptModuleRegistration registration : mModules) {
jg.writeObjectFieldStart(registration.getName());
appendJSModuleToJSONObject(jg, registration);
jg.writeEndObject();
}
jg.writeEndObject();
}
private void appendJSModuleToJSONObject(
JsonGenerator jg,
JavaScriptModuleRegistration registration) throws IOException {
jg.writeObjectField("moduleID", registration.getModuleId());
jg.writeObjectFieldStart("methods");
for (Method method : registration.getMethods()) {
jg.writeObjectFieldStart(method.getName());
jg.writeObjectField("methodID", registration.getMethodId(method));
jg.writeEndObject();
}
jg.writeEndObject();
}
public static class Builder {
private int mLastJSModuleId = 0;
private List<JavaScriptModuleRegistration> mModules =
new ArrayList<JavaScriptModuleRegistration>();
public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
int moduleId = mLastJSModuleId++;
mModules.add(new JavaScriptModuleRegistration(moduleId, moduleInterfaceClass));
return this;
}
public JavaScriptModulesConfig build() {
return new JavaScriptModulesConfig(mModules);
}
}
}
複製程式碼
/**
* Registration info for a {@link JavaScriptModule}.
Maps its methods to method ids.
*/
class JavaScriptModuleRegistration {
private final int mModuleId;
private final Class<? extends JavaScriptModule> mModuleInterface;
private final Map<Method, Integer> mMethodsToIds;
private final Map<Method, String> mMethodsToTracingNames;
JavaScriptModuleRegistration(int moduleId, Class<? extends JavaScriptModule> moduleInterface) {
mModuleId = moduleId;
mModuleInterface = moduleInterface;
mMethodsToIds = MapBuilder.newHashMap();
mMethodsToTracingNames = MapBuilder.newHashMap();
final Method[] declaredMethods = mModuleInterface.getDeclaredMethods();
Arrays.sort(declaredMethods, new Comparator<Method>() {
@Override
public int compare(Method lhs, Method rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
// Methods are sorted by name so we can dupe check and have obvious ordering
String previousName = null;
for (int i = 0; i < declaredMethods.length; i++) {
Method method = declaredMethods[i];
String name = method.getName();
Assertions.assertCondition(
!name.equals(previousName),
"Method overloading is unsupported: " + mModuleInterface.getName() + "#" + name);
previousName = name;
mMethodsToIds.put(method, i);
mMethodsToTracingNames.put(method, "JSCall__" + getName() + "_" + method.getName());
}
}
......
}
複製程式碼
到這裡就有了所有的JavaScriptModules,並且都掃描存放在JavaScriptModulesConfig中,那麼RN框架是怎麼使用的?再回顧前面ReactInstanceManagerImpl,將JavaScriptModulesConfig用來例項化catalystInstance,這個是三方通訊的中轉站。
//createCatalystInstance---Java/JS/C++三方通訊總管
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModulesConfig(javaScriptModulesConfig)
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
複製程式碼
catalystInstance是一個介面,實現類是CatalystInstanceImpl,跟到建構函式中:
private CatalystInstanceImpl(
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
...
mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
...
}
複製程式碼
通過JavaScriptModulesConfig例項化JavaScriptModuleRegistry,前面分析過JavaScriptModule在Java層都是implements JavaScriptModule的介面,那麼怎麼去呼叫方法?這裡就是對每個介面通過動態代理例項化一個代理物件,Java呼叫JS方法時統一分發到JavaScriptModuleInvocationHandler中的invoke進行處理,在invoke中其實就是呼叫mCatalystInstance.callFunction
很明顯,接下來需要到JavaScriptModuleRegistry中看看:
/**
* Class responsible for holding all the {@link JavaScriptModule}s registered to this
* {@link CatalystInstance}.
Uses Java proxy objects to dispatch method calls on JavaScriptModules
* to the bridge using the corresponding module and method ids so the proper function is executed in
* JavaScript.
*/
/*package*/ class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
public JavaScriptModuleRegistry(
CatalystInstanceImpl instance,
JavaScriptModulesConfig config) {
mModuleInstances = new HashMap<>();
for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(instance, registration));
mModuleInstances.put(moduleInterface, interfaceProxy);
}
}
public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(
mModuleInstances.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
}
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstanceImpl mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstanceImpl catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}
}
}
複製程式碼
在Java呼叫JS模組的方法時,都會通過代理物件呼叫invoke方法,在invoke方法中呼叫CatalystInstance.callFunction,CatalystInstance是一個介面,實現類CatalystInstanceImpl,看到CatalystInstanceImpl中的callFunction:
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
final int traceID = mTraceID++;
Systrace.startAsyncFlow(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
tracingName,
traceID);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Systrace.endAsyncFlow(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
tracingName,
traceID);
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
try {
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
複製程式碼
所有Java層向Javascript層的通訊請求都是走的ReactBridge.callFunction
其實就是在JSQueueThread執行緒中呼叫ReactBridge的callFunction,是Native函式,到這裡就從Java轉到C++層了
/**
* Interface to the JS execution environment and means of transport for messages Java<->JS.
*/
@DoNotStrip
public class ReactBridge extends Countable {
/* package */ static final String REACT_NATIVE_LIB = "reactnativejni";
static {
SoLoader.loadLibrary(REACT_NATIVE_LIB);
}
private final ReactCallback mCallback;
private final JavaScriptExecutor mJSExecutor;
private final MessageQueueThread mNativeModulesQueueThread;
/**
* @param jsExecutor the JS executor to use to run JS
* @param callback the callback class used to invoke native modules
* @param nativeModulesQueueThread the MessageQueueThread the callbacks should be invoked on
*/
public ReactBridge(
JavaScriptExecutor jsExecutor,
ReactCallback callback,
MessageQueueThread nativeModulesQueueThread) {
mJSExecutor = jsExecutor;
mCallback = callback;
mNativeModulesQueueThread = nativeModulesQueueThread;
initialize(jsExecutor, callback, mNativeModulesQueueThread);
}
@Override
public void dispose() {
mJSExecutor.close();
mJSExecutor.dispose();
super.dispose();
}
public void handleMemoryPressure(MemoryPressure level) {
switch (level) {
case MODERATE:
handleMemoryPressureModerate();
break;
case CRITICAL:
handleMemoryPressureCritical();
break;
default:
throw new IllegalArgumentException("Unknown level: " + level);
}
}
private native void initialize(
JavaScriptExecutor jsExecutor,
ReactCallback callback,
MessageQueueThread nativeModulesQueueThread);
/**
* All native functions are not thread safe and appropriate queues should be used
*/
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
public native boolean supportsProfiling();
public native void startProfiler(String title);
public native void stopProfiler(String title, String filename);
private native void handleMemoryPressureModerate();
private native void handleMemoryPressureCritical();
}
複製程式碼
再回頭看看ReactBridge在CatalystInstanceImpl裡面的初始化,初始化會ReactBridge呼叫setGlobalVariable,這是個Native函式,是在C++層註冊用的,先按下後面再分析元件呼叫過程再分析,先看看 buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)做了什麼。
private CatalystInstanceImpl(
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
......
try {
mBridge = mCatalystQueueConfiguration.getJSQueueThread().callOnQueue(
new Callable<ReactBridge>() {
@Override
public ReactBridge call() throws Exception {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge");
try {
return initializeBridge(jsExecutor, jsModulesConfig);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}).get(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (Exception t) {
throw new RuntimeException("Failed to initialize bridge", t);
}
}
private ReactBridge initializeBridge(
JavaScriptExecutor jsExecutor,
JavaScriptModulesConfig jsModulesConfig) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor");
ReactBridge bridge;
try {
bridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig");
try {
bridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
bridge.setGlobalVariable(
"__RCTProfileIsProfiling",
Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false");
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
return bridge;
}
複製程式碼
在CatalystInstanceImpl中的實現,其實就是寫一個JSON字串,有兩個欄位"remoteModuleConfig"和"localModulesConfig",分別對應NativeModule(Native提供給JS呼叫)和JSModule(JS提供給Native呼叫)
private String buildModulesConfigJSONProperty(
NativeModuleRegistry nativeModuleRegistry,
JavaScriptModulesConfig jsModulesConfig) {
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
jg.writeFieldName("remoteModuleConfig");
nativeModuleRegistry.writeModuleDescriptions(jg);
jg.writeFieldName("localModulesConfig");
jsModulesConfig.writeModuleDescriptions(jg);
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
}
return writer.getBuffer().toString();
}
複製程式碼
再看看localModulesConfig都寫入了什麼,跟進去JavaScriptModulesConfig.java,就是往JSON字元創中先寫入介面名、moduleID、methods(方法名、methodID)等。
/*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
jg.writeStartObject();
for (JavaScriptModuleRegistration registration : mModules) {
jg.writeObjectFieldStart(registration.getName());
appendJSModuleToJSONObject(jg, registration);
jg.writeEndObject();
}
jg.writeEndObject();
}
private void appendJSModuleToJSONObject(
JsonGenerator jg,
JavaScriptModuleRegistration registration) throws IOException {
jg.writeObjectField("moduleID", registration.getModuleId());
jg.writeObjectFieldStart("methods");
for (Method method : registration.getMethods()) {
jg.writeObjectFieldStart(method.getName());
jg.writeObjectField("methodID", registration.getMethodId(method));
jg.writeEndObject();
}
jg.writeEndObject();
}
複製程式碼
從initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions
整個過程作用是將所有JavaScriptModule的資訊生成JSON字串預先儲存到Bridge中, 通過methodID標識作為key存入JSON生成器中,用來最終生成JSON字串:
其中setGlobalVariable也是Native函式,buildModulesConfigJSONProperty把所有的JavaScriptModule模組以moduleID+methodID形式生成JSON字串,通過setGlobalVariable把JSON字串預先存入了ReactBridge中
bridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
複製程式碼
Java層的呼叫就是上面的過程,最後通過JNI呼叫到C++層,接下來進入這一層看看。
2.ReactBridge實現
通訊模型圖中要呼叫WebKit的實現,少不了Bridge這個橋樑,因為Java是不能直接呼叫WebKit,但是可以Java通過JNI,JNI再呼叫WebKit
JNI入口react/jni/OnLoad.cpp,通過RegisterNatives方式註冊的,JNI_OnLoad裡面註冊了setGlobalVariable和callFunction等native本地方法
//jni/react/jni/OnLoad.cpp
namespace bridge {
......
static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
auto bridge = extractRefPtr<Bridge>(env, obj);
bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
}
...
} // namespace bridge
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return initialize(vm, [] {
// get the current env
JNIEnv* env = Environment::current();
registerNatives("com/facebook/react/bridge/ReadableNativeMap$ReadableNativeMapKeySetIterator", {
......
registerNatives("com/facebook/react/bridge/ReactBridge", {
makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
makeNativeMethod(
"loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
bridge::loadScriptFromAssets),
makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
makeNativeMethod("callFunction", bridge::callFunction),
makeNativeMethod("invokeCallback", bridge::invokeCallback),
makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
makeNativeMethod("startProfiler", bridge::startProfiler),
makeNativeMethod("stopProfiler", bridge::stopProfiler),
makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),
});
}
複製程式碼
可以看到setGlobalVariable真正呼叫的是Bridge.cpp裡面的setGlobalVariable:
//jni/react/Bridge.h
class JSThreadState;
class Bridge : public Countable {
public:
typedef std::function<void(std::vector<MethodCall>, bool isEndOfBatch)> Callback;
Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback);
virtual ~Bridge();
/**
* Flush get the next queue of changes.
*/
void flush();
/**
* Executes a function with the module ID and method ID and any additional
* arguments in JS.
*/
void callFunction(const double moduleId, const double methodId, const folly::dynamic& args);
/**
* Invokes a callback with the cbID, and optional additional arguments in JS.
*/
void invokeCallback(const double callbackId, const folly::dynamic& args);
void executeApplicationScript(const std::string& script, const std::string& sourceURL);
void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
bool supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
void handleMemoryPressureModerate();
void handleMemoryPressureCritical();
private:
Callback m_callback;
std::unique_ptr<JSThreadState> m_threadState;
// This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
// on the same thread. In that case, the callback will try to run the task on m_callback which
// will have been destroyed within ~Bridge(), thus causing a SIGSEGV.
std::shared_ptr<bool> m_destroyed;
};
void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
m_threadState->setGlobalVariable(propName, jsonValue);
}
複製程式碼
最終呼叫的是JSThreadState裡面的setGlobalVariable:
//jni/react/Bridge.cpp
class JSThreadState {
public:
JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
m_callback(callback)
{
m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON, bool isEndOfBatch) {
m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
});
}
void setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
m_jsExecutor->setGlobalVariable(propName, jsonValue);
}
private:
std::unique_ptr<JSExecutor> m_jsExecutor;
Bridge::Callback m_callback;
};
複製程式碼
最終呼叫JSExecutor裡面setGlobalVariable:
//jni/react/JSCExecutor.h
class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
public:
/**
* Should be invoked from the JS thread.
*/
explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback);
~JSCExecutor() override;
virtual void executeApplicationScript(
const std::string& script,
const std::string& sourceURL) override;
virtual std::string flush() override;
virtual std::string callFunction(
const double moduleId,
const double methodId,
const folly::dynamic& arguments) override;
virtual std::string invokeCallback(
const double callbackId,
const folly::dynamic& arguments) override;
virtual void setGlobalVariable(
const std::string& propName,
const std::string& jsonValue) override;
virtual bool supportsProfiling() override;
virtual void startProfiler(const std::string &titleString) override;
virtual void stopProfiler(const std::string &titleString, const std::string &filename) override;
virtual void handleMemoryPressureModerate() override;
virtual void handleMemoryPressureCritical() override;
void flushQueueImmediate(std::string queueJSON);
void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
virtual void onMessageReceived(int workerId, const std::string& message) override;
virtual JSGlobalContextRef getContext() override;
virtual std::shared_ptr<JMessageQueueThread> getMessageQueueThread() override;
};
} }
//jni/react/JSCExecutor.cpp
void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
auto globalObject = JSContextGetGlobalObject(m_context);
String jsPropertyName(propName.c_str());
String jsValueJSON(jsonValue.c_str());
auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);
JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}
複製程式碼
從JSCExecutor::setGlobalVariable中可以看到java層傳過來的JSON串(包含Native Modules和JS Modules)和屬性名(__fbBatchedBridgeConfig),賦值給全域性變數globalObject,這個全域性變數通過JSContextGetGlobalObject獲取到,在JSCExecutor.h標頭檔案中:
#include <memory>
#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include "Executor.h"
#include "JSCHelpers.h"
#include "JSCWebWorker.h"
複製程式碼
JavaScriptCore/JSContextRef.h是Webkit這個庫中的檔案,在ReactAndroid下的build.gradle中有索引這個庫,到build.gradle中看看:
//ReactAndroid/build.gradle
task downloadJSCHeaders(type: Download) {
def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/!svn/bc/174650/trunk/Source/JavaScriptCore/API/'
def jscHeaderFiles = ['JSBase.h', 'JSContextRef.h', 'JSObjectRef.h', 'JSRetainPtr.h', 'JSStringRef.h', 'JSValueRef.h', 'WebKitAvailability.h']
def output = new File(downloadsDir, 'jsc')
output.mkdirs()
src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" })
onlyIfNewer true
overwrite false
dest output
}
複製程式碼
開啟jscAPIBaseURL這個url可以找到JSContextRef.h標頭檔案:
//JavaScriptCore/API/JSContextRef.h
...
/*!
@function
@abstract Gets the global object of a JavaScript execution context.
@param ctx The JSContext whose global object you want to get.
@result ctx's global object.
*/
JS_EXPORT JSObjectRef JSContextGetGlobalObject(JSContextRef ctx);
...
複製程式碼
JSCExecutor::setGlobalVariable最後給JS的全域性變數設定一個屬性__fbBatchedBridgeConfig
,這個屬性的值就是JavaScriptModule,Java呼叫JS的時候從屬性中查詢,看下callFunction:
//jni/react/JSCExecutor.cpp
std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments),
};
return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
}
static std::string executeJSCallWithJSC(
JSGlobalContextRef ctx,
const std::string& methodName,
const std::vector<folly::dynamic>& arguments) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(
TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall",
"method", methodName);
#endif
// 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();
}
複製程式碼
最終以
__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})
的形式拼接一個apply的Javascript執行語句,最後呼叫jni/react/JSCHelpers.cpp的evaluateScript的來執行這個語句,完成Bridge向Javascript的呼叫,JSCHelpers對WebKit的一些API做了封裝,它負責最終呼叫WebKit:
//jni/react/JSCHelpers.h
namespace facebook {
namespace react {
void installGlobalFunction(
JSGlobalContextRef ctx,
const char* name,
JSObjectCallAsFunctionCallback callback);
JSValueRef makeJSCException(
JSContextRef ctx,
const char* exception_text);
JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source);
} }
複製程式碼
Bridge層的呼叫過程: OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit
3.JavaScript層實現
與Javascript的通訊,實質上是Weikit執行Javascript語句,呼叫流程是Bridge->WebKit->Javascript,WebKit中提供了許多與Javascript通訊的API,比如evaluateScript、JSContextGetGlobalObject、JSObjectSetProperty等。
回顧一下前面JSCExecutor::setGlobalVariable:
//jni/react/JSCExecutor.cpp
void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
auto globalObject = JSContextGetGlobalObject(m_context);
String jsPropertyName(propName.c_str());
String jsValueJSON(jsonValue.c_str());
auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);
JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}
複製程式碼
所有JavaScriptModule資訊是呼叫的setGlobalVariable方法生成一張對映表,這張對映表最終肯定是要儲存在Javascript層的,JSContextGetGlobalObject是WeiKit的方法,其目的是獲取Global全域性物件,jsPropertyName方法字面意思就是Javascript物件的屬性名,引數propName是從Java層傳遞過來的,在CatalystInstanceImpl.java類中可以印證這一點,具體值有兩個:__fbBatchedBridgeConfig和**__RCTProfileIsProfiling**.
//com/facebook/react/bridge/CatalystInstanceImpl
bridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
複製程式碼
翻譯成JS語句大概是:
global.__fbBatchedBridgeConfig = jsonValue;
複製程式碼
由於**__fbBatchedBridgeConfig物件是被直接定義成Global全域性物件的屬性,就可以直接呼叫了,類似於window物件,__fbBatchedBridgeConfig物件裡又有兩個屬性:remoteModuleConfig和localModulesConfig**,分別對應Java模組和JS模組。
到JS層的BatchedBridge.js:
//Libraries/BatchedBridge/BatchedBridge.js
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig, //Native(Java)模組
__fbBatchedBridgeConfig.localModulesConfig, //JS模組
);
// TODO: Move these around to solve the cycle in a cleaner way.
const Systrace = require('Systrace');
const JSTimersExecution = require('JSTimersExecution');
BatchedBridge.registerCallableModule('Systrace', Systrace);
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
if (__DEV__) {
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
}
Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
module.exports = BatchedBridge;
複製程式碼
1.在全域性變數global中定義fbBatchedBridge,在前面JSCExecutor::callFunction中呼叫JS語句的形式是fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})
2.BatchedBridge=new MessageQueue
所以再繼續跟到MessageQueue.js中:
class MessageQueue {
constructor(remoteModules, localModules) {
this.RemoteModules = {};
this._callableModules = {};
this._queue = [[], [], [], 0];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = 0;
this._callID = 0;
this._lastFlush = 0;
this._eventLoopStartTime = new Date().getTime();
[
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'flushedQueue',
].forEach((fn) => this[fn] = this[fn].bind(this));
let modulesConfig = this._genModulesConfig(remoteModules);
this._genModules(modulesConfig);
localModules && this._genLookupTables(
this._genModulesConfig(localModules),this._moduleTable, this._methodTable
);
this._debugInfo = {};
this._remoteModuleTable = {};
this._remoteMethodTable = {};
this._genLookupTables(
modulesConfig, this._remoteModuleTable, this._remoteMethodTable
);
}
/**
* Public APIs
*/
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
});
return this.flushedQueue();
}
_genLookupTables(modulesConfig, moduleTable, methodTable) {
modulesConfig.forEach((config, moduleID) => {
this._genLookup(config, moduleID, moduleTable, methodTable);
});
}
_genLookup(config, moduleID, moduleTable, methodTable) {
if (!config) {
return;
}
let moduleName, methods;
if (moduleHasConstants(config)) {
[moduleName, , methods] = config;
} else {
[moduleName, methods] = config;
}
moduleTable[moduleID] = moduleName;
methodTable[moduleID] = Object.assign({}, methods);
}
}
module.exports = MessageQueue;
複製程式碼
把remoteModules, localModules傳進了**_genLookupTables的方法裡,同時還有兩個引數_moduleTable**、_methodTable,就是我們找的對映表了,一張module對映表,一張method對映表
再回顧下前面Java呼叫JS的語句形式:__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})
class MessageQueue {
constructor(remoteModules, localModules) {
this.RemoteModules = {};
this._callableModules = {};
this._queue = [[], [], [], 0];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = 0;
this._callID = 0;
this._lastFlush = 0;
this._eventLoopStartTime = new Date().getTime();
[
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'flushedQueue',
].forEach((fn) => this[fn] = this[fn].bind(this));
let modulesConfig = this._genModulesConfig(remoteModules);
this._genModules(modulesConfig);
localModules && this._genLookupTables(
this._genModulesConfig(localModules),this._moduleTable, this._methodTable
);
this._debugInfo = {};
this._remoteModuleTable = {};
this._remoteMethodTable = {};
this._genLookupTables(
modulesConfig, this._remoteModuleTable, this._remoteMethodTable
);
}
/**
* Public APIs
*/
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
});
return this.flushedQueue();
}
_genLookupTables(modulesConfig, moduleTable, methodTable) {
modulesConfig.forEach((config, moduleID) => {
this._genLookup(config, moduleID, moduleTable, methodTable);
});
}
_genLookup(config, moduleID, moduleTable, methodTable) {
if (!config) {
return;
}
let moduleName, methods;
if (moduleHasConstants(config)) {
[moduleName, , methods] = config;
} else {
[moduleName, methods] = config;
}
moduleTable[moduleID] = moduleName;
methodTable[moduleID] = Object.assign({}, methods);
}
}
module.exports = MessageQueue;
複製程式碼
其中有個callableModules, 它就是用來存放哪些Javascript元件是可以被呼叫的,正常情況下callableModules的資料和JavaScriptModules的資料(包括方法名和引數)理應是完全對應的.
看下哪些JS元件呼叫registerCallableModule進行方法的註冊
看一下RCTEventEmitter.js:
//Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js
var BatchedBridge = require('BatchedBridge');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
BatchedBridge.registerCallableModule(
'RCTEventEmitter',
ReactNativeEventEmitter
);
// Completely locally implemented - no native hooks.
module.exports = ReactNativeEventEmitter;
複製程式碼
ReactNativeEventEmitter.js:
var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
receiveEvent: function(
tag: number,
topLevelType: string,
nativeEventParam: Object
) {
......
},
receiveTouches: function(
eventTopLevelType: string,
touches: Array<Object>,
changedIndices: Array<number>
) {
......
});
複製程式碼
對比下Java層的介面表示,方法名稱和引數個數是一致的
public interface RCTEventEmitter extends JavaScriptModule {
public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
public void receiveTouches(
String eventName,
WritableArray touches,
WritableArray changedIndices);
}
複製程式碼
繼續前面__callFunction的最後一步:moduleMethods[method].apply(moduleMethods, args);呼叫對應模組的方法。
###4.總結 Java呼叫JavaScript可以總結為下面:
歡迎關注哈,
公眾號: JueCode
個人在簡書部落格: juexingzhe