publicinterfaceCatalystInstanceextendsMemoryPressureListener,JSInstance{voidrunJSBundle();// Returns the status of running the JS bundle; waits for an answer if runJSBundle is runningbooleanhasRunJSBundle();/** * Return the source URL of the JS Bundle that was run, or {@code null} if no JS * bundle has been run yet. */@NullableStringgetSourceURL();// This is called from java code, so it won't be stripped anyway, but proguard will rename it,// which this prevents.@Override@DoNotStripvoidinvokeCallback(intcallbackID,NativeArrayarguments);@DoNotStripvoidcallFunction(Stringmodule,Stringmethod,NativeArrayarguments);/** * Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration * (besides the UI thread) to finish running. Must be called from the UI thread so that we can * fully shut down other threads. */voiddestroy();booleanisDestroyed();/** * Initialize all the native modules */@VisibleForTestingvoidinitialize();ReactQueueConfigurationgetReactQueueConfiguration();<TextendsJavaScriptModule>TgetJSModule(Class<T>jsInterface);<TextendsNativeModule>booleanhasNativeModule(Class<T>nativeModuleInterface);<TextendsNativeModule>TgetNativeModule(Class<T>nativeModuleInterface);<TextendsJSIModule>TgetJSIModule(Class<T>jsiModuleInterface);Collection<NativeModule>getNativeModules();/** * This method permits a CatalystInstance to extend the known * Native modules. This provided registry contains only the new modules to load. */voidextendNativeModules(NativeModuleRegistrymodules);/** * Adds a idle listener for this Catalyst instance. The listener will receive notifications * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is * defined as there being some non-zero number of calls to JS that haven't resolved via a * onBatchCompleted call. The listener should be purely passive and not affect application logic. */voidaddBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListenerlistener);/** * Removes a NotThreadSafeBridgeIdleDebugListener previously added with * {@link #addBridgeIdleDebugListener} */voidremoveBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListenerlistener);/** This method registers the file path of an additional JS segment by its ID. */voidregisterSegment(intsegmentId,Stringpath);@VisibleForTestingvoidsetGlobalVariable(StringpropName,StringjsonValue);/** * Get the C pointer (as a long) to the JavaScriptCore context associated with this instance. * * <p>Use the following pattern to ensure that the JS context is not cleared while you are using * it: JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder() * synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); } */JavaScriptContextHoldergetJavaScriptContextHolder();voidaddJSIModules(List<JSIModuleHolder>jsiModules);}
/** * Calls the default entry point into JS which is AppRegistry.runApplication() */privatevoiddefaultJSEntryPoint(){Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE,"ReactRootView.runApplication");try{if(mReactInstanceManager==null||!mIsAttachedToInstance){return;}ReactContextreactContext=mReactInstanceManager.getCurrentReactContext();if(reactContext==null){return;}CatalystInstancecatalystInstance=reactContext.getCatalystInstance();WritableNativeMapappParams=newWritableNativeMap();appParams.putDouble("rootTag",getRootViewTag());@NullableBundleappProperties=getAppProperties();if(appProperties!=null){appParams.putMap("initialProps",Arguments.fromBundle(appProperties));}if(getUIManagerType()==FABRIC){appParams.putBoolean("fabric",true);}mShouldLogContentAppeared=true;StringjsAppModuleName=getJSModuleName();catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName,appParams);}finally{Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);}}
const[moduleName,constants,methods,promiseMethods,syncMethods]=config;if(!constants&&!methods){// Module contents will be filled in lazily laterreturn{name:moduleName};}constmodule={};methods&&methods.forEach((methodName,methodID)=>{constisPromise=promiseMethods&&arrayContains(promiseMethods,methodID);constisSync=syncMethods&&arrayContains(syncMethods,methodID);invariant(!isPromise||!isSync,'Cannot have a method that is both async and a sync hook');constmethodType=isPromise?'promise':isSync?'sync':'async';module[methodName]=genMethod(moduleID,methodID,methodType);});Object.assign(module,constants);if(__DEV__){BatchedBridge.createDebugLookup(moduleID,moduleName,methods);}return{name:moduleName,module};
voidProxyExecutor::loadApplicationScript(std::unique_ptr<constJSBigString>,std::stringsourceURL){folly::dynamicnativeModuleConfig=folly::dynamic::array;{SystraceSections("collectNativeModuleDescriptions");automoduleRegistry=m_delegate->getModuleRegistry();for(constauto&name:moduleRegistry->moduleNames()){autoconfig=moduleRegistry->getConfig(name);nativeModuleConfig.push_back(config?config->config:nullptr);}}folly::dynamicconfig=folly::dynamic::object("remoteModuleConfig",std::move(nativeModuleConfig));{SystraceSectiont("setGlobalVariable");setGlobalVariable("__fbBatchedBridgeConfig",folly::make_unique<JSBigStdString>(folly::toJson(config)));}staticautoloadApplicationScript=jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<void(jstring)>("loadApplicationScript");// The proxy ignores the script data passed in.loadApplicationScript(m_executor.get(),jni::make_jstring(sourceURL).get());// We can get pending calls here to native but the queue will be drained when// we launch the application.}
privateCatalystInstanceImpl(finalReactQueueConfigurationSpecreactQueueConfigurationSpec,finalJavaScriptExecutorjsExecutor,finalNativeModuleRegistrynativeModuleRegistry,finalJSBundleLoaderjsBundleLoader,NativeModuleCallExceptionHandlernativeModuleCallExceptionHandler){Log.d(ReactConstants.TAG,"Initializing React Xplat Bridge.");mHybridData=initHybrid();mReactQueueConfiguration=ReactQueueConfigurationImpl.create(reactQueueConfigurationSpec,newNativeExceptionHandler());mBridgeIdleListeners=newCopyOnWriteArrayList<>();mNativeModuleRegistry=nativeModuleRegistry;mJSModuleRegistry=newJavaScriptModuleRegistry();mJSBundleLoader=jsBundleLoader;mNativeModuleCallExceptionHandler=nativeModuleCallExceptionHandler;mNativeModulesQueueThread=mReactQueueConfiguration.getNativeModulesQueueThread();mTraceListener=newJSProfilerTraceListener(this);Log.d(ReactConstants.TAG,"Initializing React Xplat Bridge before initializeBridge");initializeBridge(newBridgeCallback(this),jsExecutor,mReactQueueConfiguration.getJSQueueThread(),mNativeModulesQueueThread,mNativeModuleRegistry.getJavaModules(this),mNativeModuleRegistry.getCxxModules());Log.d(ReactConstants.TAG,"Initializing React Xplat Bridge after initializeBridge");mJavaScriptContextHolder=newJavaScriptContextHolder(getJavaScriptContext());}
其中调用的 initializeBridge 调用的 Native 方法如下:
123456789101112131415161718192021222324252627
voidCatalystInstanceImpl::initializeBridge(jni::alias_ref<ReactCallback::javaobject>callback,// This executor is actually a factory holder.JavaScriptExecutorHolder*jseh,jni::alias_ref<JavaMessageQueueThread::javaobject>jsQueue,jni::alias_ref<JavaMessageQueueThread::javaobject>nativeModulesQueue,jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject>javaModules,jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject>cxxModules){moduleMessageQueue_=std::make_shared<JMessageQueueThread>(nativeModulesQueue);moduleRegistry_=std::make_shared<ModuleRegistry>(buildNativeModuleList(std::weak_ptr<Instance>(instance_),javaModules,cxxModules,moduleMessageQueue_));instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback,moduleMessageQueue_),jseh->getExecutorFactory(),folly::make_unique<JMessageQueueThread>(jsQueue),moduleRegistry_);}
folly::Optional<ModuleConfig>ModuleRegistry::getConfig(conststd::string&name){SystraceSections("ModuleRegistry::getConfig","module",name);// Initialize modulesByName_if(modulesByName_.empty()&&!modules_.empty()){moduleNames();}autoit=modulesByName_.find(name);if(it==modulesByName_.end()){if(unknownModules_.find(name)!=unknownModules_.end()){returnnullptr;}if(!moduleNotFoundCallback_||!moduleNotFoundCallback_(name)||(it=modulesByName_.find(name))==modulesByName_.end()){unknownModules_.insert(name);returnnullptr;}}size_tindex=it->second;CHECK(index<modules_.size());NativeModule*module=modules_[index].get();// string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]folly::dynamicconfig=folly::dynamic::array(name);{SystraceSections_("getConstants");config.push_back(module->getConstants());}{SystraceSections_("getMethods");std::vector<MethodDescriptor>methods=module->getMethods();folly::dynamicmethodNames=folly::dynamic::array;folly::dynamicpromiseMethodIds=folly::dynamic::array;folly::dynamicsyncMethodIds=folly::dynamic::array;for(auto&descriptor:methods){// TODO: #10487027 compare tags instead of doing string comparison?methodNames.push_back(std::move(descriptor.name));if(descriptor.type=="promise"){promiseMethodIds.push_back(methodNames.size()-1);}elseif(descriptor.type=="sync"){syncMethodIds.push_back(methodNames.size()-1);}}if(!methodNames.empty()){config.push_back(std::move(methodNames));if(!promiseMethodIds.empty()||!syncMethodIds.empty()){config.push_back(std::move(promiseMethodIds));if(!syncMethodIds.empty()){config.push_back(std::move(syncMethodIds));}}}}if(config.size()==2&&config[1].empty()){// no constants or methodsreturnnullptr;}else{returnModuleConfig{index,config};}}
其根据一个 name 值,从 modules 中获取到指定的 module,进行读取里面的值,得到 ModuleConfig 信息。
5.调用方法的暴露
这里提供了两个方法,来提供调用 Native 方法:
1234567891011121314151617181920
voidModuleRegistry::callNativeMethod(unsignedintmoduleId,unsignedintmethodId,folly::dynamic&¶ms,intcallId){if(moduleId>=modules_.size()){throwstd::runtime_error(folly::to<std::string>("moduleId ",moduleId," out of range [0..",modules_.size(),")"));}modules_[moduleId]->invoke(methodId,std::move(params),callId);}MethodCallResultModuleRegistry::callSerializableNativeHook(unsignedintmoduleId,unsignedintmethodId,folly::dynamic&¶ms){if(moduleId>=modules_.size()){throwstd::runtime_error(folly::to<std::string>("moduleId ",moduleId,"out of range [0..",modules_.size(),")"));}returnmodules_[moduleId]->callSerializableNativeHook(methodId,std::move(params));}
voidCxxNativeModule::invoke(unsignedintreactMethodId,folly::dynamic&¶ms,intcallId){if(reactMethodId>=methods_.size()){throwstd::invalid_argument(folly::to<std::string>("methodId ",reactMethodId," out of range [0..",methods_.size(),"]"));}if(!params.isArray()){throwstd::invalid_argument(folly::to<std::string>("method parameters should be array, but are ",params.typeName()));}CxxModule::Callbackfirst;CxxModule::Callbacksecond;constauto&method=methods_[reactMethodId];if(!method.func){throwstd::runtime_error(folly::to<std::string>("Method ",method.name," is synchronous but invoked asynchronously"));}if(params.size()<method.callbacks){throwstd::invalid_argument(folly::to<std::string>("Expected ",method.callbacks," callbacks, but only ",params.size()," parameters provided"));}if(method.callbacks==1){first=convertCallback(makeCallback(instance_,params[params.size()-1]));}elseif(method.callbacks==2){first=convertCallback(makeCallback(instance_,params[params.size()-2]));second=convertCallback(makeCallback(instance_,params[params.size()-1]));}params.resize(params.size()-method.callbacks);messageQueueThread_->runOnQueue([method,params=std::move(params),first,second,callId](){#ifdefWITH_FBSYSTRACEif(callId!=-1){fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS,"native",callId);}#endifSystraceSections(method.name.c_str());try{method.func(std::move(params),first,second);}catch(constfacebook::xplat::JsArgumentException&ex){throw;}catch(std::exception&e){LOG(ERROR)<<"std::exception. Method call "<<method.name.c_str()<<" failed: "<<e.what();std::terminate();}catch(std::string&error){LOG(ERROR)<<"std::string. Method call "<<method.name.c_str()<<" failed: "<<error.c_str();std::terminate();}catch(...){LOG(ERROR)<<"Method call "<<method.name.c_str()<<" failed. unknown error";std::terminate();}});}
MethodCallResultCxxNativeModule::callSerializableNativeHook(unsignedinthookId,folly::dynamic&&args){if(hookId>=methods_.size()){throwstd::invalid_argument(folly::to<std::string>("methodId ",hookId," out of range [0..",methods_.size(),"]"));}constauto&method=methods_[hookId];if(!method.syncFunc){throwstd::runtime_error(folly::to<std::string>("Method ",method.name," is asynchronous but invoked synchronously"));}returnmethod.syncFunc(std::move(args));}
voidNativeToJsBridge::callFunction(std::string&&module,std::string&&method,folly::dynamic&&arguments){intsystraceCookie=-1;...runOnExecutorQueue([module=std::move(module),method=std::move(method),arguments=std::move(arguments),systraceCookie](JSExecutor*executor){...// This is safe because we are running on the executor's thread: it won't// destruct until after it's been unregistered (which we check above) and// that will happen on this threadexecutor->callFunction(module,method,arguments);});}
而 executor 便指的是 JSCExecutor.cpp 类:
12345678910111213141516171819202122
voidJSCExecutor::callFunction(conststd::string&moduleId,conststd::string&methodId,constfolly::dynamic&arguments){SystraceSections("JSCExecutor::callFunction");// This weird pattern is because Value is not default constructible.// The lambda is inlined, so there's no overhead.autoresult=[&]{JSContextLocklock(m_context);try{if(!m_callFunctionReturnResultAndFlushedQueueJS){bindBridge();}returnm_callFunctionReturnFlushedQueueJS->callAsFunction({Value(m_context,String::createExpectingAscii(m_context,moduleId)),Value(m_context,String::createExpectingAscii(m_context,methodId)),Value::fromDynamic(m_context,std::move(arguments))});}catch(...){std::throw_with_nested(std::runtime_error("Error calling "+moduleId+"."+methodId));}}();callNativeModules(std::move(result));}
voidJSCExecutor::bindBridge()throw(JSException){SystraceSections("JSCExecutor::bindBridge");std::call_once(m_bindFlag,[this]{autoglobal=Object::getGlobalObject(m_context);autobatchedBridgeValue=global.getProperty("__fbBatchedBridge");if(batchedBridgeValue.isUndefined()){autorequireBatchedBridge=global.getProperty("__fbRequireBatchedBridge");if(!requireBatchedBridge.isUndefined()){batchedBridgeValue=requireBatchedBridge.asObject().callAsFunction({});}if(batchedBridgeValue.isUndefined()){throwJSException("Could not get BatchedBridge, make sure your bundle is packaged correctly");}}autobatchedBridge=batchedBridgeValue.asObject();m_callFunctionReturnFlushedQueueJS=batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();m_invokeCallbackAndReturnFlushedQueueJS=batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject();m_flushedQueueJS=batchedBridge.getProperty("flushedQueue").asObject();m_callFunctionReturnResultAndFlushedQueueJS=batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();});}
'use strict';constMessageQueue=require('MessageQueue');constBatchedBridge=newMessageQueue();// Wire up the batched bridge on the global object so that we can call into it.// Ideally, this would be the inverse relationship. I.e. the native environment// provides this global directly with its script embedded. Then this module// would export it. A possible fix would be to trim the dependencies in// MessageQueue to its minimal features and embed that in the native runtime.Object.defineProperty(global,'__fbBatchedBridge',{configurable:true,value:BatchedBridge,});module.exports=BatchedBridge;
这里其值则是对应着 MessageQueue,其管理着 JS 端的 function 的注册调用逻辑,其中callFunctionReturnResultAndFlushedQueue :
__callFunction(module:string,method:string,args:any[]):any{this._lastFlush=newDate().getTime();this._eventLoopStartTime=this._lastFlush;Systrace.beginEvent(`${module}.${method}()`);if(this.__spy){this.__spy({type:TO_JS,module,method,args});}constmoduleMethods=this.getCallableModule(module);invariant(!!moduleMethods,'Module %s is not a registered callable module (calling %s)',module,method,);invariant(!!moduleMethods[method],'Method %s does not exist on module %s',method,module,);constresult=moduleMethods[method].apply(moduleMethods,args);Systrace.endEvent();returnresult;}