Skip to content

Commit

Permalink
fix(🤖): bridgeless support on Android (#2269)
Browse files Browse the repository at this point in the history
Co-authored-by: William Candillon <[email protected]>
  • Loading branch information
Kudo and wcandillon authored Mar 11, 2024
1 parent 93ebd7f commit 789decc
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 19 deletions.
32 changes: 32 additions & 0 deletions package/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ target_include_directories(
"${NODE_MODULES_DIR}/react-native/ReactCommon/jsi"
"${NODE_MODULES_DIR}/react-native/ReactCommon"
"${NODE_MODULES_DIR}/react-native/ReactCommon/react/nativemodule/core"
"${NODE_MODULES_DIR}/react-native/ReactCommon/runtimeexecutor"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni"

cpp/jni/include
Expand Down Expand Up @@ -178,6 +180,34 @@ else()
endif()
message("-- FBJNI : " ${FBJNI_LIBRARY})

unset(REACTNATIVEJNI_LIB CACHE)
if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71)
# RN 0.71 distributes prebuilt binaries.
set (REACTNATIVEJNI_LIB "ReactAndroid::reactnativejni")
else()
find_library(
REACTNATIVEJNI_LIB
reactnativejni
PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH
)
endif()
message("-- REACTNATIVEJNI : " ${REACTNATIVEJNI_LIB})

unset(RUNTIMEEXECUTOR_LIB CACHE)
if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71)
# RN 0.71 distributes prebuilt binaries.
set (RUNTIMEEXECUTOR_LIB "ReactAndroid::runtimeexecutor")
else()
find_library(
RUNTIMEEXECUTOR_LIB
runtimeexecutor
PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH
)
endif()
message("-- RUNTIMEEXECUTOR : " ${RUNTIMEEXECUTOR_LIB})

unset(TURBOMODULES_LIB CACHE)
if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71)
# RN 0.71 distributes prebuilt binaries.
Expand All @@ -199,6 +229,8 @@ target_link_libraries(
${FBJNI_LIBRARY}
${REACT_LIB}
${JSI_LIB}
${REACTNATIVEJNI_LIB}
${RUNTIMEEXECUTOR_LIB}
${TURBOMODULES_LIB}
${SKIA_SVG_LIB}
${SKIA_SKSHAPER_LIB}
Expand Down
12 changes: 12 additions & 0 deletions package/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ android {
"src/paper/java",
]
}

if (REACT_NATIVE_VERSION >= 74) {
srcDirs += [
"src/reactnative74/java"
]
} else {
srcDirs += [
"src/reactnative69/java"
]
}
}
}

Expand All @@ -182,6 +192,8 @@ android {
"**/libfbjni.so",
"**/libjsi.so",
"**/libreact_nativemodule_core.so",
"**/libreactnativejni.so",
"**/libruntimeexecutor.so",
"**/libturbomodulejsijni.so",
"META-INF/**"
]
Expand Down
43 changes: 38 additions & 5 deletions package/android/cpp/jni/JniSkiaManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@

#include <RNSkManager.h>

namespace {

// For bridgeless mode, currently we don't have a way to get the JSCallInvoker
// from Java. Workaround to use RuntimeExecutor to simulate the behavior of
// JSCallInvoker. In the future when bridgeless mode is a standard and no more
// backward compatible to be considered, we could just use RuntimeExecutor to
// run task on JS thread.
class BridgelessJSCallInvoker : public facebook::react::CallInvoker {
public:
explicit BridgelessJSCallInvoker(
facebook::react::RuntimeExecutor runtimeExecutor)
: runtimeExecutor_(std::move(runtimeExecutor)) {}

void invokeAsync(std::function<void()> &&func) noexcept override {
runtimeExecutor_(
[func = std::move(func)](facebook::jsi::Runtime &runtime) { func(); });
}

void invokeSync(std::function<void()> &&func) override {
throw std::runtime_error(
"Synchronous native -> JS calls are currently not supported.");
}

private:
facebook::react::RuntimeExecutor runtimeExecutor_;

}; // class BridgelessJSCallInvoker

} // namespace

namespace RNSkia {

namespace jsi = facebook::jsi;
Expand All @@ -22,14 +52,17 @@ void JniSkiaManager::registerNatives() {

// JNI init
jni::local_ref<jni::HybridClass<JniSkiaManager>::jhybriddata>
JniSkiaManager::initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext,
JSCallInvokerHolder jsCallInvokerHolder,
JavaPlatformContext skiaContext) {
JniSkiaManager::initHybrid(
jni::alias_ref<jhybridobject> jThis, jlong jsContext,
jni::alias_ref<facebook::react::JRuntimeExecutor::javaobject>
jRuntimeExecutor,
JavaPlatformContext skiaContext) {

auto jsCallInvoker = std::make_shared<BridgelessJSCallInvoker>(
jRuntimeExecutor->cthis()->get());
// cast from JNI hybrid objects to C++ instances
return makeCxxInstance(jThis, reinterpret_cast<jsi::Runtime *>(jsContext),
jsCallInvokerHolder->cthis()->getCallInvoker(),
skiaContext->cthis());
jsCallInvoker, skiaContext->cthis());
}

void JniSkiaManager::initializeRuntime() {
Expand Down
4 changes: 0 additions & 4 deletions package/android/cpp/jni/include/JniPlatformContext.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once

#include <ReactCommon/CallInvokerHolder.h>
#include <fbjni/fbjni.h>

#include <exception>
Expand All @@ -18,9 +17,6 @@ namespace RNSkia {
namespace jsi = facebook::jsi;
namespace jni = facebook::jni;

using JSCallInvokerHolder =
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>;

class JniPlatformContext : public jni::HybridClass<JniPlatformContext> {
public:
static auto constexpr kJavaDescriptor =
Expand Down
7 changes: 3 additions & 4 deletions package/android/cpp/jni/include/JniSkiaManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
#include <react/jni/JRuntimeExecutor.h>

#include <JniPlatformContext.h>
#include <RNSkAndroidPlatformContext.h>
Expand All @@ -17,9 +18,6 @@ class RNSkManager;

namespace jsi = facebook::jsi;

using JSCallInvokerHolder =
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>;

using JavaPlatformContext = jni::alias_ref<JniPlatformContext::javaobject>;

class JniSkiaManager : public jni::HybridClass<JniSkiaManager> {
Expand All @@ -30,7 +28,8 @@ class JniSkiaManager : public jni::HybridClass<JniSkiaManager> {

static jni::local_ref<jni::HybridClass<JniSkiaManager>::jhybriddata>
initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext,
JSCallInvokerHolder jsCallInvokerHolder,
jni::alias_ref<facebook::react::JRuntimeExecutor::javaobject>
jRuntimeExecutor,
JavaPlatformContext platformContext);

static void registerNatives();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import com.facebook.jni.HybridData;
import com.facebook.jni.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.ThemedReactContext;

public class SkiaDomView extends SkiaBaseView {
@DoNotStrip
private HybridData mHybridData;

public SkiaDomView(Context context) {
public SkiaDomView(ThemedReactContext context) {
super(context);
RNSkiaModule skiaModule = ((ReactContext) context).getNativeModule(RNSkiaModule.class);
RNSkiaModule skiaModule = context.getReactApplicationContext().getNativeModule(RNSkiaModule.class);
mHybridData = initHybrid(skiaModule.getSkiaManager());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
import com.facebook.react.bridge.RuntimeExecutor;

@DoNotStrip
public class SkiaManager {
Expand All @@ -22,11 +22,11 @@ public class SkiaManager {
super();
mContext = context;

CallInvokerHolderImpl holder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder();
RuntimeExecutor runtimeExecutor = ReactNativeCompatible.getRuntimeExecutor(context);

mPlatformContext = new PlatformContext(context);

mHybridData = initHybrid(context.getJavaScriptContextHolder().get(), holder, mPlatformContext);
mHybridData = initHybrid(context.getJavaScriptContextHolder().get(), runtimeExecutor, mPlatformContext);

initializeRuntime();
}
Expand All @@ -48,7 +48,7 @@ public PlatformContext getPlatformContext() {
public void onHostPause() { mPlatformContext.onPause(); }

// private C++ functions
private native HybridData initHybrid(long jsContext, CallInvokerHolderImpl jsCallInvokerHolder,
private native HybridData initHybrid(long jsContext, RuntimeExecutor runtimeExecutor,
PlatformContext platformContext);

private native void initializeRuntime();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shopify.reactnative.skia;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.RuntimeExecutor;

/* package */ final class ReactNativeCompatible {
public static RuntimeExecutor getRuntimeExecutor(ReactContext context) {
return context.getCatalystInstance().getRuntimeExecutor();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.shopify.reactnative.skia;

import androidx.annotation.OptIn;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.RuntimeExecutor;
import com.facebook.react.common.annotations.FrameworkAPI;

/* package */ final class ReactNativeCompatible {
@OptIn(markerClass = FrameworkAPI.class)
public static RuntimeExecutor getRuntimeExecutor(ReactContext context) {
return context.getRuntimeExecutor();
}
}

0 comments on commit 789decc

Please sign in to comment.