Support ModuleHolder-based lazy init of C++ modules with C++ bridge on android

Reviewed By: AaaChiuuu

Differential Revision: D4614479

fbshipit-source-id: 109ac34b8688f0113675e4a4479d1ddcc6169ed4
This commit is contained in:
Marc Horowitz 2017-03-14 15:28:53 -07:00 committed by Facebook Github Bot
parent 6410e256c5
commit e622d51e20
20 changed files with 174 additions and 52 deletions

View File

@ -553,7 +553,8 @@ struct RCTInstanceCallback : public InstanceCallback {
}
modules.emplace_back(
new QueueNativeModule(self, std::make_unique<CxxNativeModule>(
_reactInstance, [(RCTCxxModule *)(moduleData.instance) move])));
_reactInstance, [moduleData.name UTF8String],
[moduleData] { return [(RCTCxxModule *)(moduleData.instance) move]; })));
} else {
modules.emplace_back(new RCTNativeModule(self, moduleData));
}

View File

@ -110,7 +110,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
final JavaScriptModuleRegistry jsModuleRegistry,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge.");
mHybridData = initHybrid();
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
@ -123,6 +123,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mTraceListener = new JSProfilerTraceListener(this);
FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge before initializeBridge");
initializeBridge(
new BridgeCallback(this),
jsExecutor,
@ -130,6 +131,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
mReactQueueConfiguration.getNativeModulesQueueThread(),
mJavaRegistry.getJavaModules(this),
mJavaRegistry.getCxxModules());
FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
mMainExecutorToken = getMainExecutorToken();
}
@ -182,7 +184,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
MessageQueueThread jsQueue,
MessageQueueThread moduleQueue,
Collection<JavaModuleWrapper> javaModules,
Collection<CxxModuleWrapper> cxxModules);
Collection<ModuleHolder> cxxModules);
/**
* This API is used in situations where the JS bundle is being executed not on

View File

@ -53,12 +53,12 @@ public class NativeModuleRegistry {
return javaModules;
}
/* package */ Collection<CxxModuleWrapper> getCxxModules() {
ArrayList<CxxModuleWrapper> cxxModules = new ArrayList<>();
/* package */ Collection<ModuleHolder> getCxxModules() {
ArrayList<ModuleHolder> cxxModules = new ArrayList<>();
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
Class<?> type = entry.getKey();
if (CxxModuleWrapper.class.isAssignableFrom(type)) {
cxxModules.add((CxxModuleWrapper) entry.getValue().getModule());
cxxModules.add(entry.getValue());
}
}
return cxxModules;

View File

@ -15,6 +15,7 @@ LOCAL_SRC_FILES := \
JSLogging.cpp \
JniJSModulesUnbundle.cpp \
MethodInvoker.cpp \
ModuleRegistryBuilder.cpp \
NativeArray.cpp \
NativeCommon.cpp \
NativeMap.cpp \

View File

@ -6,6 +6,7 @@ EXPORTED_HEADERS = [
"JExecutorToken.h",
"JSLoader.h",
"MethodInvoker.h",
"ModuleRegistryBuilder.h",
"NativeArray.h",
"NativeCommon.h",
"NativeMap.h",

View File

@ -20,6 +20,7 @@
#include <cxxreact/ModuleRegistry.h>
#include <cxxreact/CxxNativeModule.h>
#include "CxxModuleWrapper.h"
#include "JavaScriptExecutorHolder.h"
#include "JniJSModulesUnbundle.h"
#include "JNativeRunnable.h"
@ -128,21 +129,10 @@ void CatalystInstanceImpl::initializeBridge(
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules) {
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
// TODO mhorowitz: how to assert here?
// Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
std::vector<std::unique_ptr<NativeModule>> modules;
std::weak_ptr<Instance> winstance(instance_);
for (const auto& jm : *javaModules) {
modules.emplace_back(folly::make_unique<JavaNativeModule>(winstance, jm));
}
for (const auto& cm : *cxxModules) {
modules.emplace_back(
folly::make_unique<CxxNativeModule>(winstance, std::move(cthis(cm)->getModule())));
}
auto moduleRegistry = std::make_shared<ModuleRegistry>(std::move(modules));
// This used to be:
//
// Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
@ -163,7 +153,8 @@ void CatalystInstanceImpl::initializeBridge(
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
folly::make_unique<JMessageQueueThread>(moduleQueue),
moduleRegistry);
buildModuleRegistry(std::weak_ptr<Instance>(instance_),
javaModules, cxxModules));
}
void CatalystInstanceImpl::jniSetSourceURL(const std::string& sourceURL) {

View File

@ -10,6 +10,7 @@
#include "JMessageQueueThread.h"
#include "JSLoader.h"
#include "JavaModuleWrapper.h"
#include "ModuleRegistryBuilder.h"
namespace facebook {
namespace react {
@ -48,7 +49,7 @@ class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> {
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules);
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules);
/**
* Sets the source URL of the underlying bridge without loading any JS code.

View File

@ -12,7 +12,13 @@
namespace facebook {
namespace react {
class CxxModuleWrapper : public jni::HybridClass<CxxModuleWrapper> {
struct JNativeModule : jni::JavaClass<JNativeModule> {
constexpr static const char *const kJavaDescriptor =
"Lcom/facebook/react/bridge/NativeModule;";
};
class CxxModuleWrapper :
public jni::HybridClass<CxxModuleWrapper, JNativeModule> {
public:
constexpr static const char *const kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/CxxModuleWrapper;";

View File

@ -27,6 +27,8 @@ struct JavaModuleWrapper : jni::JavaClass<JavaModuleWrapper> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/JavaModuleWrapper;";
jni::local_ref<JBaseJavaModule::javaobject> getModule() {
// This is the call which causes a lazy Java module to actually be
// created.
static auto getModule = javaClassStatic()->getMethod<JBaseJavaModule::javaobject()>("getModule");
return getModule(self());
}

View File

@ -0,0 +1,51 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ModuleRegistryBuilder.h"
#include <cxxreact/CxxNativeModule.h>
#include <folly/Memory.h>
namespace facebook {
namespace react {
std::string ModuleHolder::getName() const {
static auto method = getClass()->getMethod<jstring()>("getName");
return method(self())->toStdString();
}
xplat::module::CxxModule::Provider ModuleHolder::getProvider() const {
return [self=jni::make_global(self())] {
static auto method =
ModuleHolder::javaClassStatic()->getMethod<JNativeModule::javaobject()>(
"getModule");
// This is the call which uses the lazy Java Provider to instantiate the
// Java CxxModuleWrapper which contains the CxxModule.
auto module = method(self);
CHECK(module->isInstanceOf(CxxModuleWrapper::javaClassStatic()))
<< "module isn't a C++ module";
auto cxxModule = jni::static_ref_cast<CxxModuleWrapper::javaobject>(module);
// Then, we grab the CxxModule from the wrapper, which is no longer needed.
return cxxModule->cthis()->getModule();
};
}
std::unique_ptr<ModuleRegistry> buildModuleRegistry(
std::weak_ptr<Instance> winstance,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
std::vector<std::unique_ptr<NativeModule>> modules;
for (const auto& jm : *javaModules) {
modules.emplace_back(folly::make_unique<JavaNativeModule>(winstance, jm));
}
for (const auto& cm : *cxxModules) {
modules.emplace_back(
folly::make_unique<CxxNativeModule>(winstance, cm->getName(), cm->getProvider()));
}
if (modules.empty()) {
return nullptr;
} else {
return folly::make_unique<ModuleRegistry>(std::move(modules));
}
}
}}

View File

@ -0,0 +1,30 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <string>
#include <cxxreact/CxxModule.h>
#include <cxxreact/ModuleRegistry.h>
#include <fb/fbjni.h>
#include "CxxModuleWrapper.h"
#include "JavaModuleWrapper.h"
namespace facebook {
namespace react {
class ModuleHolder : public jni::JavaClass<ModuleHolder> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/ModuleHolder;";
std::string getName() const;
xplat::module::CxxModule::Provider getProvider() const;
};
std::unique_ptr<ModuleRegistry> buildModuleRegistry(
std::weak_ptr<Instance> winstance,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules);
}
}

View File

@ -5,6 +5,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := libreactnativefb
LOCAL_SRC_FILES := \
CxxMessageQueue.cpp \
CxxNativeModule.cpp \
Instance.cpp \
JSCExecutor.cpp \

View File

@ -50,6 +50,8 @@ class CxxModule {
class SyncTagType {};
public:
typedef std::function<std::unique_ptr<CxxModule>()> Provider;
typedef std::function<void(std::vector<folly::dynamic>)> Callback;
constexpr static AsyncTagType AsyncTag = AsyncTagType();

View File

@ -47,18 +47,19 @@ CxxModule::Callback convertCallback(
}
CxxNativeModule::CxxNativeModule(std::weak_ptr<Instance> instance,
std::unique_ptr<CxxModule> module)
std::string name,
CxxModule::Provider provider)
: instance_(instance)
, module_(std::move(module))
, methods_(module_->getMethods()) {
module_->setInstance(instance);
}
, name_(std::move(name))
, provider_(provider) {}
std::string CxxNativeModule::getName() {
return module_->getName();
return name_;
}
std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
lazyInit();
std::vector<MethodDescriptor> descs;
for (auto& method : methods_) {
assert(method.func || method.syncFunc);
@ -68,6 +69,8 @@ std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
}
folly::dynamic CxxNativeModule::getConstants() {
lazyInit();
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
@ -168,5 +171,15 @@ MethodCallResult CxxNativeModule::callSerializableNativeHook(
return method.syncFunc(std::move(args));
}
void CxxNativeModule::lazyInit() {
if (module_) {
return;
}
module_ = provider_();
methods_ = module_->getMethods();
module_->setInstance(instance_);
}
}
}

View File

@ -15,8 +15,8 @@ std::function<void(folly::dynamic)> makeCallback(
class CxxNativeModule : public NativeModule {
public:
CxxNativeModule(std::weak_ptr<Instance> instance,
std::unique_ptr<xplat::module::CxxModule> module);
CxxNativeModule(std::weak_ptr<Instance> instance, std::string name,
xplat::module::CxxModule::Provider provider);
std::string getName() override;
std::vector<MethodDescriptor> getMethods() override;
@ -27,7 +27,11 @@ public:
ExecutorToken token, unsigned int hookId, folly::dynamic&& args) override;
private:
void lazyInit();
std::weak_ptr<Instance> instance_;
std::string name_;
xplat::module::CxxModule::Provider provider_;
std::unique_ptr<xplat::module::CxxModule> module_;
std::vector<xplat::module::CxxModule::Method> methods_;
};

View File

@ -40,7 +40,7 @@ void Instance::initializeBridge(
if (!nativeQueue) {
// TODO pass down a thread/queue from java, instead of creating our own.
auto queue = std::make_unique<CxxMessageQueue>();
auto queue = folly::make_unique<CxxMessageQueue>();
std::thread t(queue->getUnregisteredRunLoop());
t.detach();
nativeQueue = std::move(queue);

View File

@ -361,12 +361,8 @@ void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> scrip
evaluateSourceCode(m_context, bcSourceCode, jsSourceURL);
// TODO(luk): t13903306 Remove this check once we make native modules
// working for java2js
if (m_delegate) {
bindBridge();
flush();
}
bindBridge();
flush();
ReactMarker::logMarker("CREATE_REACT_CONTEXT_END");
ReactMarker::logMarker("RUN_JS_BUNDLE_END");
@ -416,11 +412,8 @@ void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> scrip
evaluateScript(m_context, jsScript, jsSourceURL);
}
// TODO(luk): t13903306 Remove this check once we make native modules working for java2js
if (m_delegate) {
bindBridge();
flush();
}
bindBridge();
flush();
ReactMarker::logMarker("CREATE_REACT_CONTEXT_END");
ReactMarker::logMarker("RUN_JS_BUNDLE_END");
@ -435,6 +428,9 @@ void JSCExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> unbund
void JSCExecutor::bindBridge() throw(JSException) {
SystraceSection s("JSCExecutor::bindBridge");
if (!m_delegate || !m_delegate->getModuleRegistry()) {
return;
}
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
if (batchedBridgeValue.isUndefined()) {
@ -466,7 +462,18 @@ void JSCExecutor::callNativeModules(Value&& value) {
void JSCExecutor::flush() {
SystraceSection s("JSCExecutor::flush");
callNativeModules(m_flushedQueueJS->callAsFunction({}));
if (!m_delegate) {
// do nothing
} else if (!m_delegate->getModuleRegistry()) {
callNativeModules(Value::makeNull(m_context));
} else {
// If this is failing, chances are you have provided a delegate with a
// module registry, but haven't loaded the JS which enables native function
// queueing. Add BatchedBridge.js to your bundle, pass a nullptr delegate,
// or make delegate->getModuleRegistry() return nullptr.
CHECK(m_flushedQueueJS) << "Attempting to use native methods without loading BatchedBridge.js";
callNativeModules(m_flushedQueueJS->callAsFunction({}));
}
}
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
@ -476,6 +483,9 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m
auto result = [&] {
try {
// See flush()
CHECK(m_callFunctionReturnFlushedQueueJS)
<< "Attempting to call native methods without loading BatchedBridge.js";
return m_callFunctionReturnFlushedQueueJS->callAsFunction({
Value(m_context, String::createExpectingAscii(m_context, moduleId)),
Value(m_context, String::createExpectingAscii(m_context, methodId)),
@ -511,6 +521,8 @@ Value JSCExecutor::callFunctionSyncWithValue(
const std::string& module, const std::string& method, Value args) {
SystraceSection s("JSCExecutor::callFunction");
// See flush()
CHECK(m_callFunctionReturnResultAndFlushedQueueJS);
Object result = m_callFunctionReturnResultAndFlushedQueueJS->callAsFunction({
Value(m_context, String::createExpectingAscii(m_context, module)),
Value(m_context, String::createExpectingAscii(m_context, method)),

View File

@ -11,6 +11,10 @@ JSCNativeModules::JSCNativeModules(std::shared_ptr<ModuleRegistry> moduleRegistr
m_moduleRegistry(std::move(moduleRegistry)) {}
JSValueRef JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName) {
if (!m_moduleRegistry) {
return Value::makeUndefined(context);
}
std::string moduleName = String::ref(context, jsName).str();
const auto it = m_objects.find(moduleName);

View File

@ -47,6 +47,9 @@ public:
void callNativeModules(
JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
CHECK(m_registry || calls.empty()) <<
"native module calls cannot be completed with no native modules";
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {
// An exception anywhere in here stops processing of the batch. This
@ -95,13 +98,8 @@ NativeToJsBridge::NativeToJsBridge(
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false))
, m_mainExecutorToken(callback->createExecutorToken())
, m_delegate(registry
? std::make_shared<JsToNativeBridge>(
this, registry, std::move(nativeQueue), callback)
: nullptr) {
if (!m_delegate) {
nativeQueue->quitSynchronous();
}
, m_delegate(std::make_shared<JsToNativeBridge>(
this, registry, std::move(nativeQueue), callback)) {
std::unique_ptr<JSExecutor> mainExecutor =
jsExecutorFactory->createJSExecutor(m_delegate, jsQueue);
// cached to avoid locked map lookup in the common case
@ -318,9 +316,7 @@ ExecutorToken NativeToJsBridge::getTokenForExecutor(JSExecutor& executor) {
}
void NativeToJsBridge::destroy() {
if (m_delegate) {
m_delegate->quitQueueSynchronous();
}
m_delegate->quitQueueSynchronous();
auto* executorMessageQueueThread = getMessageQueueThread(m_mainExecutorToken);
// All calls made through runOnExecutorQueue have an early exit if
// m_destroyed is true. Setting this before the runOnQueueSync will cause

View File

@ -305,6 +305,10 @@ public:
return Value(ctx, JSC_JSValueMakeUndefined(ctx));
}
static Value makeNull(JSContextRef ctx) {
return Value(ctx, JSC_JSValueMakeNull(ctx));
}
std::string toJSONString(unsigned indent = 0) const;
static Value fromJSON(JSContextRef ctx, const String& json);
static JSValueRef fromDynamic(JSContextRef ctx, const folly::dynamic& value);