mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-13 09:20:50 +08:00
Summary:
JSCallInvoker requires a `std::weak_ptr<Instance>` to create. In our C++, `CatalystInstance` is responsible for creating this `Instance` object. This `CatalystInstance` C++ initialization is separate from the `TurboModuleManager` C++ initialization. Therefore, in this diff, I made `CatalystInstance` responsible for creating the `JSCallInvoker`. It then exposes the `JSCallInvoker` using a hybrid class called `JSCallInvokerHolder`, which contains a `std::shared_ptr<JSCallInvoker>` member variable. Using `CatalystInstance.getJSCallInvokerHolder()` in TurboModuleManager.java, we get a handle to this hybrid container. Then, we pass it this hybrid object to `TurboModuleManager::initHybrid`, which retrieves the `std::shared_ptr<JSCallInvoker>` from the `JavaJSCallInvokerHandler`.
There were a few cyclic dependencies, so I had to break down the buck targets:
- `CatalystInstanceImpl.java` depends on `JSCallInvokerHolderImpl.java`, and `TurboModuleManager.java` depends on classes that are packaged with `CatalystInstanceImpl.java`. So, I had to put `JSCallInvokerHolderImpl.java` in its own buck target.
- `CatalystInstance.cpp` depends on `JavaJSCallInvokerHolder.cpp`, and `TurboModuleManager.cpp` depends on classes that are build with `CatalystInstance.cpp`. So, I had to put `JavaJSCallInvokerHolder.cpp` in its own buck target. To make things simpler, I also moved `JSCallInvoker.{cpp,h}` files into the same buck target as `JavaJSCallInvokerHolder.{cpp,h}`.
I think these steps should be enough to create the TurboModuleManager without needing a bridge:
1. Make `JSCallInvoker` an abstract base class.
2. On Android, create another derived class of `JSCallInvoker` that doesn't depend on Instance.
3. Create `JavaJSCallInvokerHolder` using an instance of this new class somewhere in C++.
4. Pass this instance of `JavaJSCallInvokerHolder` to Java and use it to create/instatiate `TurboModuleManager`.
Regarding steps 1 and 2, we can also make JSCallInvoker accept a lambda.
Reviewed By: mdvacca
Differential Revision: D15055511
fbshipit-source-id: 0ad72a86599819ec35d421dbee7e140959a26ab6
279 lines
10 KiB
C++
279 lines
10 KiB
C++
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
|
|
// This source code is licensed under the MIT license found in the
|
|
// LICENSE file in the root directory of this source tree.
|
|
|
|
#include "CatalystInstanceImpl.h"
|
|
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include <cxxreact/CxxNativeModule.h>
|
|
#include <cxxreact/Instance.h>
|
|
#include <cxxreact/JSBigString.h>
|
|
#include <cxxreact/JSBundleType.h>
|
|
#include <cxxreact/JSDeltaBundleClient.h>
|
|
#include <cxxreact/JSIndexedRAMBundle.h>
|
|
#include <cxxreact/MethodCall.h>
|
|
#include <cxxreact/ModuleRegistry.h>
|
|
#include <cxxreact/RecoverableError.h>
|
|
#include <cxxreact/RAMBundleRegistry.h>
|
|
#include <fb/log.h>
|
|
#include <fb/fbjni/ByteBuffer.h>
|
|
#include <folly/dynamic.h>
|
|
#include <folly/Memory.h>
|
|
#include <jni/Countable.h>
|
|
#include <jni/LocalReference.h>
|
|
#include <jsireact/JSCallInvokerHolder.h>
|
|
|
|
#include "CxxModuleWrapper.h"
|
|
#include "JavaScriptExecutorHolder.h"
|
|
#include "JNativeRunnable.h"
|
|
#include "JniJSModulesUnbundle.h"
|
|
#include "NativeArray.h"
|
|
|
|
using namespace facebook::jni;
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
namespace {
|
|
|
|
class Exception : public jni::JavaClass<Exception> {
|
|
public:
|
|
};
|
|
|
|
class JInstanceCallback : public InstanceCallback {
|
|
public:
|
|
explicit JInstanceCallback(
|
|
alias_ref<ReactCallback::javaobject> jobj,
|
|
std::shared_ptr<JMessageQueueThread> messageQueueThread)
|
|
: jobj_(make_global(jobj)), messageQueueThread_(std::move(messageQueueThread)) {}
|
|
|
|
void onBatchComplete() override {
|
|
messageQueueThread_->runOnQueue([this] {
|
|
static auto method =
|
|
ReactCallback::javaClassStatic()->getMethod<void()>("onBatchComplete");
|
|
method(jobj_);
|
|
});
|
|
}
|
|
|
|
void incrementPendingJSCalls() override {
|
|
// For C++ modules, this can be called from an arbitrary thread
|
|
// managed by the module, via callJSCallback or callJSFunction. So,
|
|
// we ensure that it is registered with the JVM.
|
|
jni::ThreadScope guard;
|
|
static auto method =
|
|
ReactCallback::javaClassStatic()->getMethod<void()>("incrementPendingJSCalls");
|
|
method(jobj_);
|
|
}
|
|
|
|
void decrementPendingJSCalls() override {
|
|
jni::ThreadScope guard;
|
|
static auto method =
|
|
ReactCallback::javaClassStatic()->getMethod<void()>("decrementPendingJSCalls");
|
|
method(jobj_);
|
|
}
|
|
|
|
private:
|
|
global_ref<ReactCallback::javaobject> jobj_;
|
|
std::shared_ptr<JMessageQueueThread> messageQueueThread_;
|
|
};
|
|
|
|
}
|
|
|
|
jni::local_ref<CatalystInstanceImpl::jhybriddata> CatalystInstanceImpl::initHybrid(
|
|
jni::alias_ref<jclass>) {
|
|
return makeCxxInstance();
|
|
}
|
|
|
|
CatalystInstanceImpl::CatalystInstanceImpl()
|
|
: instance_(folly::make_unique<Instance>()) {}
|
|
|
|
CatalystInstanceImpl::~CatalystInstanceImpl() {
|
|
if (moduleMessageQueue_ != NULL) {
|
|
moduleMessageQueue_->quitSynchronous();
|
|
}
|
|
}
|
|
|
|
void CatalystInstanceImpl::registerNatives() {
|
|
registerHybrid({
|
|
makeNativeMethod("initHybrid", CatalystInstanceImpl::initHybrid),
|
|
makeNativeMethod("initializeBridge", CatalystInstanceImpl::initializeBridge),
|
|
makeNativeMethod("jniExtendNativeModules", CatalystInstanceImpl::extendNativeModules),
|
|
makeNativeMethod("jniSetSourceURL", CatalystInstanceImpl::jniSetSourceURL),
|
|
makeNativeMethod("jniRegisterSegment", CatalystInstanceImpl::jniRegisterSegment),
|
|
makeNativeMethod("jniLoadScriptFromAssets", CatalystInstanceImpl::jniLoadScriptFromAssets),
|
|
makeNativeMethod("jniLoadScriptFromFile", CatalystInstanceImpl::jniLoadScriptFromFile),
|
|
makeNativeMethod("jniLoadScriptFromDeltaBundle", CatalystInstanceImpl::jniLoadScriptFromDeltaBundle),
|
|
makeNativeMethod("jniCallJSFunction", CatalystInstanceImpl::jniCallJSFunction),
|
|
makeNativeMethod("jniCallJSCallback", CatalystInstanceImpl::jniCallJSCallback),
|
|
makeNativeMethod("setGlobalVariable", CatalystInstanceImpl::setGlobalVariable),
|
|
makeNativeMethod("getJavaScriptContext", CatalystInstanceImpl::getJavaScriptContext),
|
|
makeNativeMethod("getJSCallInvokerHolder", CatalystInstanceImpl::getJSCallInvokerHolder),
|
|
makeNativeMethod("jniHandleMemoryPressure", CatalystInstanceImpl::handleMemoryPressure),
|
|
});
|
|
|
|
JNativeRunnable::registerNatives();
|
|
}
|
|
|
|
void CatalystInstanceImpl::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) {
|
|
// TODO mhorowitz: how to assert here?
|
|
// Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
|
|
moduleMessageQueue_ = std::make_shared<JMessageQueueThread>(nativeModulesQueue);
|
|
|
|
// This used to be:
|
|
//
|
|
// Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
|
|
// --weak--> ReactCallback -> Java CatalystInstanceImpl
|
|
//
|
|
// Now the weak ref is a global ref. So breaking the loop depends on
|
|
// CatalystInstanceImpl#destroy() calling mHybridData.resetNative(), which
|
|
// should cause all the C++ pointers to be cleaned up (except C++
|
|
// CatalystInstanceImpl might be kept alive for a short time by running
|
|
// callbacks). This also means that all native calls need to be pre-checked
|
|
// to avoid NPE.
|
|
|
|
// See the comment in callJSFunction. Once js calls switch to strings, we
|
|
// don't need jsModuleDescriptions any more, all the way up and down the
|
|
// stack.
|
|
|
|
moduleRegistry_ = std::make_shared<ModuleRegistry>(
|
|
buildNativeModuleList(
|
|
std::weak_ptr<Instance>(instance_),
|
|
javaModules,
|
|
cxxModules,
|
|
moduleMessageQueue_));
|
|
|
|
instance_->initializeBridge(
|
|
std::make_unique<JInstanceCallback>(
|
|
callback,
|
|
moduleMessageQueue_),
|
|
jseh->getExecutorFactory(),
|
|
folly::make_unique<JMessageQueueThread>(jsQueue),
|
|
moduleRegistry_);
|
|
}
|
|
|
|
void CatalystInstanceImpl::extendNativeModules(
|
|
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
|
|
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
|
|
moduleRegistry_->registerModules(buildNativeModuleList(
|
|
std::weak_ptr<Instance>(instance_),
|
|
javaModules,
|
|
cxxModules,
|
|
moduleMessageQueue_));
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniSetSourceURL(const std::string& sourceURL) {
|
|
instance_->setSourceURL(sourceURL);
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniRegisterSegment(int segmentId, const std::string& path) {
|
|
instance_->registerBundle((uint32_t)segmentId, path);
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniLoadScriptFromAssets(
|
|
jni::alias_ref<JAssetManager::javaobject> assetManager,
|
|
const std::string& assetURL,
|
|
bool loadSynchronously) {
|
|
const int kAssetsLength = 9; // strlen("assets://");
|
|
auto sourceURL = assetURL.substr(kAssetsLength);
|
|
|
|
auto manager = extractAssetManager(assetManager);
|
|
auto script = loadScriptFromAssets(manager, sourceURL);
|
|
if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
|
|
auto bundle = JniJSModulesUnbundle::fromEntryFile(manager, sourceURL);
|
|
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
|
|
instance_->loadRAMBundle(
|
|
std::move(registry),
|
|
std::move(script),
|
|
sourceURL,
|
|
loadSynchronously);
|
|
return;
|
|
} else {
|
|
instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
|
|
}
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniLoadScriptFromFile(const std::string& fileName,
|
|
const std::string& sourceURL,
|
|
bool loadSynchronously) {
|
|
if (Instance::isIndexedRAMBundle(fileName.c_str())) {
|
|
instance_->loadRAMBundleFromFile(fileName, sourceURL, loadSynchronously);
|
|
} else {
|
|
std::unique_ptr<const JSBigFileString> script;
|
|
RecoverableError::runRethrowingAsRecoverable<std::system_error>(
|
|
[&fileName, &script]() {
|
|
script = JSBigFileString::fromPath(fileName);
|
|
});
|
|
instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
|
|
}
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniLoadScriptFromDeltaBundle(
|
|
const std::string& sourceURL,
|
|
jni::alias_ref<NativeDeltaClient::jhybridobject> jDeltaClient,
|
|
bool loadSynchronously) {
|
|
|
|
auto deltaClient = jDeltaClient->cthis()->getDeltaClient();
|
|
auto registry = RAMBundleRegistry::singleBundleRegistry(
|
|
folly::make_unique<JSDeltaBundleClientRAMBundle>(deltaClient));
|
|
|
|
instance_->loadRAMBundle(
|
|
std::move(registry), deltaClient->getStartupCode(), sourceURL, loadSynchronously);
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniCallJSFunction(std::string module, std::string method, NativeArray* arguments) {
|
|
// We want to share the C++ code, and on iOS, modules pass module/method
|
|
// names as strings all the way through to JS, and there's no way to do
|
|
// string -> id mapping on the objc side. So on Android, we convert the
|
|
// number to a string, here which gets passed as-is to JS. There, they they
|
|
// used as ids if isFinite(), which handles this case, and looked up as
|
|
// strings otherwise. Eventually, we'll probably want to modify the stack
|
|
// from the JS proxy through here to use strings, too.
|
|
instance_->callJSFunction(std::move(module),
|
|
std::move(method),
|
|
arguments->consume());
|
|
}
|
|
|
|
void CatalystInstanceImpl::jniCallJSCallback(jint callbackId, NativeArray* arguments) {
|
|
instance_->callJSCallback(callbackId, arguments->consume());
|
|
}
|
|
|
|
void CatalystInstanceImpl::setGlobalVariable(std::string propName,
|
|
std::string&& jsonValue) {
|
|
// This is only ever called from Java with short strings, and only
|
|
// for testing, so no need to try hard for zero-copy here.
|
|
|
|
instance_->setGlobalVariable(std::move(propName),
|
|
folly::make_unique<JSBigStdString>(std::move(jsonValue)));
|
|
}
|
|
|
|
jlong CatalystInstanceImpl::getJavaScriptContext() {
|
|
return (jlong) (intptr_t) instance_->getJavaScriptContext();
|
|
}
|
|
|
|
void CatalystInstanceImpl::handleMemoryPressure(int pressureLevel) {
|
|
instance_->handleMemoryPressure(pressureLevel);
|
|
}
|
|
|
|
jni::alias_ref<JSCallInvokerHolder::javaobject> CatalystInstanceImpl::getJSCallInvokerHolder() {
|
|
if (javaInstanceHolder_ == nullptr) {
|
|
jsCallInvoker_ = std::make_shared<JSCallInvoker>(instance_);
|
|
javaInstanceHolder_ = jni::make_global(JSCallInvokerHolder::newObjectCxxArgs(jsCallInvoker_));
|
|
}
|
|
|
|
return javaInstanceHolder_;
|
|
}
|
|
|
|
}}
|