Move Android TurboModules to github

Summary:
This commit moves all the turbo module files for Android to Github.

Note that gradle build is not yet enabled.
Sample Turbo Modules will be added in a later commit.

Other missing features

- Support for `CxxModule`
- Remove usage of folly::dynamic for arguments and result conversion
- Support for Promise return types.

Reviewed By: mdvacca

Differential Revision: D13647438

fbshipit-source-id: 5f1188556d6c64bfa2b2fd2146ac72b0fb456891
This commit is contained in:
Ram N 2019-01-14 15:40:31 -08:00 committed by Facebook Github Bot
parent 8a8a11b77a
commit e6f7d69428
10 changed files with 549 additions and 1 deletions

View File

@ -0,0 +1,25 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")
rn_android_library(
name = "core",
srcs = glob(
[
"*.java",
],
),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
deps = [
react_native_target("java/com/facebook/react/turbomodule/core/interfaces:interfaces"),
react_native_dep("java/com/facebook/systrace:systrace"),
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/turbomodule/core/jni:jni"),
react_native_target("java/com/facebook/debug/holder:holder"),
react_native_target("java/com/facebook/react/bridge:interfaces"),
react_native_target("java/com/facebook/react/bridge:bridge"),
],
)

View File

@ -0,0 +1,76 @@
/**
* 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.
*/
package com.facebook.react.turbomodule.core;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.JSIModule;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import com.facebook.soloader.SoLoader;
/**
* This is the main class and entry point for TurboModules.
* Note that this is a hybrid class, and has a C++ counterpart
* This class installs the JSI bindings. It also implements the method to get a Java module, that the C++ counterpart calls.
*/
public class TurboModuleManager implements JSIModule {
static {
SoLoader.loadLibrary("turbomodulejsijni");
}
private final ReactApplicationContext mReactApplicationContext;
@DoNotStrip
@SuppressWarnings("unused")
private final HybridData mHybridData;
private final ModuleProvider mModuleProvider;
public TurboModuleManager(
ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext, ModuleProvider moduleProvider) {
mReactApplicationContext = reactApplicationContext;
MessageQueueThread jsMessageQueueThread =
mReactApplicationContext
.getCatalystInstance()
.getReactQueueConfiguration()
.getJSQueueThread();
mHybridData = initHybrid(jsContext.get(), jsMessageQueueThread);
mModuleProvider = moduleProvider;
}
@DoNotStrip
@SuppressWarnings("unused")
protected TurboModule getJavaModule(String name) {
return mModuleProvider.getModule(name, mReactApplicationContext);
}
protected native HybridData initHybrid(long jsContext, MessageQueueThread jsQueue);
protected native void installJSIBindings();
public void installBindings() {
installJSIBindings();
}
protected ReactApplicationContext getReactApplicationContext() {
return mReactApplicationContext;
}
@Override
public void initialize() {}
@Override
public void onCatalystInstanceDestroy() {}
/** All applications must implement this interface, and provide the Java TurboModule class */
public interface ModuleProvider {
TurboModule getModule(String name, ReactApplicationContext reactApplicationContext);
}
}

View File

@ -0,0 +1,12 @@
load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library")
rn_android_library(
name = "interfaces",
srcs = [
"TurboModule.java",
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
)

View File

@ -0,0 +1,16 @@
/**
* 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.
*/
package com.facebook.react.turbomodule.core.interfaces;
/**
* All turbo modules should inherit from this interface
*/
public interface TurboModule {
/** When CatalystInstance is destroyed, this method will be called. All implementing TurboModules can perform cleanup here. */
void invalidate();
}

View File

@ -0,0 +1,38 @@
load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob")
load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library")
rn_xplat_cxx_library(
name = "jni",
srcs = glob(["**/*.cpp"]),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "**/*.h"),
],
prefix = "jsireact",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
force_static = True,
platforms = ANDROID,
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
react_native_target("jni/react/jni:jni"),
"xplat//jsi:JSIDynamic",
"xplat//jsi:jsi",
],
exported_deps = [
"xplat//jsi:jsi",
react_native_xplat_target("turbomodule/core:core"),
],
)

View File

@ -0,0 +1,74 @@
/**
* 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 <memory>
#include <string>
#include <fb/fbjni.h>
#include <jsi/jsi.h>
#include <jsireact/TurboModuleBinding.h>
#include <react/jni/JMessageQueueThread.h>
#include "TurboModuleManager.h"
namespace facebook {
namespace react {
static JTurboModuleProviderFunctionType moduleProvider_ = nullptr;
TurboModuleManager::TurboModuleManager(
jni::alias_ref<TurboModuleManager::javaobject> jThis,
jsi::Runtime* rt,
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread
):
javaPart_(make_global(jThis)),
runtime_(rt),
jsMessageQueueThread_(jsMessageQueueThread)
{}
jni::local_ref<TurboModuleManager::jhybriddata> TurboModuleManager::initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue
) {
auto sharedJSMessageQueueThread = std::make_shared<JMessageQueueThread> (jsQueue);
return makeCxxInstance(jThis, (jsi::Runtime *) jsContext, sharedJSMessageQueueThread);
}
void TurboModuleManager::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", TurboModuleManager::initHybrid),
makeNativeMethod("installJSIBindings", TurboModuleManager::installJSIBindings),
});
}
void TurboModuleManager::installJSIBindings() {
if (!runtime_) {
return; // Runtime doesn't exist when attached to Chrome debugger.
}
TurboModuleBinding::install(*runtime_, std::make_shared<TurboModuleBinding>(
[this](const std::string &name) {
const auto moduleInstance = getJavaModule(name);
const auto jsInvoker = std::make_shared<react::JSCallInvoker>(jsMessageQueueThread_);
return moduleProvider_(name, moduleInstance, jsInvoker);
})
);
}
jni::global_ref<JTurboModule> TurboModuleManager::getJavaModule(std::string name) {
static auto method = javaClassStatic()->getMethod<jni::alias_ref<JTurboModule>(const std::string&)>("getJavaModule");
return make_global(method(javaPart_.get(), name));
}
void TurboModuleManager::setModuleProvider(JTurboModuleProviderFunctionType fn) {
moduleProvider_ = fn;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,49 @@
/**
* 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.
*/
#pragma once
#include <memory>
#include <fb/fbjni.h>
#include <jsi/jsi.h>
#include <jsireact/TurboModule.h>
#include <jsireact/JavaTurboModule.h>
#include <react/jni/JMessageQueueThread.h>
namespace facebook {
namespace react {
using JTurboModuleProviderFunctionType = std::function<std::shared_ptr<TurboModule>(
const std::string &name, jni::global_ref<JTurboModule> moduleInstance, std::shared_ptr<JSCallInvoker> jsInvoker)>;
class TurboModuleManager : public jni::HybridClass<TurboModuleManager> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/turbomodule/core/TurboModuleManager;";
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue
);
static void registerNatives();
static void setModuleProvider(JTurboModuleProviderFunctionType moduleProvider);
private:
friend HybridBase;
jni::global_ref<TurboModuleManager::javaobject> javaPart_;
jsi::Runtime* runtime_;
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread_;
jni::global_ref<JTurboModule> getJavaModule(std::string name);
void installJSIBindings();
explicit TurboModuleManager(
jni::alias_ref<TurboModuleManager::jhybridobject> jThis,
jsi::Runtime* rt,
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread
);
};
} // namespace react
} // namespace facebook

View File

@ -1,5 +1,5 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "OBJC_ARC_PREPROCESSOR_FLAGS", "get_debug_preprocessor_flags", "get_static_library_ios_flags")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
rn_xplat_cxx_library(
name = "core",
@ -17,6 +17,20 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
fbandroid_deps = [
react_native_target("jni/react/jni:jni"),
],
fbandroid_exported_headers = subdir_glob(
[
("platform/android", "*.h"),
],
prefix = "jsireact",
),
fbandroid_srcs = glob(
[
"platform/android/**/*.cpp",
],
),
fbobjc_compiler_flags = [
"-Wall",
"-fobjc-arc-exceptions",

View File

@ -0,0 +1,199 @@
/**
* 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 <memory>
#include <string>
#include <fb/fbjni.h>
#include <jsi/jsi.h>
#include <jsireact/TurboModule.h>
#import <jsireact/TurboModuleUtils.h>
#include <jsi/JSIDynamic.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
#include <react/jni/NativeMap.h>
#include <react/jni/JCallback.h>
#include "JavaTurboModule.h"
namespace facebook {
namespace react {
JavaTurboModule::JavaTurboModule(const std::string &name, jni::global_ref<JTurboModule> instance, std::shared_ptr<JSCallInvoker> jsInvoker)
: TurboModule(name, jsInvoker), instance_(instance) {}
// fnjni already does this conversion, but since we are using plain JNI, this needs to be done again
// TODO (axe) Reuse existing implementation as needed - the exist in MethodInvoker.cpp
// TODO (axe) If at runtime, JS sends incorrect arguments and this is not typechecked, conversion here will fail. Check for that case (OSS)
std::unique_ptr<jvalue[]> convertFromJValueArgsToJNIArgs(
JNIEnv *env,
jsi::Runtime &rt,
const jsi::Value *args,
size_t count,
std::shared_ptr<JSCallInvoker> jsInvoker
) {
auto jargs = std::make_unique<jvalue[]>(count);
for (size_t i = 0; i < count; i++) {
const jsi::Value *arg = &args[i];
if (arg->isBool()) {
jargs[i].z = arg->getBool();
} else if (arg->isNumber()) {
jargs[i].d = arg->getNumber();
} else if (arg->isNull() || arg->isUndefined()) {
// What happens if Java is expecting a bool or a number, and JS sends a null or undefined?
jargs[i].l = nullptr;
} else if (arg->isString()) {
// We are basically creating a whole new string here
// TODO (axe) Is there a way to copy this instead of creating a whole new string ?
jargs[i].l = env->NewStringUTF(arg->getString(rt).utf8(rt).c_str());
} else if (arg->isObject()) {
auto objectArg = arg->getObject(rt);
// We are currently using folly:dynamic to convert JSON to Writable Map
// TODO (axe) Don't use folly:dynamic, instead construct Java map directly
if (objectArg.isArray(rt)) {
auto dynamicFromValue = jsi::dynamicFromValue(rt, args[i]);
auto jParams = ReadableNativeArray::newObjectCxxArgs(std::move(dynamicFromValue));
jargs[i].l = jParams.release();
} else if (objectArg.isFunction(rt)) {
auto wrapper = std::make_shared<react::CallbackWrapper>(objectArg.getFunction(rt), rt, jsInvoker);
std::function<void(folly::dynamic)> fn = [wrapper](folly::dynamic responses){
if (wrapper == nullptr) {
throw std::runtime_error("callback arg cannot be called more than once");
}
std::shared_ptr<react::CallbackWrapper> rw = wrapper;
wrapper->jsInvoker->invokeAsync([rw, responses]() {
// TODO (axe) valueFromDynamic already returns a Value array. Don't iterate again
jsi::Value args = jsi::valueFromDynamic(rw->runtime, responses);
auto argsArray = args.getObject(rw->runtime).asArray(rw->runtime);
std::vector<jsi::Value> result;
for (size_t i = 0; i < argsArray.size(rw->runtime); i++) {
result.emplace_back(rw->runtime, argsArray.getValueAtIndex(rw->runtime, i));
}
rw->callback.call(rw->runtime, (const jsi::Value *)result.data(), result.size());
});
};
wrapper = nullptr;
// TODO Use our own implementation of callback instead of relying on JCxxCallbackImpl
auto callback = JCxxCallbackImpl::newObjectCxxArgs(fn);
jargs[i].l = callback.release();
} else {
auto dynamicFromValue = jsi::dynamicFromValue(rt, args[i]);
auto jParams = ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
jargs[i].l = jParams.release();
}
}
}
return jargs;
}
jsi::Value convertFromJMapToValue(JNIEnv *env, jsi::Runtime &rt, jobject arg) {
// We currently use Java Argument.makeNativeMap() method to do this conversion
// This could also be done purely in C++, but iterative over map methods
// but those may end up calling reflection methods anyway
// TODO (axe) Investigate the best way to convert Java Map to Value
static jclass jArguments = env->FindClass("com/facebook/react/bridge/Arguments");
static jmethodID jMakeNativeMap = env->GetStaticMethodID(jArguments, "makeNativeMap", "(Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;");
auto constants = (jobject) env->CallStaticObjectMethod(jArguments, jMakeNativeMap, arg);
auto jResult = jni::adopt_local(constants);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
jsi::Value JavaTurboModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
if (propNameUtf8 == "getConstants") {
// This is the special method to get the constants from the module.
// Since `getConstants` in Java only returns a Map, this function takes the map
// and converts it to a WritableMap.
return jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
JNIEnv *env = jni::Environment::current();
jclass cls = env->FindClass(jClassName_.c_str());
static jmethodID methodID = env->GetMethodID(cls, "getConstants", "()Ljava/util/Map;");
auto constantsMap = (jobject) env->CallObjectMethod(instance_.get(), methodID);
if (constantsMap == nullptr) {
return jsi::Value::undefined();
}
return convertFromJMapToValue(env, rt, constantsMap);
}
);
} else {
return TurboModule::get(runtime, propName);
}
}
jsi::Value JavaTurboModule::invokeJavaMethod(
jsi::Runtime &rt,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const std::string &methodSignature,
const jsi::Value *args,
size_t count) {
// We are using JNI directly instead of fbjni since we don't want template functiosn
// when finding methods.
JNIEnv *env = jni::Environment::current();
// TODO (axe) Memoize this class, so that we don't have to find it for every calls
jclass cls = env->FindClass(jClassName_.c_str());
// TODO (axe) Memoize method call, so we don't look it up each time the method is called
jmethodID methodID = env->GetMethodID(cls, methodName.c_str(), methodSignature.c_str());
std::unique_ptr<jvalue[]>jargs = convertFromJValueArgsToJNIArgs(env, rt, args, count, jsInvoker_);
auto instance = instance_.get();
switch (valueKind) {
case VoidKind: {
env->CallVoidMethodA(instance, methodID, jargs.get());
return jsi::Value::undefined();
}
case BooleanKind: {
return jsi::Value((bool)env->CallBooleanMethodA(instance, methodID, jargs.get()));
}
case NumberKind: {
return jsi::Value((double)env->CallDoubleMethodA(instance, methodID, jargs.get()));
}
case StringKind: {
auto returnString = (jstring) env->CallObjectMethodA(instance, methodID, jargs.get());
if (returnString == nullptr) {
return jsi::Value::null();
}
const char *js = env->GetStringUTFChars(returnString, nullptr);
std::string result = js;
env->ReleaseStringUTFChars(returnString, js);
return jsi::Value(rt, jsi::String::createFromUtf8(rt, result));
}
case ObjectKind: {
auto returnObject = (jobject) env->CallObjectMethodA(instance, methodID, jargs.get());
if (returnObject == nullptr) {
return jsi::Value::null();
}
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
case ArrayKind: {
auto returnObject = (jobject) env->CallObjectMethodA(instance, methodID, jargs.get());
if (returnObject == nullptr) {
return jsi::Value::null();
}
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeArray::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
default:
throw std::runtime_error("Unable to find method module: " + methodName + "(" + methodSignature + ")" + "in module " + jClassName_);
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,45 @@
/**
* 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.
*/
#pragma once
#include <string>
#include <fb/fbjni.h>
#include <jsi/jsi.h>
#include <jsireact/TurboModule.h>
namespace facebook {
namespace react {
struct JTurboModule : jni::JavaClass<JTurboModule> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/turbomodule/core/interfaces/TurboModule;";
};
class JSI_EXPORT JavaTurboModule : public TurboModule {
public:
JavaTurboModule(const std::string &name, jni::global_ref<JTurboModule> instance, std::shared_ptr<JSCallInvoker> jsInvoker);
jsi::Value invokeJavaMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const std::string &methodSignature,
const jsi::Value *args,
size_t count);
virtual facebook::jsi::Value get(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override;
protected:
// TODO (axe) Specify class name as kJavaDescriptor instead of a class variable
std::string jClassName_;
private:
jni::global_ref<JTurboModule> instance_;
jclass findClass(JNIEnv *env) const;
};
} // namespace react
} // namespace facebook